1
- using Codehard . DJ . Extensions ;
1
+ using System . Reactive . Linq ;
2
+ using Codehard . DJ . Extensions ;
2
3
using Codehard . DJ . Providers . Models ;
3
4
using Codehard . Functional ;
4
5
using Microsoft . Extensions . Logging ;
@@ -10,13 +11,13 @@ public class SpotifyProvider : IMusicProvider
10
11
{
11
12
private readonly SpotifyClient _client ;
12
13
private readonly ILogger < SpotifyProvider > _logger ;
13
- private readonly Timer _timer ;
14
14
private readonly Queue < Music > _queue ;
15
15
private readonly Stack < Music > _playedStack ;
16
16
17
17
private Music ? _currentMusic ;
18
18
private bool _disposed ;
19
19
private int _volume = - 1 ;
20
+ private readonly Action _disposer ;
20
21
21
22
public SpotifyProvider (
22
23
SpotifyClient client ,
@@ -30,7 +31,10 @@ public SpotifyProvider(
30
31
// so we doing the queue in memory
31
32
this . _queue = new Queue < Music > ( ) ;
32
33
this . _playedStack = new Stack < Music > ( ) ;
33
- this . _timer = new Timer ( GetPlayingTrackInfoAsync , default , TimeSpan . Zero , TimeSpan . FromSeconds ( 3 ) ) ;
34
+
35
+ var disposeSubscriptionAction = GetPlayingTrackInfo ( ) ;
36
+
37
+ this . _disposer = disposeSubscriptionAction ;
34
38
}
35
39
36
40
public event PlayStartEventHandler ? PlayStartEvent ;
@@ -265,90 +269,101 @@ public async ValueTask<bool> SetVolumeAsync(int volume, CancellationToken cancel
265
269
}
266
270
}
267
271
268
- private async void GetPlayingTrackInfoAsync ( object ? state )
272
+ private Action GetPlayingTrackInfo ( )
269
273
{
270
- try
274
+ var playbackStateObserver =
275
+ Observable . Timer ( TimeSpan . Zero , TimeSpan . FromSeconds ( 3 ) )
276
+ . SelectMany ( _ => Observable . FromAsync ( IsCurrentPlaybackEndedAsync ) )
277
+ . DistinctUntilChanged ( ) ;
278
+
279
+ var stateChangedSubscription =
280
+ playbackStateObserver
281
+ . Where ( t => t . State != this . State )
282
+ . Subscribe ( t => this . PlayerStateChangedEvent ? . Invoke ( this , t . State ) ) ;
283
+
284
+ var playingStateSubscription =
285
+ playbackStateObserver
286
+ . Where ( t => t . State == PlaybackState . Playing )
287
+ . Subscribe ( PlayingSubscribeHandler ) ;
288
+
289
+ var playStoppedOrEndedSubscription =
290
+ playbackStateObserver
291
+ . Where ( t => t . State is PlaybackState . Ended or PlaybackState . Stopped )
292
+ . Subscribe ( PlayerStoppedOrEndedSubscribeHandler ) ;
293
+
294
+ return ( ) =>
271
295
{
272
- var ( track , playbackState ) = await IsCurrentPlaybackEndedAsync ( ) ;
296
+ stateChangedSubscription . Dispose ( ) ;
297
+ playingStateSubscription . Dispose ( ) ;
298
+ playStoppedOrEndedSubscription . Dispose ( ) ;
299
+ } ;
273
300
274
- if ( this . State != playbackState )
275
- {
276
- this . PlayerStateChangedEvent ? . Invoke ( this , playbackState ) ;
277
- }
301
+ async void PlayingSubscribeHandler ( ( FullTrack ? TrackAudio , PlaybackState State ) t )
302
+ {
303
+ var track = t . TrackAudio ;
278
304
279
- this . State = playbackState ;
305
+ var isOutOfSync = track ! . Id != this . _currentMusic ? . Id ;
280
306
281
- switch ( playbackState )
307
+ if ( ! isOutOfSync )
282
308
{
283
- case PlaybackState . Playing :
284
- var isOutOfSync = track ! . Id != this . _currentMusic ? . Id ;
285
-
286
- if ( ! isOutOfSync )
287
- {
288
- break ;
289
- }
290
-
291
- TryInvokePlaybackEnded ( ) ;
292
-
293
- this . PlaybackOutOfSyncEvent ? . Invoke ( this , new MusicPlayerEventArgs
294
- {
295
- Music = new Music (
296
- track . Id ,
297
- track . Name ,
298
- track . Artists . Select ( a => new Artist ( a . Id , a . Name , System . Array . Empty < string > ( ) ) ) . ToArray ( ) ,
299
- new Album ( track . Album . Name , track . Album . Images . Select ( i => i . Url ) . ToArray ( ) , string . Empty ) ,
300
- track . DurationMs ,
301
- new Uri ( track . Uri ) ) ,
302
- } ) ;
303
-
304
- if ( this . RemainingInQueue > 0 )
305
- {
306
- await this . StopAsync ( ) ;
307
- }
309
+ return ;
310
+ }
308
311
309
- break ;
310
- case PlaybackState . Stopped :
311
- case PlaybackState . Ended :
312
- TryInvokePlaybackEnded ( ) ;
312
+ TryInvokePlaybackEnded ( ) ;
313
313
314
- if ( this . _queue . Any ( ) )
315
- {
316
- await this . NextAsync ( ) ;
317
- }
314
+ this . PlaybackOutOfSyncEvent ? . Invoke ( this ,
315
+ new MusicPlayerEventArgs
316
+ {
317
+ Music = new Music ( track . Id , track . Name , track . Artists . Select ( a => new Artist ( a . Id , a . Name , System . Array . Empty < string > ( ) ) ) . ToArray ( ) ,
318
+ new Album ( track . Album . Name , track . Album . Images . Select ( i => i . Url ) . ToArray ( ) , string . Empty ) , track . DurationMs , new Uri ( track . Uri ) ) ,
319
+ } ) ;
318
320
319
- break ;
320
- default :
321
- throw new NotSupportedException ( ) ;
321
+ if ( this . RemainingInQueue > 0 )
322
+ {
323
+ await this . StopAsync ( ) ;
322
324
}
323
325
}
324
- catch ( Exception ex )
325
- {
326
- this . _logger . LogError ( ex , "An error occurred during track info gathering" ) ;
327
- }
328
326
329
- async Task < ( FullTrack ? TrackAudio , PlaybackState State ) > IsCurrentPlaybackEndedAsync ( )
327
+ async void PlayerStoppedOrEndedSubscribeHandler ( ( FullTrack ? TrackAudio , PlaybackState State ) t )
330
328
{
331
- var playingContext = await this . _client . Player . GetCurrentPlayback ( ) ;
329
+ TryInvokePlaybackEnded ( ) ;
332
330
333
- if ( playingContext == null ! )
331
+ if ( this . _queue . Any ( ) )
334
332
{
335
- return ( default , PlaybackState . Stopped ) ;
333
+ await this . NextAsync ( ) ;
336
334
}
335
+ }
337
336
338
- if ( playingContext . Item is not FullTrack currentTrack )
337
+ async Task < ( FullTrack ? TrackAudio , PlaybackState State ) > IsCurrentPlaybackEndedAsync ( )
338
+ {
339
+ try
339
340
{
340
- return ( default , PlaybackState . Stopped ) ;
341
- }
341
+ var playingContext = await this . _client . Player . GetCurrentPlayback ( ) ;
342
342
343
- var trackEnded = playingContext . ProgressMs >= currentTrack . DurationMs - 1 ;
344
- var trackStopped = playingContext is { IsPlaying : false , ProgressMs : 0 } ;
343
+ if ( playingContext == null ! )
344
+ {
345
+ return ( default , PlaybackState . Stopped ) ;
346
+ }
347
+
348
+ if ( playingContext . Item is not FullTrack currentTrack )
349
+ {
350
+ return ( default , PlaybackState . Stopped ) ;
351
+ }
352
+
353
+ var trackEnded = playingContext . ProgressMs >= currentTrack . DurationMs - 1 ;
354
+ var trackStopped = playingContext is { IsPlaying : false , ProgressMs : 0 } ;
345
355
346
- var playbackState =
347
- trackEnded ? PlaybackState . Ended :
348
- trackStopped ? PlaybackState . Stopped :
349
- PlaybackState . Playing ;
356
+ var playbackState =
357
+ trackEnded ? PlaybackState . Ended :
358
+ trackStopped ? PlaybackState . Stopped :
359
+ PlaybackState . Playing ;
350
360
351
- return ( currentTrack , playbackState ) ;
361
+ return ( currentTrack , playbackState ) ;
362
+ }
363
+ catch
364
+ {
365
+ return ( null , PlaybackState . Unknown ) ;
366
+ }
352
367
}
353
368
354
369
void TryInvokePlaybackEnded ( )
@@ -374,13 +389,14 @@ protected void Dispose(bool disposing)
374
389
return ;
375
390
}
376
391
377
- this . _timer . Dispose ( ) ;
392
+ this . _disposer ( ) ;
378
393
379
394
this . _disposed = true ;
380
395
}
381
396
382
397
public void Dispose ( )
383
398
{
384
- _timer . Dispose ( ) ;
399
+ this . Dispose ( true ) ;
400
+ GC . SuppressFinalize ( this ) ;
385
401
}
386
402
}
0 commit comments