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();
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
@ -223,5 +248,30 @@ namespace Selector.Tests
var eq = new StringEquality();
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
};
}
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 static IEnumerable<object[]> TrackData =>
//new List<object[]>
//{
// new object[] { new List<CurrentlyPlaying>(){
// new CurrentlyPlaying(){
// Item = new FullTrack() {
// }
// }
// }, 1 }
//};
//[Theory]
//[MemberData(nameof(TrackData))]
//public void Test1(List<CurrentlyPlaying> playing)
//{
// var spotMock = new Mock<IPlayerClient>();
// // spotMock.Setup(spot => spot.GetCurrentlyPlaying(It.IsAny<PlayerCurrentlyPlayingRequest>())).Returns();
// // var watch = new Watcher();
//}
// [Fact]
// public void Test2()
// {
// var artist = new SimpleArtist(){
// Name = "test"
// };
// var track = new FullTrack() {
// Name = "Test",
// Album = new SimpleAlbum() {
// Name = "test",
// Artists = new List<SimpleArtist>(){
// artist
// }
// },
// Artists = new List<SimpleArtist>(){
// artist
// }
// };
// var track2 = new FullTrack() {
// Name = "Test",
// Album = new SimpleAlbum() {
// Name = "test",
// Artists = new List<SimpleArtist>(){
// artist
// }
// },
// Artists = new List<SimpleArtist>(){
// artist
// }
// };
// track.Should().Be(track2);
// }
public static IEnumerable<object[]> NowPlayingData =>
new List<object[]>
{
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1")),
Helper.CurrentlyPlaying(Helper.FullTrack("track2", "album2", "artist2")),
Helper.CurrentlyPlaying(Helper.FullTrack("track3", "album3", "artist3")),
}
}
};
[Theory]
[MemberData(nameof(NowPlayingData))]
public async void NowPlaying(List<CurrentlyPlaying> playing)
{
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);
for(var i = 0; i < playing.Count; i++)
{
await watcher.WatchOne();
var current = watcher.NowPlaying();
current.Should().Be(playing[i]);
}
}
public static IEnumerable<object[]> EventsData =>
new List<object[]>
{
// 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"),
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1"), isPlaying: true, context: "context1"),
},
// to raise
new List<string>(){ },
// to not raise
new List<string>(){ "TrackChange", "AlbumChange", "ArtistChange", "ContextChange", "PlayingChange" }
},
// TRACK CHANGE
new object[] { new List<CurrentlyPlaying>(){
Helper.CurrentlyPlaying(Helper.FullTrack("track1", "album1", "artist1")),
Helper.CurrentlyPlaying(Helper.FullTrack("track2", "album1", "artist1"))
},
// to raise
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 bool Track(FullTrack track1, FullTrack track2, bool includingAlbum);
public bool Episode(FullEpisode ep1, FullEpisode ep2);
public bool Album(FullAlbum album1, FullAlbum album2);
public bool Artist(FullArtist artist1, FullArtist artist2);
public bool Track(SimpleTrack track1, SimpleTrack track2);
public bool Episode(SimpleEpisode ep1, SimpleEpisode ep2);
public bool Album(SimpleAlbum album1, SimpleAlbum album2);
public bool Artist(SimpleArtist artist1, SimpleArtist artist2);

View File

@ -15,11 +15,18 @@ namespace Selector {
&& track1.Name == track2.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)
{
return album1.Name == album1.Name
return album1.Name == album2.Name
&& Enumerable.SequenceEqual(album1.Artists.Select(a => a.Name), album2.Artists.Select(a => a.Name));
}
new public bool Artist(FullArtist artist1, FullArtist artist2)
{
return artist1.Name == artist2.Name;
@ -30,11 +37,18 @@ namespace Selector {
return track1.Name == track2.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)
{
return album1.Name == album1.Name
return album1.Name == album2.Name
&& Enumerable.SequenceEqual(album1.Artists.Select(a => a.Name), album2.Artists.Select(a => a.Name));
}
new public bool Artist(SimpleArtist artist1, SimpleArtist artist2)
{
return artist1.Name == artist2.Name;

View File

@ -13,6 +13,12 @@ namespace Selector {
&& track1.Uri == track2.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)
{
return album1.Uri == album2.Uri
@ -28,6 +34,12 @@ namespace Selector {
return track1.Uri == track2.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)
{
return album1.Uri == album2.Uri

View File

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

View File

@ -17,21 +17,82 @@ namespace Selector
public event EventHandler<ListeningChangeEventArgs> ArtistChange;
public event EventHandler<ListeningChangeEventArgs> ContextChange;
public event EventHandler<ListeningChangeEventArgs> VolumeChange;
public event EventHandler<ListeningChangeEventArgs> DeviceChange;
// public event EventHandler<ListeningChangeEventArgs> VolumeChange;
// public event EventHandler<ListeningChangeEventArgs> DeviceChange;
public event EventHandler<ListeningChangeEventArgs> PlayingChange;
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) {
this.spotifyClient = spotifyClient;
this.sleepScheduler = sleepScheduler;
this.equalityChecker = equalityChecker;
lastPlays = new List<List<CurrentlyPlaying>>();
}
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)
@ -41,7 +102,58 @@ namespace Selector
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)
@ -65,14 +177,19 @@ namespace Selector
}
protected virtual void OnVolumeChange(ListeningChangeEventArgs args)
{
ArtistChange?.Invoke(this, args);
}
// protected virtual void OnVolumeChange(ListeningChangeEventArgs 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);
}
}
}