Skip to content

Commit 6194e65

Browse files
authored
Merge pull request #37 from markjamesm/soundengine-rewrite
Soundengine rewrite to enable cross-platform support.
2 parents 42bcc78 + 9d85c1d commit 6194e65

File tree

13 files changed

+273
-324
lines changed

13 files changed

+273
-324
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace MusicSharpTests;
2+
3+
public class SoundFlowPlayerTests
4+
{
5+
[Test]
6+
public void Play_NullFile()
7+
{
8+
// arrange
9+
var isFileValid = File.Exists("thisisafail.exe");
10+
11+
// act and assert
12+
Assert.That(isFileValid, Is.False);
13+
}
14+
}

MusicSharpTests/WinPlayerTests.cs

Lines changed: 0 additions & 26 deletions
This file was deleted.

README.md

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
# MusicSharp
22
[![.NET](https://github.com/markjamesm/Baseball-Sharp/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/markjamesm/MusicSharp/actions) [![C#](https://img.shields.io/badge/Language-CSharp-darkgreen.svg)](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) [![License](https://img.shields.io/badge/License-GPL-orange.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
33

4-
MusicSharp is a cross-platform Terminal User Interface (TUI) music player written in C# (.NET 8) with the goal of being minimalistic and light on resources.
4+
A cross-platform Terminal User Interface (TUI) music player written in C# (.NET 8) with the goal of being minimalistic and light on resources.
55

6-
Currently in beta, MusicSharp makes use of the [NAudio](https://github.com/naudio/NAudio) and [Terminal.Gui](https://github.com/migueldeicaza/gui.cs) libraries. A project build log can be [found here](https://markjames.dev/blog/developing-a-cli-music-player-csharp/)
6+
MusicSharp makes use of the [SoundFlow](https://github.com/LSXPrime/SoundFlow) and [Terminal.Gui](https://github.com/migueldeicaza/gui.cs) libraries.
77

88
## Screenshot
99

1010
<img src="https://user-images.githubusercontent.com/20845425/99861949-06763200-2b66-11eb-9d5a-9bf2ea5151ee.png" alt="Screenshot of MusicSharp">
1111

1212
## Features
1313

14+
- Cross-platform support (Windows, Mac, Linux).
1415
- Play audio files.
15-
- Load music playlists (M3U)
16-
- Audio streaming.
17-
- Lightweight
18-
19-
## Planned
20-
21-
- Save playlists.
22-
- Cross platform support.
16+
- Load and play from music playlists (M3U).
17+
- Streaming support.
2318

2419
## Installation
2520

src/SoundEngines/IPlayer.cs renamed to src/AudioPlayer/IPlayer.cs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@
22
// Licensed under the GNU GPL v3 License. See LICENSE in the project root for license information.
33
// </copyright>
44

5+
using System;
6+
using System.IO;
57
using MusicSharp.Enums;
68

7-
namespace MusicSharp.SoundEngines;
9+
namespace MusicSharp.AudioPlayer;
810

911
/// <summary>
1012
/// Defines the methods an audio player class should implement.
1113
/// </summary>
12-
public interface IPlayer
14+
public interface IPlayer: IDisposable
1315
{
1416
/// <summary>
1517
/// Gets or sets a value indicating whether the audio player is playing.
1618
/// </summary>
17-
ePlayerStatus PlayerStatus { get; set; }
19+
EPlayerStatus PlayerStatus { get; set; }
1820

1921
/// <summary>
2022
/// Gets or sets the last file opened by the player.
@@ -25,19 +27,14 @@ public interface IPlayer
2527
/// Method to play audio.
2628
/// </summary>
2729
/// <param name="path">The filepath of the audio file to play.</param>
28-
void OpenFile(string path);
29-
30-
/// <summary>
31-
/// Method to play an audio stream from a URL.
32-
/// </summary>
33-
/// <param name="streamUrl">The stream URL of the audio file to play.</param>
34-
void OpenStream(string streamUrl);
35-
30+
/// /// <param name="stream">The audio stream.</param>
31+
void Play(Stream stream);
32+
3633
/// <summary>
37-
/// Method to pause audio playback.
34+
/// Method to play or pause depending on state.
3835
/// </summary>
3936
void PlayPause();
40-
37+
4138
/// <summary>
4239
/// Method to stop audio playback.
4340
/// </summary>
@@ -53,23 +50,17 @@ public interface IPlayer
5350
/// </summary>
5451
void DecreaseVolume();
5552

56-
/// <summary>
57-
/// Play an audio file contained in a playlist.
58-
/// </summary>
59-
/// <param name="path">The path to the audio file.</param>
60-
void PlayFromPlaylist(string path);
61-
6253
/// <summary>
6354
/// Returns the current playtime of the audioFileReader instance.
6455
/// </summary>
6556
/// <returns>The current time played as TimeSpan.</returns>
66-
System.TimeSpan CurrentTime();
57+
float CurrentTime();
6758

6859
/// <summary>
6960
/// Returns the total track length in timespan format.
7061
/// </summary>
7162
/// <returns>The length of the track in timespan format.</returns>
72-
System.TimeSpan TrackLength();
63+
float TrackLength();
7364

7465
/// <summary>
7566
/// Skip ahead in the audio file 5s.

src/AudioPlayer/SoundFlowPlayer.cs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System;
2+
using System.IO;
3+
using MusicSharp.Enums;
4+
using SoundFlow.Backends.MiniAudio;
5+
using SoundFlow.Components;
6+
using SoundFlow.Providers;
7+
8+
namespace MusicSharp.AudioPlayer;
9+
10+
// Cross-platform sound engine that works for all devices which
11+
// the .NET platform runs on.
12+
public sealed class SoundFlowPlayer : IPlayer
13+
{
14+
private readonly MiniAudioEngine _soundEngine;
15+
private SoundPlayer _player;
16+
17+
public EPlayerStatus PlayerStatus { get; set; }
18+
public string LastFileOpened { get; set; }
19+
20+
21+
public SoundFlowPlayer(MiniAudioEngine soundEngine)
22+
{
23+
_soundEngine = soundEngine;
24+
}
25+
26+
public void Play(Stream stream)
27+
{
28+
if (_player != null)
29+
{
30+
_player.Stop();
31+
}
32+
33+
_player = new SoundPlayer(new StreamDataProvider(stream));
34+
35+
// Add the player to the master mixer. This connects the player's output to the audio engine's output.
36+
Mixer.Master.AddComponent(_player);
37+
38+
_player.Play();
39+
PlayerStatus = EPlayerStatus.Playing;
40+
}
41+
42+
public void PlayPause()
43+
{
44+
switch (PlayerStatus)
45+
{
46+
case EPlayerStatus.Playing:
47+
_player.Pause();
48+
PlayerStatus = EPlayerStatus.Paused;
49+
break;
50+
case EPlayerStatus.Paused:
51+
case EPlayerStatus.Stopped:
52+
_player.Play();
53+
PlayerStatus = EPlayerStatus.Playing;
54+
break;
55+
default:
56+
throw new ArgumentOutOfRangeException();
57+
}
58+
}
59+
60+
public void Stop()
61+
{
62+
if (PlayerStatus != EPlayerStatus.Stopped)
63+
{
64+
_player.Stop();
65+
PlayerStatus = EPlayerStatus.Stopped;
66+
}
67+
}
68+
69+
public void IncreaseVolume()
70+
{
71+
// Need to verify what SoundFlow's max volume level is
72+
// For now this should be enough based on testing
73+
if (_player.Volume < 2.0f)
74+
{
75+
_player.Volume += .1f;
76+
}
77+
}
78+
79+
public void DecreaseVolume()
80+
{
81+
// Ensure that the volume isn't negative
82+
// otherwise the player will crash
83+
if (_player.Volume > .1f)
84+
{
85+
_player.Volume -= .1f;
86+
}
87+
88+
if (_player.Volume <= .1f)
89+
{
90+
_player.Volume = 0f;
91+
}
92+
}
93+
94+
public void SeekForward()
95+
{
96+
if (_player.Time < _player.Duration - 5f)
97+
{
98+
_player.Seek(_player.Time + 5f);
99+
}
100+
}
101+
102+
public void SeekBackwards()
103+
{
104+
if (_player.Time > 5f)
105+
{
106+
_player.Seek(_player.Time - 5f);
107+
}
108+
}
109+
110+
public float CurrentTime()
111+
{
112+
return _player.Time;
113+
}
114+
115+
public float TrackLength()
116+
{
117+
return _player.Duration;
118+
}
119+
120+
public void Dispose()
121+
{
122+
_soundEngine.Dispose();
123+
}
124+
}

src/Enums/EFileType.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace MusicSharp.Enums;
2+
3+
public enum EFileType
4+
{
5+
File,
6+
Stream
7+
}

src/Enums/ePlayerStatus.cs renamed to src/Enums/EPlayerStatus.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
/// <summary>
44
/// The status of the audio player.
55
/// </summary>
6-
public enum ePlayerStatus
6+
public enum EPlayerStatus
77
{
88
Playing,
99
Paused,
1010
Stopped
11-
}
11+
}

src/Helpers/Converters.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.IO;
2+
using System.Net.Http;
3+
using System.Threading.Tasks;
4+
5+
namespace MusicSharp.Helpers;
6+
7+
public class Converters
8+
{
9+
private readonly HttpClient _httpClient;
10+
11+
public Converters(HttpClient httpClient)
12+
{
13+
_httpClient = httpClient;
14+
}
15+
16+
public static Stream ConvertFileToStream(string path)
17+
{
18+
return File.OpenRead(path);
19+
}
20+
21+
public async Task<Stream> ConvertUrlToStream(string url)
22+
{
23+
var stream = await _httpClient.GetStreamAsync(url);
24+
return stream;
25+
}
26+
}

src/Models/PlaylistLoader.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// </copyright>
44

55
using System.Collections.Generic;
6+
using System.Linq;
67
using ATL.Playlist;
78

89
namespace MusicSharp.Models;
@@ -22,14 +23,9 @@ public static class PlaylistLoader
2223
/// <param name="userPlaylist">The user specified playlist path.</param>
2324
public static List<string> LoadPlaylist(string userPlaylist)
2425
{
25-
var filePaths = new List<string>();
2626
var theReader = PlaylistIOFactory.GetInstance().GetPlaylistIO(userPlaylist);
2727

28-
foreach (var s in theReader.FilePaths)
29-
{
30-
filePaths.Add(s);
31-
}
32-
33-
return filePaths;
28+
// Fix space formatting as SoundFlow doesn't support encoded spaces
29+
return theReader.FilePaths.Select(s => s.Replace("%20", " ")).ToList();
3430
}
3531
}

src/MusicSharp.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>net8.0</TargetFramework>
6-
<Version>0.4.9</Version>
6+
<Version>1.0.0</Version>
77
<Authors>Mark-James McDougall</Authors>
88
<Company>Mark-James McDougall</Company>
9+
<Nullable>enable</Nullable>
910
<PackageIcon></PackageIcon>
1011
<ApplicationIcon>MusicSharp.ico</ApplicationIcon>
1112
</PropertyGroup>
@@ -19,7 +20,7 @@
1920
</ItemGroup>
2021

2122
<ItemGroup>
22-
<PackageReference Include="NAudio" Version="1.10.0" />
23+
<PackageReference Include="SoundFlow" Version="1.0.3" />
2324
<PackageReference Include="Terminal.Gui" Version="0.90.3" />
2425
<PackageReference Include="z440.atl.core" Version="3.13.0" />
2526
</ItemGroup>

0 commit comments

Comments
 (0)