lots more progress on apple music integration

This commit is contained in:
janis
2023-11-16 14:35:23 +01:00
parent 59b9a39db3
commit 5a133e3f12
10 changed files with 218 additions and 19 deletions

View File

@@ -82,8 +82,8 @@ app.get( '/useAppleMusic', ( req, res ) => {
app.get( '/openSongs', ( req, res ) => {
// res.send( '{ "data": [ "/home/janis/Music/KB2022" ] }' );
res.send( '{ "data": [ "/mnt/storage/SORTED/Music/audio/KB2022" ] }' );
// res.send( { 'data': dialog.showOpenDialogSync( { properties: [ 'openDirectory' ], title: 'Open music library folder' } ) } );
// res.send( '{ "data": [ "/mnt/storage/SORTED/Music/audio/KB2022" ] }' );
res.send( { 'data': dialog.showOpenDialogSync( { properties: [ 'openDirectory' ], title: 'Open music library folder' } ) } );
} );
app.get( '/showcase.js', ( req, res ) => {

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="361px" height="361px" viewBox="0 0 361 361" style="enable-background:new 0 0 361 361;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
</style>
<g id="Layer_5">
</g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="180" y1="358.6047" x2="180" y2="7.7586">
<stop offset="0" style="stop-color:#FA233B"/>
<stop offset="1" style="stop-color:#FB5C74"/>
</linearGradient>
<path class="st0" d="M360,112.61c0-4.3,0-8.6-0.02-12.9c-0.02-3.62-0.06-7.24-0.16-10.86c-0.21-7.89-0.68-15.84-2.08-23.64
c-1.42-7.92-3.75-15.29-7.41-22.49c-3.6-7.07-8.3-13.53-13.91-19.14c-5.61-5.61-12.08-10.31-19.15-13.91
c-7.19-3.66-14.56-5.98-22.47-7.41c-7.8-1.4-15.76-1.87-23.65-2.08c-3.62-0.1-7.24-0.14-10.86-0.16C255.99,0,251.69,0,247.39,0
H112.61c-4.3,0-8.6,0-12.9,0.02c-3.62,0.02-7.24,0.06-10.86,0.16C80.96,0.4,73,0.86,65.2,2.27c-7.92,1.42-15.28,3.75-22.47,7.41
c-7.07,3.6-13.54,8.3-19.15,13.91c-5.61,5.61-10.31,12.07-13.91,19.14c-3.66,7.2-5.99,14.57-7.41,22.49
c-1.4,7.8-1.87,15.76-2.08,23.64c-0.1,3.62-0.14,7.24-0.16,10.86C0,104.01,0,108.31,0,112.61v134.77c0,4.3,0,8.6,0.02,12.9
c0.02,3.62,0.06,7.24,0.16,10.86c0.21,7.89,0.68,15.84,2.08,23.64c1.42,7.92,3.75,15.29,7.41,22.49c3.6,7.07,8.3,13.53,13.91,19.14
c5.61,5.61,12.08,10.31,19.15,13.91c7.19,3.66,14.56,5.98,22.47,7.41c7.8,1.4,15.76,1.87,23.65,2.08c3.62,0.1,7.24,0.14,10.86,0.16
c4.3,0.03,8.6,0.02,12.9,0.02h134.77c4.3,0,8.6,0,12.9-0.02c3.62-0.02,7.24-0.06,10.86-0.16c7.89-0.21,15.85-0.68,23.65-2.08
c7.92-1.42,15.28-3.75,22.47-7.41c7.07-3.6,13.54-8.3,19.15-13.91c5.61-5.61,10.31-12.07,13.91-19.14
c3.66-7.2,5.99-14.57,7.41-22.49c1.4-7.8,1.87-15.76,2.08-23.64c0.1-3.62,0.14-7.24,0.16-10.86c0.03-4.3,0.02-8.6,0.02-12.9V112.61
z"/>
</g>
<g id="Glyph_2_">
<g>
<path class="st1" d="M254.5,55c-0.87,0.08-8.6,1.45-9.53,1.64l-107,21.59l-0.04,0.01c-2.79,0.59-4.98,1.58-6.67,3
c-2.04,1.71-3.17,4.13-3.6,6.95c-0.09,0.6-0.24,1.82-0.24,3.62c0,0,0,109.32,0,133.92c0,3.13-0.25,6.17-2.37,8.76
c-2.12,2.59-4.74,3.37-7.81,3.99c-2.33,0.47-4.66,0.94-6.99,1.41c-8.84,1.78-14.59,2.99-19.8,5.01
c-4.98,1.93-8.71,4.39-11.68,7.51c-5.89,6.17-8.28,14.54-7.46,22.38c0.7,6.69,3.71,13.09,8.88,17.82
c3.49,3.2,7.85,5.63,12.99,6.66c5.33,1.07,11.01,0.7,19.31-0.98c4.42-0.89,8.56-2.28,12.5-4.61c3.9-2.3,7.24-5.37,9.85-9.11
c2.62-3.75,4.31-7.92,5.24-12.35c0.96-4.57,1.19-8.7,1.19-13.26l0-116.15c0-6.22,1.76-7.86,6.78-9.08c0,0,88.94-17.94,93.09-18.75
c5.79-1.11,8.52,0.54,8.52,6.61l0,79.29c0,3.14-0.03,6.32-2.17,8.92c-2.12,2.59-4.74,3.37-7.81,3.99
c-2.33,0.47-4.66,0.94-6.99,1.41c-8.84,1.78-14.59,2.99-19.8,5.01c-4.98,1.93-8.71,4.39-11.68,7.51
c-5.89,6.17-8.49,14.54-7.67,22.38c0.7,6.69,3.92,13.09,9.09,17.82c3.49,3.2,7.85,5.56,12.99,6.6c5.33,1.07,11.01,0.69,19.31-0.98
c4.42-0.89,8.56-2.22,12.5-4.55c3.9-2.3,7.24-5.37,9.85-9.11c2.62-3.75,4.31-7.92,5.24-12.35c0.96-4.57,1-8.7,1-13.26V64.46
C263.54,58.3,260.29,54.5,254.5,55z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -11,12 +11,25 @@
</head>
<body>
<div id="app">
<div v-if="!isLoggedIn">
<h1>Apple Music</h1>
<button @click="logInto()">Log in</button>
<div v-if="isPreparingToPlay" class="preparingToPlay">
<span class="material-symbols-outlined loading-spinner">autorenew</span>
<h1>Loading player...</h1>
</div>
<div v-if="!isLoggedIn" class="start-page">
<div class="image-wrapper">
<img src="/logo.png" alt="Music player icon" id="logo-main">
<img src="/apple-music/helpers/appleMusicIcon.svg" alt="Apple Music Icon" id="apple-music-logo">
</div>
<h1>Apple Music integration</h1>
<button @click="logInto()" class="button">Log in</button>
</div>
<div v-else class="home">
<div v-if="!hasSelectedPlaylist" class="song-list-wrapper">
<h1>Your playlists</h1>
<div v-if="!hasLoadedPlaylists" style="display: flex; justify-content: center; align-items: center; flex-direction: column;">
<span class="material-symbols-outlined loading-spinner">autorenew</span>
<h3>Loading playlists...</h3>
</div>
<div v-for="playlist in playlists" class="song-list" @click="selectPlaylist( playlist.id )">
<h3>{{ playlist.title }}</h3>
</div>
@@ -82,9 +95,9 @@
</div>
<div class="pool-wrapper">
<div style="width: 100%;" class="song-list-wrapper">
<div v-for="song in songQueue" class="song-list" :class="[ isPlaying ? ( currentlyPlaying === song.filename ? 'playing': 'not-playing' ) : 'not-playing', !isPlaying && currentlyPlaying === song.filename ? 'active-song': undefined ]">
<div v-for="song in songQueue" class="song-list" :class="[ isPlaying ? ( playingSong.filename === song.filename ? 'playing': 'not-playing' ) : 'not-playing', !isPlaying && playingSong.filename === song.filename ? 'active-song': undefined ]">
<img :src="song.coverArtURL" class="song-image">
<div v-if="currentlyPlaying === song.filename && isPlaying" class="playing-symbols">
<div v-if="playingSong.filename === song.filename && isPlaying" class="playing-symbols">
<div class="playing-symbols-wrapper">
<div class="playing-bar" id="bar-1"></div>
<div class="playing-bar" id="bar-2"></div>

View File

@@ -19,6 +19,9 @@ const app = Vue.createApp( {
playbackPosBeautified: '',
durationBeautified: '',
isShowingRemainingTime: false,
localIP: '',
hasLoadedPlaylists: false,
isPreparingToPlay: false,
// slider
offset: 0,
@@ -35,10 +38,13 @@ const app = Vue.createApp( {
if ( !this.musicKit.isAuthorized ) {
this.musicKit.authorize().then( () => {
this.isLoggedIn = true;
this.initMusicKit();
} );
} else {
this.musicKit.authorize().then( () => {
this.isLoggedIn = true;
this.musicKit.play();
this.initMusicKit();
} );
}
},
@@ -60,6 +66,23 @@ const app = Vue.createApp( {
this.isLoggedIn = true;
this.config.userToken = this.musicKit.musicUserToken;
}
this.musicKit.addEventListener( 'mediaItemDidChange', ( e ) => {
// Assemble this.playingSong
// 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,
'title': e.item.attributes.name,
'year': e.item.attributes.releaseDate,
// Think about bpm analysis
// 'bpm': metadata[ 'common' ][ 'bpm' ],
'genre': e.item.attributes.genreNames,
'duration': Math.round( e.item.attributes.durationInMillis / 1000 ),
'filename': e.item.id,
'coverArtOrigin': 'api',
'coverArtURL': e.item.attributes.artwork.url,
}
} );
this.apiGetRequest( 'https://api.music.apple.com/v1/me/library/playlists', this.playlistHandler );
} );
}
@@ -76,6 +99,7 @@ const app = Vue.createApp( {
playParams: d[ el ].attributes.playParams,
}
}
this.hasLoadedPlaylists = true;
}
},
apiGetRequest( url, callback ) {
@@ -102,13 +126,16 @@ const app = Vue.createApp( {
} else return false;
},
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();
this.songQueue = this.musicKit.player.queue.items;
this.hasSelectedPlaylist = true;
this.isPreparingToPlay = false;
} catch( err ) {
this.hasSelectedPlaylist = false;
console.error( err );
@@ -223,32 +250,33 @@ const app = Vue.createApp( {
this.sendUpdate( 'isPlaying' );
} else if ( action === 'replay10' ) {
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime > 10 ? musicPlayer.currentPlaybackTime - 10 : 0 );
this.playbackPos = musicPlayer.currentTime;
this.pos = musicPlayer.currentTime;
this.sendUpdate( 'pos' );
} else if ( action === 'forward10' ) {
if ( musicPlayer.currentTime < ( musicPlayer.duration - 10 ) ) {
musicPlayer.currentTime = musicPlayer.currentTime + 10;
this.playbackPos = musicPlayer.currentTime;
if ( this.musicKit.currentPlaybackTime < ( this.playingSong.duration - 10 ) ) {
this.musicKit.seekToTime( this.musicKit.currentTime + 10 );
this.pos = this.musicKit.currentPlaybackTime;
this.sendUpdate( 'pos' );
// Get currently playing song and get duration from there
} else {
if ( this.repeatMode !== 'one' ) {
this.control( 'next' );
} else {
musicPlayer.currentTime = 0;
this.playbackPos = musicPlayer.currentTime;
this.musicKit.seekToTime( 0 );
this.pos = this.musicKit.currentPlaybackTime;
this.sendUpdate( 'pos' );
}
}
} else if ( action === 'reset' ) {
clearInterval( this.progressTracker );
this.playbackPos = 0;
musicPlayer.currentTime = 0;
this.pos = 0;
this.musicKit.seekToTime( 0 );
this.sendUpdate( 'pos' );
} else if ( action === 'next' ) {
this.$emit( 'update', { 'type': 'next' } );
} else if ( action === 'previous' ) {
if ( this.playbackPos > 3 ) {
this.playbackPos = 0;
if ( this.pos > 3 ) {
this.pos = 0;
musicPlayer.currentTime = 0;
this.sendUpdate( 'pos' );
} else {
@@ -275,7 +303,7 @@ const app = Vue.createApp( {
}
},
watch: {
position() {
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 ) );
@@ -290,5 +318,12 @@ const app = Vue.createApp( {
} else {
this.initMusicKit();
}
fetch( '/getLocalIP' ).then( res => {
if ( res.status === 200 ) {
res.text().then( ip => {
this.localIP = ip;
} );
}
} );
},
} ).mount( '#app' );

View File

@@ -105,4 +105,32 @@
border-width: 10px;
border-style: solid;
border-color: transparent transparent rgb(63, 63, 63) transparent;
}
/* Prepare to play */
.preparingToPlay {
background-color: rgba(0, 0, 0, 0.7);
width: 100vw;
height: 100vh;
position: fixed;
align-items: center;
justify-content: center;
display: flex;
flex-direction: column;
top: 0;
left: 0;
}
.loading-spinner {
animation: spin 2s infinite linear;
}
@keyframes spin {
from {
transform: rotate( 0deg );
}
to {
transform: rotate( 720deg );
}
}

View File

@@ -17,6 +17,48 @@ body, html {
color: white;
}
/* Start page style */
.start-page {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.image-wrapper {
height: 50vh;
width: 50vh;
}
#logo-main {
height: 50vh;
}
#apple-music-logo {
height: 10vh;
position: relative;
bottom: 10vh;
left: 41vh;
}
.button {
padding: 20px;
background-color: rgb(1, 1, 88);
color: white;
border: none;
border-radius: 50px;
transition: all 1s;
cursor: pointer;
font-size: 120%;
}
.button:hover {
background-color: rgb(1, 1, 120);
border-radius: 20px;
}
/* Main style */
.home {

View File

@@ -443,6 +443,8 @@ export default {
this.localIP = ip;
} );
}
} ).catch( err => {
this.localIP = 'ERROR fetching';
} );
}
}