import { Injectable } from '@angular/core' ; import { Title } from '@angular/platform-browser' ; import { RxState } from '@rx-angular/state' ; import { fromEvent } from 'rxjs' ; import { map , switchMap , tap , withLatestFrom } from 'rxjs/operators' ; import { Song } from 'src/@types/song' ; export enum PlaybackStates { NONE , LOADING , PLAYING , PAUSED , STOPPED , ENDED , SEEKING , NULL , WAITING , STALLED , COMPLETED } interface IPlayerState { bitrate : number ; playbackState : PlaybackStates ; queue : any [ ] ; queuePosition : number ; upNext : any [ ] ; repeatMode : number ; isShuffling : boolean ; infiniteLoadTimeout : any ; playbackDuration : number ; playbackTime : number ; nowPlayingItem : Partial < Song | any > ; } @ Injectable ( { providedIn : 'root' } ) export class PlayerService extends RxState < IPlayerState > { private mkInstance = ( window as any ) . MusicKit . getInstance ( ) ; private mkEvents = ( window as any ) . MusicKit . Events ; private playbackTimeDidChange$ = fromEvent ( this . mkInstance , this . mkEvents . playbackTimeDidChange ) . pipe ( map ( ( { currentPlaybackTime } ) => ( { playbackTime : currentPlaybackTime } ) ) ) ; private playbackDurationDidChange$ = fromEvent ( this . mkInstance , this . mkEvents . playbackDurationDidChange ) . pipe ( map ( ( event ) => ( { playbackDuration : event . duration } ) ) ) ; private mediaItemsStateDidChange$ = fromEvent ( this . mkInstance , this . mkEvents . mediaItemStateDidChange ) . pipe ( map ( ( event ) => ( { nowPlayingItem : event , playbackTime : 0 } ) ) ) ; private playbackStateDidChange$ = fromEvent ( this . mkInstance , this . mkEvents . playbackStateDidChange ) . pipe ( map ( ( event ) => ( { playbackState : PlaybackStates [ PlaybackStates [ event . state ] ] } ) ) ) ; private queueItemsDidChange$ = fromEvent ( this . mkInstance , this . mkEvents . queueItemsDidChange ) . pipe ( map ( ( ) => ( { queue : this . mkInstance . queue . items , upNext : this . mkInstance . queue . unplayedUserItems . slice ( 1 ) , } ) ) ) ; private queuePositionDidChange$ = fromEvent ( this . mkInstance , this . mkEvents . queuePositionDidChange ) . pipe ( map ( ( e ) => ( { queuePosition : e . position + 1 , upNext : this . mkInstance . queue . unplayedUserItems . slice ( 1 ) , } ) ) ) ; private playbackState$ = this . select ( 'playbackState' ) . pipe ( withLatestFrom ( this . $ ) , tap ( ( [ playbackState , currentState ] ) => { if ( playbackState === PlaybackStates . PAUSED || playbackState === PlaybackStates . STOPPED ) { this . title . setTitle ( 'Star Track' ) ; } else { this . title . setTitle ( `${ currentState . nowPlayingItem . attributes . name } • ${ currentState . nowPlayingItem . attributes . artistName }` ) ; } } ) ) ; private mediaPlaybackError$ = fromEvent ( this . mkInstance , this . mkEvents . mediaPlaybackError ) . pipe ( tap ( ( ) => console . log ( 'mediaPlayBackError' ) ) , map ( ( ) => ( { nowPlaying : null , queue : null } ) ) ) ; constructor ( private title ) { super ( ) ; this . set ( { bitrate : 256 , playbackState : PlaybackStates . NONE , queue : [ ] , upNext : [ ] , queuePosition : 0 , repeatMode : 0 , isShuffling : false , infiniteLoadTimeout : null , playbackDuration : 0 , playbackTime : 0 , nowPlayingItem : { attributes : { artwork : { url : 'assets/imgs/default.svg' } } , } , } ) ; this . connect ( this . mediaPlaybackError$ ) ; this . connect ( this . playbackTimeDidChange$ ) ; this . connect ( this . playbackDurationDidChange$ ) ; this . connect ( this . playbackStateDidChange$ ) ; this . connect ( this . mediaItemsStateDidChange$ ) ; this . connect ( this . queueItemsDidChange$ ) ; this . connect ( this . queuePositionDidChange$ ) ; this . hold ( this . playbackState$ ) ; } async playAlbum ( album , startPosition , shuffle = false ) { await this . mkInstance . setQueue ( { album , startPosition } ) ; this . toggleShuffle ( shuffle ) ; await this . play ( ) ; } async playPlaylist ( playlist , startPosition , shuffle = false ) { await this . mkInstance . setQueue ( { playlist , startPosition } ) ; this . toggleShuffle ( shuffle ) ; await this . play ( ) ; } async playCollection ( opts ) { console . log ( opts ) ; await this . mkInstance . setQueue ( { items : opts . songs . map ( ( item ) => this . createMediaItem ( item ) ) , startPosition : opts . startPosition , } ) ; await this . play ( ) ; } createMediaItem ( song , container = null ) { if ( container ) { const containerName = container . type === 'albums' || container . type === 'library-albums' ? 'albums' : 'playlists' ; return { ... song , container : { id : container . id , type : container . type , name : containerName , } , } ; } return { ... song , container : { id : song . id , } , } ; } async setQueueFromItems ( items , startPosition = 0 , shuffle = false ) { if ( shuffle ) { items = items . sort ( ( ) => 0.5 - Math . random ( ) ) ; } const newItems = items . map ( ( item ) => ( { ... item , container : { id : item . id } , } ) ) ; await this . mkInstance . setQueue ( { items : newItems } ) ; await this . mkInstance . changeToMediaAtIndex ( startPosition ) ; } async play ( ) { await this . mkInstance . play ( ) ; } async pause ( ) { await this . mkInstance . pause ( ) ; } async stop ( ) { await this . mkInstance . stop ( ) ; } toggleRepeat ( ) { const nextRepeatMode = ( this . mkInstance . repeatMode + 1 ) % 3 ; this . mkInstance . repeatMode = nextRepeatMode ; this . set ( ( state ) => ( { ... state , repeatMode : this . mkInstance . repeatMode , } ) ) ; } toggleShuffle ( shouldShuffle ) { this . mkInstance . shuffleMode = ! ! shouldShuffle ; this . set ( ( state ) => ( { ... state , isShuffling : this . mkInstance . shuffleMode , } ) ) ; } async skipToNextItem ( ) { const state = this . get ( ) ; if ( state . repeatMode === 1 ) { return this . seekToTime ( 0 ) ; } await this . mkInstance . skipToNextItem ( ) ; } async skipToPreviousItem ( ) { const state = this . get ( ) ; if ( state . repeatMode === 1 ) { return this . seekToTime ( 0 ) ; } await this . mkInstance . skipToPreviousItem ( ) ; } async seekToTime ( time ) { await this . mkInstance . seekToTime ( time ) ; } async skipTo ( song ) { const index = this . get ( 'queue' ) . indexOf ( song ) ; await this . mkInstance . changeToMediaAtIndex ( index ) ; } }