From aa64d0757011a96e11bd51a88e3259c4812ff1e2 Mon Sep 17 00:00:00 2001 From: Janis Hutz Date: Thu, 16 Nov 2023 20:59:31 +0100 Subject: [PATCH] more progress on apple music integration --- backend/ui/showcase.js | 6 +- frontend/src/client/appleMusic/index.html | 17 +-- frontend/src/client/appleMusic/index.js | 160 +++++++++++++--------- frontend/src/client/showcase.js | 8 +- 4 files changed, 113 insertions(+), 78 deletions(-) diff --git a/backend/ui/showcase.js b/backend/ui/showcase.js index e9d6e83..66e0649 100644 --- a/backend/ui/showcase.js +++ b/backend/ui/showcase.js @@ -18,17 +18,19 @@ createApp( { computed: { songQueue() { let ret = []; + let pos = 0; for ( let song in this.songs ) { - if ( parseInt( song ) >= this.queuePos ) { + if ( pos >= this.queuePos ) { ret.push( this.songs[ song ] ); } + pos += 1; } return ret; }, getTimeUntil() { return ( song ) => { let timeRemaining = 0; - for ( let i = this.queuePos; i < this.songs.length; i++ ) { + for ( let i = this.queuePos; i < Object.keys( this.songs ).length; i++ ) { if ( this.songs[ i ] == song ) { break; } diff --git a/frontend/src/client/appleMusic/index.html b/frontend/src/client/appleMusic/index.html index 2d534c9..a22b534 100644 --- a/frontend/src/client/appleMusic/index.html +++ b/frontend/src/client/appleMusic/index.html @@ -30,7 +30,7 @@ autorenew

Loading playlists...

-
+

{{ playlist.title }}

@@ -62,10 +62,7 @@
-
- music_note - -
+ music_note

{{ playingSong.title ?? 'No song selected' }}

@@ -79,8 +76,8 @@
-
+
@@ -95,9 +92,9 @@
-
+
-
+
@@ -105,7 +102,7 @@
play_arrow - pause + pause

{{ song.title }}

diff --git a/frontend/src/client/appleMusic/index.js b/frontend/src/client/appleMusic/index.js index 2396457..a70a0d1 100644 --- a/frontend/src/client/appleMusic/index.js +++ b/frontend/src/client/appleMusic/index.js @@ -29,7 +29,6 @@ const app = Vue.createApp( { sliderPos: 0, originalPos: 0, sliderProgress: 0, - position: 0, active: false, } }, @@ -43,7 +42,6 @@ const app = Vue.createApp( { } else { this.musicKit.authorize().then( () => { this.isLoggedIn = true; - this.musicKit.play(); this.initMusicKit(); } ); } @@ -58,7 +56,8 @@ const app = Vue.createApp( { app: { name: 'MusicPlayer', build: '2' - } + }, + storefrontId: 'CH', } ); this.config.devToken = token; this.musicKit = MusicKit.getInstance(); @@ -66,9 +65,18 @@ const app = Vue.createApp( { this.isLoggedIn = true; this.config.userToken = this.musicKit.musicUserToken; } + this.musicKit.addEventListener( 'playbackStateDidChange', ( e ) => { + console.log( 'Playback state changed: ', e ); + } ); + this.musicKit.addEventListener( 'queueItemsDidChange', ( event ) => { + console.log( 'Queue items changed:', event ); + } ); + this.musicKit.addEventListener( 'playbackError', ( event ) => { + console.log( 'Playback Error:', event ); + } ); this.musicKit.addEventListener( 'mediaItemDidChange', ( e ) => { // Assemble this.playingSong - // Also add additional items to queue if there are new + // TODO: Also add additional items to queue if there are new // items that weren't previously shown (limitation of MusicKitJS). this.playingSong = { 'artist': e.item.attributes.artistName, @@ -78,13 +86,19 @@ const app = Vue.createApp( { // 'bpm': metadata[ 'common' ][ 'bpm' ], 'genre': e.item.attributes.genreNames, 'duration': Math.round( e.item.attributes.durationInMillis / 1000 ), - 'filename': e.item.id, + 'filename': this.songQueue[ this.musicKit.player.nowPlayingItemIndex ].filename, 'coverArtOrigin': 'api', + 'hasCoverArt': true, + 'queuePos': this.musicKit.player.nowPlayingItemIndex, } let url = e.item.attributes.artwork.url; url = url.replace( '{w}', e.item.attributes.artwork.width ); url = url.replace( '{h}', e.item.attributes.artwork.height ); - this.songQueue[ item ][ 'coverArtURL' ] = url; + this.playingSong[ 'coverArtURL' ] = url; + this.queuePos = this.musicKit.player.nowPlayingItemIndex; + this.sendUpdate( 'playingSong' ); + this.sendUpdate( 'pos' ); + this.sendUpdate( 'queuePos' ); } ); this.apiGetRequest( 'https://api.music.apple.com/v1/me/library/playlists', this.playlistHandler ); } ); @@ -130,46 +144,43 @@ const app = Vue.createApp( { }, selectPlaylist( id ) { this.isPreparingToPlay = true; - this.musicKit.api.library.playlist( id ).then( playlist => { - const tracks = playlist.relationships.tracks.data.map( tracks => tracks.id ); - - this.musicKit.setQueue( { songs: tracks } ).then( () => { - try { - this.musicKit.play(); - const songQueue = this.musicKit.player.queue.items; - for ( let item in songQueue ) { - this.songQueue[ item ] = { - 'artist': songQueue[ item ].attributes.artistName, - 'title': songQueue[ item ].attributes.name, - 'year': songQueue[ item ].attributes.releaseDate, - // Think about bpm analysis - // 'bpm': metadata[ 'common' ][ 'bpm' ], - 'genre': songQueue[ item ].attributes.genreNames, - 'duration': Math.round( songQueue[ item ].attributes.durationInMillis / 1000 ), - 'filename': songQueue[ item ].id, - 'coverArtOrigin': 'api', - } - let url = songQueue[ item ].attributes.artwork.url; - url = url.replace( '{w}', songQueue[ item ].attributes.artwork.width ); - url = url.replace( '{h}', songQueue[ item ].attributes.artwork.height ); - this.songQueue[ item ][ 'coverArtURL' ] = url; + this.musicKit.setQueue( { playlist: id } ).then( () => { + try { + this.control( 'play' ); + const songQueue = this.musicKit.player.queue.items; + for ( let item in songQueue ) { + this.songQueue[ item ] = { + 'artist': songQueue[ item ].attributes.artistName, + 'title': songQueue[ item ].attributes.name, + 'year': songQueue[ item ].attributes.releaseDate, + 'genre': songQueue[ item ].attributes.genreNames, + 'duration': Math.round( songQueue[ item ].attributes.durationInMillis / 1000 ), + 'filename': songQueue[ item ].id, + 'coverArtOrigin': 'api', + 'hasCoverArt': true, + 'queuePos': item, } - // TODO: Load additional data from file - this.hasSelectedPlaylist = true; - this.isPreparingToPlay = false; - } catch( err ) { - this.hasSelectedPlaylist = false; - console.error( err ); - alert( 'We were unable to play. Please ensure that DRM (yeah sorry it is Apple Music, we cannot do anything about that) is enabled and working' ); + let url = songQueue[ item ].attributes.artwork.url; + url = url.replace( '{w}', songQueue[ item ].attributes.artwork.width ); + url = url.replace( '{h}', songQueue[ item ].attributes.artwork.height ); + this.songQueue[ item ][ 'coverArtURL' ] = url; } - } ).catch( err => { - console.error( 'ERROR whilst settings Queue', err ); - } ) + this.sendUpdate( 'songQueue' ); + // TODO: Load additional data from file + this.hasSelectedPlaylist = true; + this.isPreparingToPlay = false; + } catch( err ) { + this.hasSelectedPlaylist = false; + console.error( err ); + alert( 'We were unable to play. Please ensure that DRM (yeah sorry it is Apple Music, we cannot do anything about that) is enabled and working' ); + } + } ).catch( err => { + console.error( 'ERROR whilst settings Queue', err ); } ); }, handleDrag( e ) { if ( this.isDragging ) { - if ( 0 < this.originalPos + e.screenX - this.offset && this.originalPos + e.screenX - this.offset < document.getElementById( 'progress-slider-' + this.name ).clientWidth - 5 ) { + if ( 0 < this.originalPos + e.screenX - this.offset && this.originalPos + e.screenX - this.offset < document.getElementById( 'progress-slider' ).clientWidth - 5 ) { this.sliderPos = e.screenX - this.offset; this.calcProgressPos(); } @@ -189,17 +200,18 @@ const app = Vue.createApp( { this.calcPlaybackPos(); }, setPos ( e ) { - if ( this.active ) { + if ( this.hasSelectedPlaylist ) { this.originalPos = e.offsetX; this.calcProgressPos(); this.calcPlaybackPos(); + this.musicKit.seekToTime( this.pos ); } }, calcProgressPos() { - this.sliderProgress = Math.ceil( ( this.originalPos + parseInt( this.sliderPos ) ) / ( document.getElementById( 'progress-slider-' + this.name ).clientWidth - 5 ) * 1000 ); + this.sliderProgress = Math.ceil( ( this.originalPos + parseInt( this.sliderPos ) ) / ( document.getElementById( 'progress-slider' ).clientWidth - 5 ) * 1000 ); }, calcPlaybackPos() { - this.pos = Math.round( ( this.originalPos + parseInt( this.sliderPos ) ) / ( document.getElementById( 'progress-slider-' + this.name ).clientWidth - 5 ) * this.duration ); + this.pos = Math.round( ( this.originalPos + parseInt( this.sliderPos ) ) / ( document.getElementById( 'progress-slider' ).clientWidth - 5 ) * this.playingSong.duration ); }, sendUpdate( update ) { let data = {}; @@ -212,6 +224,7 @@ const app = Vue.createApp( { } else if ( update === 'songQueue' ) { data = this.songQueue; } else if ( update === 'queuePos' ) { + this.queuePos = this.musicKit.player.nowPlayingItemIndex >= 0 ? this.musicKit.player.nowPlayingItemIndex : 0; data = this.queuePos; } let fetchOptions = { @@ -228,9 +241,16 @@ const app = Vue.createApp( { }, control( action ) { if ( action === 'play' ) { - this.musicKit.play(); + this.isPlaying = true; + this.musicKit.player.play().catch( err => { + console.log( 'player failed to start' ); + console.log( err ); + } ); + try { + clearInterval( this.progressTracker ); + } catch( err ) {}; this.progressTracker = setInterval( () => { - this.pos = this.musicKit.currentPlaybackTime; + this.pos = parseInt( this.musicKit.player.currentPlaybackTime ); const minuteCount = Math.floor( this.pos / 60 ); this.playbackPosBeautified = minuteCount + ':'; @@ -270,13 +290,13 @@ const app = Vue.createApp( { this.isPlaying = false; this.sendUpdate( 'isPlaying' ); } else if ( action === 'replay10' ) { - this.musicKit.seekToTime( this.musicKit.currentPlaybackTime > 10 ? musicPlayer.currentPlaybackTime - 10 : 0 ); - this.pos = musicPlayer.currentTime; + this.musicKit.seekToTime( this.musicKit.player.currentPlaybackTime > 10 ? musicPlayer.player.currentPlaybackTime - 10 : 0 ); + this.pos = this.musicKit.player.currentPlaybackTime; this.sendUpdate( 'pos' ); } else if ( action === 'forward10' ) { - if ( this.musicKit.currentPlaybackTime < ( this.playingSong.duration - 10 ) ) { - this.musicKit.seekToTime( this.musicKit.currentTime + 10 ); - this.pos = this.musicKit.currentPlaybackTime; + if ( this.musicKit.player.currentPlaybackTime < ( this.playingSong.duration - 10 ) ) { + this.musicKit.seekToTime( this.musicKit.player.currentPlaybackTime + 10 ); + this.pos = this.musicKit.player.currentPlaybackTime; this.sendUpdate( 'pos' ); // Get currently playing song and get duration from there } else { @@ -294,40 +314,54 @@ const app = Vue.createApp( { this.musicKit.seekToTime( 0 ); this.sendUpdate( 'pos' ); } else if ( action === 'next' ) { - this.$emit( 'update', { 'type': 'next' } ); + this.sendUpdate( 'queuePos' ); + this.musicKit.skipToNextItem(); + this.control( 'play' ); } else if ( action === 'previous' ) { if ( this.pos > 3 ) { this.pos = 0; - musicPlayer.currentTime = 0; + this.musicKit.seekToTime( 0 ); this.sendUpdate( 'pos' ); + this.sendUpdate( 'queuePos' ); + this.control( 'play' ); } else { - this.$emit( 'update', { 'type': 'previous' } ); + this.musicKit.skipToPreviousItem(); + this.control( 'play' ); } } else if ( action === 'shuffleOff' ) { - this.$emit( 'update', { 'type': 'shuffleOff' } ); this.isShuffleEnabled = false; + this.musicKit.PlayerShuffleMode = 'off'; } else if ( action === 'shuffleOn' ) { - this.$emit( 'update', { 'type': 'shuffle' } ); + this.musicKit.PlayerShuffleMode = 'songs'; this.isShuffleEnabled = true; } else if ( action === 'repeatOne' ) { this.repeatMode = 'one'; + this.musicKit.PlayerRepeatMode = 'one'; } else if ( action === 'repeatAll' ) { - this.$emit( 'update', { 'type': 'repeat' } ); + this.musicKit.PlayerRepeatMode = 'all'; this.repeatMode = 'all'; } else if ( action === 'repeatOff' ) { - this.$emit( 'update', { 'type': 'repeatOff' } ); + this.musicKit.PlayerRepeatMode = 'none'; this.repeatMode = 'off'; - } else if ( action === 'exitFancyView' ) { - this.isShowingFancyView = false; - this.$emit( 'update', { 'type': 'fancyView', 'status': false } ); } - } + }, + play( song ) { + let foundSong = 0; + for ( let s in this.songQueue ) { + if ( this.songQueue[ s ] === song ) { + foundSong = s; + } + } + this.control( 'stop' ); + this.musicKit.player.changeToMediaAtIndex( foundSong ); + this.control( 'play' ); + }, }, watch: { pos() { if ( !this.isDragging ) { - this.sliderProgress = Math.ceil( this.position / this.duration * 1000 + 2 ); - this.originalPos = Math.ceil( this.position / this.duration * ( document.getElementById( 'progress-slider-' + this.name ).scrollWidth - 5 ) ); + this.sliderProgress = Math.ceil( this.pos / this.playingSong.duration * 1000 + 2 ); + this.originalPos = Math.ceil( this.pos / this.playingSong.duration * ( document.getElementById( 'progress-slider' ).scrollWidth - 5 ) ); } } }, diff --git a/frontend/src/client/showcase.js b/frontend/src/client/showcase.js index 21c7055..b2a8cd4 100644 --- a/frontend/src/client/showcase.js +++ b/frontend/src/client/showcase.js @@ -22,17 +22,19 @@ createApp( { computed: { songQueue() { let ret = []; + let pos = 0; for ( let song in this.songs ) { - if ( parseInt( song ) >= this.queuePos ) { + if ( pos >= this.queuePos ) { ret.push( this.songs[ song ] ); } + pos += 1; } return ret; }, getTimeUntil() { return ( song ) => { let timeRemaining = 0; - for ( let i = this.queuePos; i < this.songs.length; i++ ) { + for ( let i = this.queuePos; i < Object.keys( this.songs ).length - 1; i++ ) { if ( this.songs[ i ] == song ) { break; } @@ -267,7 +269,7 @@ createApp( { }, animateBeat () { $( '.beat-manual' ).stop(); - const duration = Math.ceil( 60 / this.playingSong.bpm * 500 ) - 50; + const duration = Math.ceil( 60 / ( this.playingSong.bpm ?? 180 ) * 500 ) - 50; $( '.beat-manual' ).fadeIn( 50 ); setTimeout( () => { $( '.beat-manual' ).fadeOut( duration );