Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f3ffe06
Remove NAudio, add SoundFlow library.
markjamesm Mar 2, 2025
ca78dff
Refactor IPlayer to ISoundEngine.cs as it makes more sense conceptually.
markjamesm Mar 2, 2025
a5642b2
Rename SoundFlow classto SoundEngine.
markjamesm Mar 2, 2025
0e71076
Working on implementing new cross-platform sound engine using SoundFl…
markjamesm Mar 2, 2025
39b8a6a
Implement CurrentTime & TrackLength methods.
markjamesm Mar 3, 2025
041cf3b
Implement seek functionality
markjamesm Mar 3, 2025
375103f
Fix OpenFile() logic to make sure player is stopped before playing ne…
markjamesm Mar 9, 2025
8d68333
Implement Play From Playlist functionality using new sound engine.
markjamesm Mar 11, 2025
c1f16d9
Tests for new SoundEngine.
markjamesm Mar 11, 2025
62e3de5
Display not implemented yet notice for streaming playback.
markjamesm Mar 11, 2025
ee0eaa8
Working on implementing streaming functionality.
markjamesm Mar 11, 2025
c386821
Update tests.
markjamesm Mar 11, 2025
ff137b3
Refactor Tui namespace to Interface.
markjamesm Mar 11, 2025
2881945
Finish implementing streaming functionality.
markjamesm Mar 11, 2025
1e189c1
Update README.md to reflect new version.
markjamesm Mar 12, 2025
18fd226
Enable nullable, update version number.
markjamesm Mar 12, 2025
499d7a4
Refactor player status cases.
markjamesm Mar 12, 2025
e12cc4f
Update enums to use proper naming convention.
markjamesm Mar 12, 2025
280ab78
Refactor project to make program sections clearer.
markjamesm Mar 12, 2025
a3df4e6
Add Converters.cs class to convert files and URLs to Stream type.
markjamesm Mar 12, 2025
7ccfc63
Remove unnecessary using statement.
markjamesm Mar 12, 2025
9d85c1d
Update README.md
markjamesm Mar 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions MusicSharpTests/SoundFlowPlayerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace MusicSharpTests;

public class SoundFlowPlayerTests
{
[Test]
public void Play_NullFile()
{
// arrange
var isFileValid = File.Exists("thisisafail.exe");

// act and assert
Assert.That(isFileValid, Is.False);
}
}
26 changes: 0 additions & 26 deletions MusicSharpTests/WinPlayerTests.cs

This file was deleted.

15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
# MusicSharp
[![.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)

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.
A cross-platform Terminal User Interface (TUI) music player written in C# (.NET 8) with the goal of being minimalistic and light on resources.

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/)
MusicSharp makes use of the [SoundFlow](https://github.com/LSXPrime/SoundFlow) and [Terminal.Gui](https://github.com/migueldeicaza/gui.cs) libraries.

## Screenshot

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

## Features

- Cross-platform support (Windows, Mac, Linux).
- Play audio files.
- Load music playlists (M3U)
- Audio streaming.
- Lightweight

## Planned

- Save playlists.
- Cross platform support.
- Load and play from music playlists (M3U).
- Streaming support.

## Installation

Expand Down
33 changes: 12 additions & 21 deletions src/SoundEngines/IPlayer.cs → src/AudioPlayer/IPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@
// Licensed under the GNU GPL v3 License. See LICENSE in the project root for license information.
// </copyright>

using System;
using System.IO;
using MusicSharp.Enums;

namespace MusicSharp.SoundEngines;
namespace MusicSharp.AudioPlayer;

/// <summary>
/// Defines the methods an audio player class should implement.
/// </summary>
public interface IPlayer
public interface IPlayer: IDisposable
{
/// <summary>
/// Gets or sets a value indicating whether the audio player is playing.
/// </summary>
ePlayerStatus PlayerStatus { get; set; }
EPlayerStatus PlayerStatus { get; set; }

/// <summary>
/// Gets or sets the last file opened by the player.
Expand All @@ -25,19 +27,14 @@ public interface IPlayer
/// Method to play audio.
/// </summary>
/// <param name="path">The filepath of the audio file to play.</param>
void OpenFile(string path);

/// <summary>
/// Method to play an audio stream from a URL.
/// </summary>
/// <param name="streamUrl">The stream URL of the audio file to play.</param>
void OpenStream(string streamUrl);

/// /// <param name="stream">The audio stream.</param>
void Play(Stream stream);

/// <summary>
/// Method to pause audio playback.
/// Method to play or pause depending on state.
/// </summary>
void PlayPause();

/// <summary>
/// Method to stop audio playback.
/// </summary>
Expand All @@ -53,23 +50,17 @@ public interface IPlayer
/// </summary>
void DecreaseVolume();

/// <summary>
/// Play an audio file contained in a playlist.
/// </summary>
/// <param name="path">The path to the audio file.</param>
void PlayFromPlaylist(string path);

/// <summary>
/// Returns the current playtime of the audioFileReader instance.
/// </summary>
/// <returns>The current time played as TimeSpan.</returns>
System.TimeSpan CurrentTime();
float CurrentTime();

/// <summary>
/// Returns the total track length in timespan format.
/// </summary>
/// <returns>The length of the track in timespan format.</returns>
System.TimeSpan TrackLength();
float TrackLength();

/// <summary>
/// Skip ahead in the audio file 5s.
Expand Down
124 changes: 124 additions & 0 deletions src/AudioPlayer/SoundFlowPlayer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System;
using System.IO;
using MusicSharp.Enums;
using SoundFlow.Backends.MiniAudio;
using SoundFlow.Components;
using SoundFlow.Providers;

namespace MusicSharp.AudioPlayer;

// Cross-platform sound engine that works for all devices which
// the .NET platform runs on.
public sealed class SoundFlowPlayer : IPlayer
{
private readonly MiniAudioEngine _soundEngine;
private SoundPlayer _player;

public EPlayerStatus PlayerStatus { get; set; }
public string LastFileOpened { get; set; }


public SoundFlowPlayer(MiniAudioEngine soundEngine)

Check warning on line 21 in src/AudioPlayer/SoundFlowPlayer.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'LastFileOpened' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 21 in src/AudioPlayer/SoundFlowPlayer.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_player' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
{
_soundEngine = soundEngine;
}

public void Play(Stream stream)
{
if (_player != null)
{
_player.Stop();
}

_player = new SoundPlayer(new StreamDataProvider(stream));

// Add the player to the master mixer. This connects the player's output to the audio engine's output.
Mixer.Master.AddComponent(_player);

_player.Play();
PlayerStatus = EPlayerStatus.Playing;
}

public void PlayPause()
{
switch (PlayerStatus)
{
case EPlayerStatus.Playing:
_player.Pause();
PlayerStatus = EPlayerStatus.Paused;
break;
case EPlayerStatus.Paused:
case EPlayerStatus.Stopped:
_player.Play();
PlayerStatus = EPlayerStatus.Playing;
break;
default:
throw new ArgumentOutOfRangeException();
}
}

public void Stop()
{
if (PlayerStatus != EPlayerStatus.Stopped)
{
_player.Stop();
PlayerStatus = EPlayerStatus.Stopped;
}
}

public void IncreaseVolume()
{
// Need to verify what SoundFlow's max volume level is
// For now this should be enough based on testing
if (_player.Volume < 2.0f)
{
_player.Volume += .1f;
}
}

public void DecreaseVolume()
{
// Ensure that the volume isn't negative
// otherwise the player will crash
if (_player.Volume > .1f)
{
_player.Volume -= .1f;
}

if (_player.Volume <= .1f)
{
_player.Volume = 0f;
}
}

public void SeekForward()
{
if (_player.Time < _player.Duration - 5f)
{
_player.Seek(_player.Time + 5f);
}
}

public void SeekBackwards()
{
if (_player.Time > 5f)
{
_player.Seek(_player.Time - 5f);
}
}

public float CurrentTime()
{
return _player.Time;
}

public float TrackLength()
{
return _player.Duration;
}

public void Dispose()
{
_soundEngine.Dispose();
}
}
7 changes: 7 additions & 0 deletions src/Enums/EFileType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MusicSharp.Enums;

public enum EFileType
{
File,
Stream
}
4 changes: 2 additions & 2 deletions src/Enums/ePlayerStatus.cs → src/Enums/EPlayerStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
/// <summary>
/// The status of the audio player.
/// </summary>
public enum ePlayerStatus
public enum EPlayerStatus
{
Playing,
Paused,
Stopped
}
}
26 changes: 26 additions & 0 deletions src/Helpers/Converters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

namespace MusicSharp.Helpers;

public class Converters
{
private readonly HttpClient _httpClient;

public Converters(HttpClient httpClient)
{
_httpClient = httpClient;
}

public static Stream ConvertFileToStream(string path)
{
return File.OpenRead(path);
}

public async Task<Stream> ConvertUrlToStream(string url)
{
var stream = await _httpClient.GetStreamAsync(url);
return stream;
}
}
10 changes: 3 additions & 7 deletions src/Models/PlaylistLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// </copyright>

using System.Collections.Generic;
using System.Linq;
using ATL.Playlist;

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

foreach (var s in theReader.FilePaths)
{
filePaths.Add(s);
}

return filePaths;
// Fix space formatting as SoundFlow doesn't support encoded spaces
return theReader.FilePaths.Select(s => s.Replace("%20", " ")).ToList();
}
}
5 changes: 3 additions & 2 deletions src/MusicSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Version>0.4.9</Version>
<Version>1.0.0</Version>
<Authors>Mark-James McDougall</Authors>
<Company>Mark-James McDougall</Company>
<Nullable>enable</Nullable>
<PackageIcon></PackageIcon>
<ApplicationIcon>MusicSharp.ico</ApplicationIcon>
</PropertyGroup>
Expand All @@ -19,7 +20,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="NAudio" Version="1.10.0" />
<PackageReference Include="SoundFlow" Version="1.0.3" />
<PackageReference Include="Terminal.Gui" Version="0.90.3" />
<PackageReference Include="z440.atl.core" Version="3.13.0" />
</ItemGroup>
Expand Down
Loading