player watcher firing events

This commit is contained in:
andy 2021-09-26 12:11:44 +01:00
parent 2d2fdde623
commit 55bba58fca
10 changed files with 417 additions and 64 deletions

26
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Selector.Tests/bin/Debug/net5.0/Selector.Tests.dll",
"args": [],
"cwd": "${workspaceFolder}/Selector.Tests",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

42
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,42 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Selector.Tests/Selector.Tests.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/Selector.Tests/Selector.Tests.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/Selector.Tests/Selector.Tests.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@ -115,6 +115,31 @@ namespace Selector.Tests
var eq = new UriEquality(); var eq = new UriEquality();
eq.Album(album1, album2).Should().Be(shouldEqual); eq.Album(album1, album2).Should().Be(shouldEqual);
} }
public static IEnumerable<object[]> ArtistData =>
new List<object[]>
{
// SAME
new object[] {
Helper.SimpleArtist("1"),
Helper.SimpleArtist("1"),
true
},
// DIFFERENT
new object[] {
Helper.SimpleArtist("1"),
Helper.SimpleArtist("2"),
false
}
};
[Theory]
[MemberData(nameof(ArtistData))]
public void ArtistEquality(SimpleArtist artist1, SimpleArtist artist2, bool shouldEqual)
{
var eq = new UriEquality();
eq.Artist(artist1, artist2).Should().Be(shouldEqual);
}
} }
public class StringEqualityTests public class StringEqualityTests
@ -223,5 +248,30 @@ namespace Selector.Tests
var eq = new StringEquality(); var eq = new StringEquality();
eq.Album(album1, album2).Should().Be(shouldEqual); eq.Album(album1, album2).Should().Be(shouldEqual);
} }
public static IEnumerable<object[]> ArtistData =>
new List<object[]>
{
// SAME
new object[] {
Helper.SimpleArtist("1"),
Helper.SimpleArtist("1"),
true
},
// DIFFERENT
new object[] {
Helper.SimpleArtist("1"),
Helper.SimpleArtist("2"),
false
}
};
[Theory]
[MemberData(nameof(ArtistData))]
public void ArtistEquality(SimpleArtist artist1, SimpleArtist artist2, bool shouldEqual)
{
var eq = new StringEquality();
eq.Artist(artist1, artist2).Should().Be(shouldEqual);
}
} }
} }

View File

@ -49,5 +49,23 @@ namespace Selector.Tests
Uri = name Uri = name
}; };
} }
public static CurrentlyPlaying CurrentlyPlaying(FullTrack track, bool isPlaying = true, string context = null)
{
return new CurrentlyPlaying()
{
Context = Context(context ?? track.Uri),
IsPlaying = isPlaying,
Item = track
};
}
public static Context Context(string uri)
{
return new Context()
{
Uri = uri
};
}
} }
} }

View File

@ -11,60 +11,131 @@ namespace Selector.Tests
{ {
public class PlayerWatcherTests public class PlayerWatcherTests
{ {
//public static IEnumerable<object[]> TrackData => public static IEnumerable<object[]> NowPlayingData =>
//new List<object[]> new List<object[]>
//{ {
// new object[] { new List<CurrentlyPlaying>(){ new object[] { new List<CurrentlyPlaying>(){
// new CurrentlyPlaying(){ Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1")),
// Item = new FullTrack() { Helper.CurrentlyPlaying(Helper.FullTrack("track2", "album2", "artist2")),
Helper.CurrentlyPlaying(Helper.FullTrack("track3", "album3", "artist3")),
// } }
// } }
// }, 1 } };
//};
[Theory]
//[Theory] [MemberData(nameof(NowPlayingData))]
//[MemberData(nameof(TrackData))] public async void NowPlaying(List<CurrentlyPlaying> playing)
//public void Test1(List<CurrentlyPlaying> playing) {
//{ var playingQueue = new Queue<CurrentlyPlaying>(playing);
// var spotMock = new Mock<IPlayerClient>();
// // spotMock.Setup(spot => spot.GetCurrentlyPlaying(It.IsAny<PlayerCurrentlyPlayingRequest>())).Returns(); var spotMock = new Mock<IPlayerClient>();
// // var watch = new Watcher(); var scheduleMock = new Mock<IScheduler>();
//} var eq = new UriEquality();
// [Fact] spotMock.Setup(s => s.GetCurrentlyPlaying(It.IsAny<PlayerCurrentlyPlayingRequest>()).Result).Returns(playingQueue.Dequeue);
// public void Test2()
// { var watcher = new PlayerWatcher(spotMock.Object, scheduleMock.Object, eq);
// var artist = new SimpleArtist(){
// Name = "test" for(var i = 0; i < playing.Count; i++)
// }; {
// var track = new FullTrack() { await watcher.WatchOne();
// Name = "Test", var current = watcher.NowPlaying();
// Album = new SimpleAlbum() { current.Should().Be(playing[i]);
// Name = "test", }
// Artists = new List<SimpleArtist>(){ }
// artist
// } public static IEnumerable<object[]> EventsData =>
// }, new List<object[]>
// Artists = new List<SimpleArtist>(){ {
// artist // NO CHANGING
// } new object[] { new List<CurrentlyPlaying>(){
// }; Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: true, context: "context1"),
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: true, context: "context1"),
// var track2 = new FullTrack() { Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: true, context: "context1"),
// Name = "Test", },
// Album = new SimpleAlbum() { // to raise
// Name = "test", new List<string>(){ },
// Artists = new List<SimpleArtist>(){ // to not raise
// artist new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange", "ContextChange", "PlayingChange" }
// } },
// }, // TRACK CHANGE
// Artists = new List<SimpleArtist>(){ new object[] { new List<CurrentlyPlaying>(){
// artist Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1")),
// } Helper.CurrentlyPlaying(Helper.FullTrack("track2", "album1", "artist1"))
// }; },
// to raise
// track.Should().Be(track2); new List<string>(){ "TrackChange" },
// } // to not raise
new List<string>(){ "AlbumChange", "ArtistChange" }
},
// ALBUM CHANGE
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1")),
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album2", "artist1"))
},
// to raise
new List<string>(){ "TrackChange", "AlbumChange" },
// to not raise
new List<string>(){ "ArtistChange" }
},
// ARTIST CHANGE
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1")),
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist2"))
},
// to raise
new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange" },
// to not raise
new List<string>(){ }
},
// CONTEXT CHANGE
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), context: "context1"),
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), context: "context2")
},
// to raise
new List<string>(){ "ContextChange" },
// to not raise
new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange" }
},
// PLAYING CHANGE
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: true, context: "context1"),
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: false, context: "context1")
},
// to raise
new List<string>(){ "PlayingChange" },
// to not raise
new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange", "ContextChange" }
}
};
[Theory]
[MemberData(nameof(EventsData))]
public async void Events(List<CurrentlyPlaying> playing, List<string> toRaise, List<string> toNotRaise)
{
var playingQueue = new Queue<CurrentlyPlaying>(playing);
var spotMock = new Mock<IPlayerClient>();
var scheduleMock = new Mock<IScheduler>();
var eq = new UriEquality();
spotMock.Setup(s => s.GetCurrentlyPlaying(It.IsAny<PlayerCurrentlyPlayingRequest>()).Result).Returns(playingQueue.Dequeue);
var watcher = new PlayerWatcher(spotMock.Object, scheduleMock.Object, eq);
using var monitoredWatcher = watcher.Monitor();
for(var i = 0; i < playing.Count; i++)
{
await watcher.WatchOne();
}
foreach(var raise in toRaise){
monitoredWatcher.Should().Raise(raise).WithSender(watcher);
}
foreach(var notRraise in toNotRaise){
monitoredWatcher.Should().NotRaise(notRraise);
}
}
} }
} }

View File

@ -5,10 +5,12 @@ namespace Selector {
public interface IEqualityChecker { public interface IEqualityChecker {
public bool Track(FullTrack track1, FullTrack track2, bool includingAlbum); public bool Track(FullTrack track1, FullTrack track2, bool includingAlbum);
public bool Episode(FullEpisode ep1, FullEpisode ep2);
public bool Album(FullAlbum album1, FullAlbum album2); public bool Album(FullAlbum album1, FullAlbum album2);
public bool Artist(FullArtist artist1, FullArtist artist2); public bool Artist(FullArtist artist1, FullArtist artist2);
public bool Track(SimpleTrack track1, SimpleTrack track2); public bool Track(SimpleTrack track1, SimpleTrack track2);
public bool Episode(SimpleEpisode ep1, SimpleEpisode ep2);
public bool Album(SimpleAlbum album1, SimpleAlbum album2); public bool Album(SimpleAlbum album1, SimpleAlbum album2);
public bool Artist(SimpleArtist artist1, SimpleArtist artist2); public bool Artist(SimpleArtist artist1, SimpleArtist artist2);

View File

@ -15,11 +15,18 @@ namespace Selector {
&& track1.Name == track2.Name && track1.Name == track2.Name
&& Enumerable.SequenceEqual(track1.Artists.Select(a => a.Name), track2.Artists.Select(a => a.Name)); && Enumerable.SequenceEqual(track1.Artists.Select(a => a.Name), track2.Artists.Select(a => a.Name));
} }
new public bool Episode(FullEpisode ep1, FullEpisode ep2)
{
return ep1.Uri == ep2.Uri;
}
new public bool Album(FullAlbum album1, FullAlbum album2) new public bool Album(FullAlbum album1, FullAlbum album2)
{ {
return album1.Name == album1.Name return album1.Name == album2.Name
&& Enumerable.SequenceEqual(album1.Artists.Select(a => a.Name), album2.Artists.Select(a => a.Name)); && Enumerable.SequenceEqual(album1.Artists.Select(a => a.Name), album2.Artists.Select(a => a.Name));
} }
new public bool Artist(FullArtist artist1, FullArtist artist2) new public bool Artist(FullArtist artist1, FullArtist artist2)
{ {
return artist1.Name == artist2.Name; return artist1.Name == artist2.Name;
@ -30,11 +37,18 @@ namespace Selector {
return track1.Name == track2.Name return track1.Name == track2.Name
&& Enumerable.SequenceEqual(track1.Artists.Select(a => a.Name), track2.Artists.Select(a => a.Name)); && Enumerable.SequenceEqual(track1.Artists.Select(a => a.Name), track2.Artists.Select(a => a.Name));
} }
new public bool Episode(SimpleEpisode ep1, SimpleEpisode ep2)
{
return ep1.Name == ep2.Name;
}
new public bool Album(SimpleAlbum album1, SimpleAlbum album2) new public bool Album(SimpleAlbum album1, SimpleAlbum album2)
{ {
return album1.Name == album1.Name return album1.Name == album2.Name
&& Enumerable.SequenceEqual(album1.Artists.Select(a => a.Name), album2.Artists.Select(a => a.Name)); && Enumerable.SequenceEqual(album1.Artists.Select(a => a.Name), album2.Artists.Select(a => a.Name));
} }
new public bool Artist(SimpleArtist artist1, SimpleArtist artist2) new public bool Artist(SimpleArtist artist1, SimpleArtist artist2)
{ {
return artist1.Name == artist2.Name; return artist1.Name == artist2.Name;

View File

@ -13,6 +13,12 @@ namespace Selector {
&& track1.Uri == track2.Uri && track1.Uri == track2.Uri
&& Enumerable.SequenceEqual(track1.Artists.Select(a => a.Uri), track2.Artists.Select(a => a.Uri)); && Enumerable.SequenceEqual(track1.Artists.Select(a => a.Uri), track2.Artists.Select(a => a.Uri));
} }
public bool Episode(FullEpisode ep1, FullEpisode ep2)
{
return ep1.Uri == ep2.Uri;
}
public bool Album(FullAlbum album1, FullAlbum album2) public bool Album(FullAlbum album1, FullAlbum album2)
{ {
return album1.Uri == album2.Uri return album1.Uri == album2.Uri
@ -28,6 +34,12 @@ namespace Selector {
return track1.Uri == track2.Uri return track1.Uri == track2.Uri
&& Enumerable.SequenceEqual(track1.Artists.Select(a => a.Uri), track2.Artists.Select(a => a.Uri)); && Enumerable.SequenceEqual(track1.Artists.Select(a => a.Uri), track2.Artists.Select(a => a.Uri));
} }
public bool Episode(SimpleEpisode ep1, SimpleEpisode ep2)
{
return ep1.Uri == ep2.Uri;
}
public bool Album(SimpleAlbum album1, SimpleAlbum album2) public bool Album(SimpleAlbum album1, SimpleAlbum album2)
{ {
return album1.Uri == album2.Uri return album1.Uri == album2.Uri

View File

@ -11,8 +11,9 @@ namespace Selector
public event EventHandler<ListeningChangeEventArgs> ArtistChange; public event EventHandler<ListeningChangeEventArgs> ArtistChange;
public event EventHandler<ListeningChangeEventArgs> ContextChange; public event EventHandler<ListeningChangeEventArgs> ContextChange;
public event EventHandler<ListeningChangeEventArgs> VolumeChange; // public event EventHandler<ListeningChangeEventArgs> VolumeChange;
public event EventHandler<ListeningChangeEventArgs> DeviceChange; // public event EventHandler<ListeningChangeEventArgs> DeviceChange;
public event EventHandler<ListeningChangeEventArgs> PlayingChange;
public CurrentlyPlaying NowPlaying(); public CurrentlyPlaying NowPlaying();
// recently playing // recently playing

View File

@ -17,21 +17,82 @@ namespace Selector
public event EventHandler<ListeningChangeEventArgs> ArtistChange; public event EventHandler<ListeningChangeEventArgs> ArtistChange;
public event EventHandler<ListeningChangeEventArgs> ContextChange; public event EventHandler<ListeningChangeEventArgs> ContextChange;
public event EventHandler<ListeningChangeEventArgs> VolumeChange; // public event EventHandler<ListeningChangeEventArgs> VolumeChange;
public event EventHandler<ListeningChangeEventArgs> DeviceChange; // public event EventHandler<ListeningChangeEventArgs> DeviceChange;
public event EventHandler<ListeningChangeEventArgs> PlayingChange;
private CurrentlyPlaying live { get; set; } private CurrentlyPlaying live { get; set; }
//public List<CurrentlyPlaying> LastPlays { get; private set; } private List<List<CurrentlyPlaying>> lastPlays { get; set; }
public PlayerWatcher(IPlayerClient spotifyClient, IScheduler sleepScheduler, IEqualityChecker equalityChecker) { public PlayerWatcher(IPlayerClient spotifyClient, IScheduler sleepScheduler, IEqualityChecker equalityChecker) {
this.spotifyClient = spotifyClient; this.spotifyClient = spotifyClient;
this.sleepScheduler = sleepScheduler; this.sleepScheduler = sleepScheduler;
this.equalityChecker = equalityChecker; this.equalityChecker = equalityChecker;
lastPlays = new List<List<CurrentlyPlaying>>();
} }
public async Task WatchOne() public async Task WatchOne()
{ {
var polledCurrent = await spotifyClient.GetCurrentlyPlaying(new PlayerCurrentlyPlayingRequest());
StoreCurrentPlaying(polledCurrent);
CurrentlyPlaying existing;
if(live is null) {
live = polledCurrent;
existing = polledCurrent;
}
else {
existing = live;
live = polledCurrent;
}
try{
var existingItem = (FullTrack) existing.Item;
var currentItem = (FullTrack) live.Item;
if(!equalityChecker.Track(existingItem, currentItem, true)) {
OnTrackChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
}
if(!equalityChecker.Album(existingItem.Album, currentItem.Album)) {
OnAlbumChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
}
if(!equalityChecker.Artist(existingItem.Artists[0], currentItem.Artists[0])) {
OnArtistChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
}
if(!equalityChecker.Context(existing.Context, live.Context)) {
OnContextChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
}
if(existing.IsPlaying != live.IsPlaying) {
OnPlayingChange(new ListeningChangeEventArgs(){
Previous = existing,
Current = live
});
}
}
catch(InvalidCastException)
{
var existingItem = (FullEpisode) existing.Item;
throw new NotImplementedException("Podcasts not implemented");
}
} }
public Task Watch(CancellationToken cancelToken) public Task Watch(CancellationToken cancelToken)
@ -41,7 +102,58 @@ namespace Selector
public CurrentlyPlaying NowPlaying() public CurrentlyPlaying NowPlaying()
{ {
throw new NotImplementedException(); return live;
}
/// <summary>
/// Store currently playing in last plays. Determine whether new list or appending required
/// </summary>
/// <param name="current">New currently playing to store</param>
private void StoreCurrentPlaying(CurrentlyPlaying current)
{
if(lastPlays.Count > 0)
{
bool matchesMostRecent;
try {
var castItem = (FullTrack) current.Item;
var castStoredItem = (FullTrack) lastPlays[0][0].Item;
matchesMostRecent = equalityChecker.Track(castItem, castStoredItem, true);
}
catch(InvalidCastException)
{
var castItem = (FullEpisode) current.Item;
var castStoredItem = (FullEpisode) lastPlays[0][0].Item;
matchesMostRecent = equalityChecker.Episode(castItem, castStoredItem);
}
if (matchesMostRecent)
{
lastPlays[0].Add(current);
}
else
{
StoreNewTrack(current);
}
}
else {
StoreNewTrack(current);
}
}
/// <summary>
/// Store currently playing at front of last plays list. Pushes new list to hold same track
/// </summary>
/// <param name="current">New currently playing to store</param>
private void StoreNewTrack(CurrentlyPlaying current)
{
if (live != null) {
var newPlayingList = new List<CurrentlyPlaying>();
newPlayingList.Add(live);
lastPlays.Insert(0, newPlayingList);
}
} }
protected virtual void OnTrackChange(ListeningChangeEventArgs args) protected virtual void OnTrackChange(ListeningChangeEventArgs args)
@ -65,14 +177,19 @@ namespace Selector
} }
protected virtual void OnVolumeChange(ListeningChangeEventArgs args) // protected virtual void OnVolumeChange(ListeningChangeEventArgs args)
{ // {
ArtistChange?.Invoke(this, args); // ArtistChange?.Invoke(this, args);
} // }
protected virtual void OnDeviceChange(ListeningChangeEventArgs args) // protected virtual void OnDeviceChange(ListeningChangeEventArgs args)
// {
// ContextChange?.Invoke(this, args);
// }
protected virtual void OnPlayingChange(ListeningChangeEventArgs args)
{ {
ContextChange?.Invoke(this, args); PlayingChange?.Invoke(this, args);
} }
} }
} }