mirror of
https://github.com/janishutz/MusicPlayerV2.git
synced 2025-11-25 13:04:23 +00:00
technically working player
This commit is contained in:
@@ -1,13 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>Library</h1>
|
<h1>Library</h1>
|
||||||
<playlistsView :playlists="$props.playlists"></playlistsView>
|
<playlistsView :playlists="$props.playlists" @selected-playlist="( id ) => selectPlaylist( id )"></playlistsView>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import playlistsView from '@/components/playlistsView.vue';
|
import playlistsView from '@/components/playlistsView.vue';
|
||||||
|
|
||||||
|
const emits = defineEmits( [ 'selected-playlist' ] );
|
||||||
|
|
||||||
|
const selectPlaylist = ( id: string ) => {
|
||||||
|
emits( 'selected-playlist', id );
|
||||||
|
}
|
||||||
|
|
||||||
defineProps( {
|
defineProps( {
|
||||||
'playlists': {
|
'playlists': {
|
||||||
'default': [],
|
'default': [],
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
<div>
|
<div>
|
||||||
<div :class="'player' + ( isShowingFullScreenPlayer ? '' : ' player-hidden' )">
|
<div :class="'player' + ( isShowingFullScreenPlayer ? '' : ' player-hidden' )">
|
||||||
<!-- TODO: Make cover art of song or otherwise MusicPlayer Logo -->
|
<!-- TODO: Make cover art of song or otherwise MusicPlayer Logo -->
|
||||||
<img src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png" alt="MusicPlayer Logo" class="logo-player" @click="controlUI( 'show' )">
|
<img src="https://github.com/simplePCBuilding/MusicPlayerV2/raw/master/assets/logo.png" alt="MusicPlayer Logo" class="logo-player" @click="controlUI( 'show' )" v-if="coverArt === ''">
|
||||||
|
<img :src="coverArt" alt="MusicPlayer Logo" class="logo-player" @click="controlUI( 'show' )" v-else>
|
||||||
<p class="song-name" @click="controlUI( 'show' )">{{ currentlyPlayingSongName }}</p>
|
<p class="song-name" @click="controlUI( 'show' )">{{ currentlyPlayingSongName }}</p>
|
||||||
|
<p>{{ nicePlaybackPos }} / {{ niceDuration }}</p>
|
||||||
<div class="controls-wrapper">
|
<div class="controls-wrapper">
|
||||||
<span class="material-symbols-outlined controls next-previous" @click="control( 'previous' )" id="previous">skip_previous</span>
|
<span class="material-symbols-outlined controls next-previous" @click="control( 'previous' )" id="previous">skip_previous</span>
|
||||||
<span class="material-symbols-outlined controls forward-back" @click="control( 'back' )" :style="'rotate: -' + 360 * clickCountBack + 'deg;'">replay_10</span>
|
<span class="material-symbols-outlined controls forward-back" @click="control( 'back' )" :style="'rotate: -' + 360 * clickCountBack + 'deg;'">replay_10</span>
|
||||||
@@ -18,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div :class="'playlist-view' + ( isShowingFullScreenPlayer ? '' : ' hidden' )">
|
<div :class="'playlist-view' + ( isShowingFullScreenPlayer ? '' : ' hidden' )">
|
||||||
<span class="material-symbols-outlined close-fullscreen" @click="controlUI( 'hide' )">close</span>
|
<span class="material-symbols-outlined close-fullscreen" @click="controlUI( 'hide' )">close</span>
|
||||||
<playlistView></playlistView>
|
<playlistView :playlist="playlist" class="pl-wrapper"></playlistView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -27,9 +29,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO: Handle resize, hide all non-essential controls when below 900px width
|
// TODO: Handle resize, hide all non-essential controls when below 900px width
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref, type Ref } from 'vue';
|
||||||
import playlistView from '@/components/playlistView.vue';
|
import playlistView from '@/components/playlistView.vue';
|
||||||
import MusicKitJSWrapper from '@/scripts/music-player';
|
import MusicKitJSWrapper from '@/scripts/music-player';
|
||||||
|
import type { Song } from '@/scripts/song';
|
||||||
|
|
||||||
const isPlaying = ref( false );
|
const isPlaying = ref( false );
|
||||||
const repeatMode = ref( '' );
|
const repeatMode = ref( '' );
|
||||||
@@ -39,6 +42,11 @@
|
|||||||
const clickCountBack = ref( 0 );
|
const clickCountBack = ref( 0 );
|
||||||
const isShowingFullScreenPlayer = ref( false );
|
const isShowingFullScreenPlayer = ref( false );
|
||||||
const player = new MusicKitJSWrapper();
|
const player = new MusicKitJSWrapper();
|
||||||
|
const playlist: Ref<Song[]> = ref( [] );
|
||||||
|
const coverArt = ref( '' );
|
||||||
|
const nicePlaybackPos = ref( '' );
|
||||||
|
const niceDuration = ref( '' );
|
||||||
|
const isShowingRemainingTime = ref( false );
|
||||||
|
|
||||||
const emits = defineEmits( [ 'playerStateChange' ] );
|
const emits = defineEmits( [ 'playerStateChange' ] );
|
||||||
|
|
||||||
@@ -47,8 +55,10 @@
|
|||||||
// TODO: Execute function on player
|
// TODO: Execute function on player
|
||||||
if ( isPlaying.value ) {
|
if ( isPlaying.value ) {
|
||||||
player.control( 'play' );
|
player.control( 'play' );
|
||||||
|
startProgressTracker();
|
||||||
} else {
|
} else {
|
||||||
player.control( 'pause' );
|
player.control( 'pause' );
|
||||||
|
stopProgressTracker();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,21 +66,44 @@
|
|||||||
if ( action === 'repeat' ) {
|
if ( action === 'repeat' ) {
|
||||||
if ( repeatMode.value === '' ) {
|
if ( repeatMode.value === '' ) {
|
||||||
repeatMode.value = '_on';
|
repeatMode.value = '_on';
|
||||||
|
player.setRepeatMode( 'all' );
|
||||||
} else if ( repeatMode.value === '_on' ) {
|
} else if ( repeatMode.value === '_on' ) {
|
||||||
repeatMode.value = '_one_on';
|
repeatMode.value = '_one_on';
|
||||||
|
player.setRepeatMode( 'once' );
|
||||||
} else {
|
} else {
|
||||||
repeatMode.value = '';
|
repeatMode.value = '';
|
||||||
|
player.setRepeatMode( 'off' );
|
||||||
}
|
}
|
||||||
} else if ( action === 'shuffle' ) {
|
} else if ( action === 'shuffle' ) {
|
||||||
if ( shuffleMode.value === '' ) {
|
if ( shuffleMode.value === '' ) {
|
||||||
shuffleMode.value = '_on';
|
shuffleMode.value = '_on';
|
||||||
|
player.setShuffle( true );
|
||||||
} else {
|
} else {
|
||||||
shuffleMode.value = '';
|
shuffleMode.value = '';
|
||||||
|
player.setShuffle( false );
|
||||||
}
|
}
|
||||||
} else if ( action === 'forward' ) {
|
} else if ( action === 'forward' ) {
|
||||||
clickCountForward.value += 1;
|
clickCountForward.value += 1;
|
||||||
|
player.control( 'skip-10' );
|
||||||
} else if ( action === 'back' ) {
|
} else if ( action === 'back' ) {
|
||||||
clickCountBack.value += 1;
|
clickCountBack.value += 1;
|
||||||
|
player.control( 'back-10' );
|
||||||
|
} else if ( action === 'next' ) {
|
||||||
|
stopProgressTracker();
|
||||||
|
player.control( 'next' );
|
||||||
|
currentlyPlayingSongName.value = 'Loading...';
|
||||||
|
setTimeout( () => {
|
||||||
|
getDetails();
|
||||||
|
startProgressTracker();
|
||||||
|
}, 2000 );
|
||||||
|
} else if ( action === 'previous' ) {
|
||||||
|
stopProgressTracker();
|
||||||
|
player.control( 'previous' );
|
||||||
|
currentlyPlayingSongName.value = 'Loading...';
|
||||||
|
setTimeout( () => {
|
||||||
|
getDetails();
|
||||||
|
startProgressTracker();
|
||||||
|
}, 2000 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,12 +134,87 @@
|
|||||||
player.init();
|
player.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectPlaylist = ( id: string ) => {
|
||||||
|
currentlyPlayingSongName.value = 'Loading...';
|
||||||
|
player.setPlaylistByID( id ).then( () => {
|
||||||
|
isPlaying.value = true;
|
||||||
|
setTimeout( () => {
|
||||||
|
startProgressTracker();
|
||||||
|
getDetails();
|
||||||
|
}, 2000 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDetails = () => {
|
||||||
|
const details = player.getPlayingSong();
|
||||||
|
currentlyPlayingSongName.value = details.title;
|
||||||
|
coverArt.value = details.cover;
|
||||||
|
// console.log( player.getQueue() );
|
||||||
|
playlist.value = player.getPlaylist();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let progressTracker = 0;
|
||||||
|
const startProgressTracker = () => {
|
||||||
|
const playingSong = player.getPlayingSong();
|
||||||
|
const minuteCounts = Math.floor( ( playingSong.duration ) / 60 );
|
||||||
|
niceDuration.value = String( minuteCounts ) + ':';
|
||||||
|
if ( ( '' + minuteCounts ).length === 1 ) {
|
||||||
|
niceDuration.value = '0' + minuteCounts + ':';
|
||||||
|
}
|
||||||
|
const secondCounts = Math.floor( ( playingSong.duration ) - minuteCounts * 60 );
|
||||||
|
if ( ( '' + secondCounts ).length === 1 ) {
|
||||||
|
niceDuration.value += '0' + secondCounts;
|
||||||
|
} else {
|
||||||
|
niceDuration.value += secondCounts;
|
||||||
|
}
|
||||||
|
progressTracker = setInterval( () => {
|
||||||
|
const pos = player.getPlaybackPos();
|
||||||
|
if ( pos > playingSong.duration - 1 ) {
|
||||||
|
control( 'next' );
|
||||||
|
}
|
||||||
|
|
||||||
|
const minuteCount = Math.floor( pos / 60 );
|
||||||
|
nicePlaybackPos.value = minuteCount + ':';
|
||||||
|
if ( ( '' + minuteCount ).length === 1 ) {
|
||||||
|
nicePlaybackPos.value = '0' + minuteCount + ':';
|
||||||
|
}
|
||||||
|
const secondCount = Math.floor( pos - minuteCount * 60 );
|
||||||
|
if ( ( '' + secondCount ).length === 1 ) {
|
||||||
|
nicePlaybackPos.value += '0' + secondCount;
|
||||||
|
} else {
|
||||||
|
nicePlaybackPos.value += secondCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isShowingRemainingTime.value ) {
|
||||||
|
const minuteCounts = Math.floor( ( playingSong.duration - pos ) / 60 );
|
||||||
|
niceDuration.value = '-' + String( minuteCounts ) + ':';
|
||||||
|
if ( ( '' + minuteCounts ).length === 1 ) {
|
||||||
|
niceDuration.value = '-0' + minuteCounts + ':';
|
||||||
|
}
|
||||||
|
const secondCounts = Math.floor( ( playingSong.duration - pos ) - minuteCounts * 60 );
|
||||||
|
if ( ( '' + secondCounts ).length === 1 ) {
|
||||||
|
niceDuration.value += '0' + secondCounts;
|
||||||
|
} else {
|
||||||
|
niceDuration.value += secondCounts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 50 );
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopProgressTracker = () => {
|
||||||
|
try {
|
||||||
|
clearInterval( progressTracker );
|
||||||
|
} catch ( _ ) { /* empty */ }
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose( {
|
defineExpose( {
|
||||||
logIntoAppleMusic,
|
logIntoAppleMusic,
|
||||||
getPlaylists,
|
getPlaylists,
|
||||||
controlUI,
|
controlUI,
|
||||||
getAuth,
|
getAuth,
|
||||||
skipLogin,
|
skipLogin,
|
||||||
|
selectPlaylist,
|
||||||
} );
|
} );
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -141,7 +249,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.playlist-view {
|
.playlist-view {
|
||||||
height: 15%;
|
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,4 +321,8 @@
|
|||||||
.hidden .close-fullscreen {
|
.hidden .close-fullscreen {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pl-wrapper {
|
||||||
|
height: 80vh;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,5 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>Playlist</h1>
|
<h1>Playlist</h1>
|
||||||
|
<div class="playlist-box">
|
||||||
|
<div class="song" v-for="song in $props.playlist" v-bind:key="song.id">
|
||||||
|
<img :src="song.cover" alt="Song cover" class="song-cover">
|
||||||
|
<h3 class="song-title">{{ song.title }}</h3>
|
||||||
|
<p class="playing-in">playing in</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Song } from '@/scripts/song';
|
||||||
|
|
||||||
|
defineProps( {
|
||||||
|
'playlist': {
|
||||||
|
default: [],
|
||||||
|
required: true,
|
||||||
|
type: Array<Song>
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.playlist-box {
|
||||||
|
height: 80vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song {
|
||||||
|
border: solid var( --primary-color ) 1px;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px;
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-title {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-cover {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,18 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="playlists">
|
||||||
<h3>Your playlists</h3>
|
<h3 style="width: fit-content;">Your playlists</h3>
|
||||||
<div v-for="pl in $props.playlists" v-bind:key="pl.id">
|
<div v-if="$props.playlists ? $props.playlists.length < 1 : true">
|
||||||
|
loading...
|
||||||
|
<!-- TODO: Make prettier -->
|
||||||
|
</div>
|
||||||
|
<div class="playlist-wrapper">
|
||||||
|
<div v-for="pl in $props.playlists" v-bind:key="pl.id" class="playlist" @click="selectPlaylist( pl.id )">
|
||||||
{{ pl.attributes.name }}
|
{{ pl.attributes.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps( {
|
defineProps( {
|
||||||
'playlists': {
|
'playlists': {
|
||||||
'default': [],
|
'default': [],
|
||||||
'type': Array<any>,
|
'type': Array<any>,
|
||||||
'required': true,
|
'required': true,
|
||||||
}
|
}
|
||||||
} )
|
} );
|
||||||
|
|
||||||
|
const emits = defineEmits( [ 'selected-playlist' ] );
|
||||||
|
|
||||||
|
const selectPlaylist = ( id: string ) => {
|
||||||
|
emits( 'selected-playlist', id );
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.playlists {
|
||||||
|
width: 100%;
|
||||||
|
height: 75vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-wrapper {
|
||||||
|
width: 85%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
border: solid var( --primary-color ) 1px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 1px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,46 +1,4 @@
|
|||||||
type Origin = 'apple-music' | 'disk';
|
import type { Song } from "./song";
|
||||||
|
|
||||||
interface Song {
|
|
||||||
/**
|
|
||||||
* The ID. Either the apple music ID, or if from local disk, an ID starting in local_
|
|
||||||
*/
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Origin of the song
|
|
||||||
*/
|
|
||||||
origin: Origin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cover image as a URL
|
|
||||||
*/
|
|
||||||
cover: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The artist of the song
|
|
||||||
*/
|
|
||||||
artist: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the song
|
|
||||||
*/
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Duration of the song in milliseconds
|
|
||||||
*/
|
|
||||||
duration: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (OPTIONAL) The genres this song belongs to. Can be displayed on the showcase screen, but requires settings there
|
|
||||||
*/
|
|
||||||
genres?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (OPTIONAL) This will be displayed in brackets on the showcase screens
|
|
||||||
*/
|
|
||||||
additionalInfo?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Config {
|
interface Config {
|
||||||
devToken: string;
|
devToken: string;
|
||||||
@@ -61,6 +19,7 @@ class MusicKitJSWrapper {
|
|||||||
repeatMode: RepeatMode;
|
repeatMode: RepeatMode;
|
||||||
isShuffleEnabled: boolean;
|
isShuffleEnabled: boolean;
|
||||||
hasEncounteredAuthError: boolean;
|
hasEncounteredAuthError: boolean;
|
||||||
|
queuePos: number;
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.playingSongID = 0;
|
this.playingSongID = 0;
|
||||||
@@ -75,6 +34,7 @@ class MusicKitJSWrapper {
|
|||||||
this.isPreparedToPlay = false;
|
this.isPreparedToPlay = false;
|
||||||
this.isLoggedIn = false;
|
this.isLoggedIn = false;
|
||||||
this.hasEncounteredAuthError = false;
|
this.hasEncounteredAuthError = false;
|
||||||
|
this.queuePos = 0;
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
@@ -187,6 +147,39 @@ class MusicKitJSWrapper {
|
|||||||
this.setShuffle( this.isShuffleEnabled );
|
this.setShuffle( this.isShuffleEnabled );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPlaylistByID ( id: string ): Promise<void> {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
this.musicKit.setQueue( { playlist: id } ).then( () => {
|
||||||
|
const pl = this.musicKit.queue.items;
|
||||||
|
const songs: Song[] = [];
|
||||||
|
for ( const item in pl ) {
|
||||||
|
let url = pl[ item ].attributes.artwork.url;
|
||||||
|
url = url.replace( '{w}', pl[ item ].attributes.artwork.width );
|
||||||
|
url = url.replace( '{h}', pl[ item ].attributes.artwork.height );
|
||||||
|
const song: Song = {
|
||||||
|
artist: pl[ item ].attributes.artistName,
|
||||||
|
cover: url,
|
||||||
|
duration: pl[ item ].attributes.durationInMillis / 1000,
|
||||||
|
id: pl[ item ].id,
|
||||||
|
origin: 'apple-music',
|
||||||
|
title: pl[ item ].attributes.name,
|
||||||
|
genres: pl[ item ].attributes.genreNames
|
||||||
|
}
|
||||||
|
songs.push( song );
|
||||||
|
}
|
||||||
|
this.playlist = songs;
|
||||||
|
this.setShuffle( this.isShuffleEnabled );
|
||||||
|
this.queuePos = 0;
|
||||||
|
this.playingSongID = this.queue[ 0 ];
|
||||||
|
this.prepare( this.playingSongID );
|
||||||
|
resolve();
|
||||||
|
} ).catch( err => {
|
||||||
|
console.error( err );
|
||||||
|
reject( err );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare a specific song in the queue for playing and start playing
|
* Prepare a specific song in the queue for playing and start playing
|
||||||
* @param {number} playlistID The ID of the song in the playlist to prepare to play
|
* @param {number} playlistID The ID of the song in the playlist to prepare to play
|
||||||
@@ -196,6 +189,17 @@ class MusicKitJSWrapper {
|
|||||||
if ( this.playlist.length > 0 ) {
|
if ( this.playlist.length > 0 ) {
|
||||||
this.playingSongID = playlistID;
|
this.playingSongID = playlistID;
|
||||||
this.isPreparedToPlay = true;
|
this.isPreparedToPlay = true;
|
||||||
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
|
this.musicKit.setQueue( { 'song': this.playlist[ this.playingSongID ].id } ).then( () => {
|
||||||
|
setTimeout( () => {
|
||||||
|
this.control( 'play' );
|
||||||
|
}, 500 );
|
||||||
|
} ).catch( ( err ) => {
|
||||||
|
console.log( err );
|
||||||
|
} );
|
||||||
|
} else {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -205,48 +209,54 @@ class MusicKitJSWrapper {
|
|||||||
/**
|
/**
|
||||||
* Control the player
|
* Control the player
|
||||||
* @param {ControlAction} action Action to take on the player
|
* @param {ControlAction} action Action to take on the player
|
||||||
* @returns {void}
|
* @returns {boolean} returns a boolean indicating if there was a change in song.
|
||||||
*/
|
*/
|
||||||
control ( action: ControlAction ): void {
|
control ( action: ControlAction ): boolean {
|
||||||
switch ( action ) {
|
switch ( action ) {
|
||||||
case "play":
|
case "play":
|
||||||
if ( this.isPreparedToPlay ) {
|
if ( this.isPreparedToPlay ) {
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
this.musicKit.play();
|
this.musicKit.play();
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
return false;
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "pause":
|
case "pause":
|
||||||
if ( this.isPreparedToPlay ) {
|
if ( this.isPreparedToPlay ) {
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
this.musicKit.pause();
|
this.musicKit.pause();
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
return false;
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "back-10":
|
case "back-10":
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime > 10 ? this.musicKit.currentPlaybackTime - 10 : 0 );
|
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime > 10 ? this.musicKit.currentPlaybackTime - 10 : 0 );
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
return false;
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "skip-10":
|
case "skip-10":
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
if ( this.musicKit.currentPlaybackTime < ( this.playlist[ this.playingSongID ].duration - 10 ) ) {
|
if ( this.musicKit.currentPlaybackTime < ( this.playlist[ this.playingSongID ].duration - 10 ) ) {
|
||||||
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime + 10 );
|
this.musicKit.seekToTime( this.musicKit.currentPlaybackTime + 10 );
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if ( this.repeatMode !== 'once' ) {
|
if ( this.repeatMode !== 'once' ) {
|
||||||
this.control( 'next' );
|
this.control( 'next' );
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.musicKit.seekToTime( 0 );
|
this.musicKit.seekToTime( 0 );
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -264,18 +274,47 @@ class MusicKitJSWrapper {
|
|||||||
// this.sendUpdate( 'pos' );
|
// this.sendUpdate( 'pos' );
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "next":
|
case "next":
|
||||||
//
|
if ( this.queuePos < this.queue.length ) {
|
||||||
break;
|
this.queuePos += 1;
|
||||||
|
this.prepare( this.queue[ this.queuePos ] );
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.queuePos = 0;
|
||||||
|
this.control( 'pause' );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case "previous":
|
case "previous":
|
||||||
|
if ( this.queuePos > 0 ) {
|
||||||
|
this.queuePos -= 1;
|
||||||
|
this.prepare( this.queue[ this.queuePos ] );
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.queuePos = this.queue.length - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setShuffle ( enabled: boolean ) {
|
setShuffle ( enabled: boolean ) {
|
||||||
this.isShuffleEnabled = enabled;
|
this.isShuffleEnabled = enabled;
|
||||||
// TODO: Shuffle playlist
|
this.queue = [];
|
||||||
|
if ( enabled ) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
for ( const _ in this.playlist ) {
|
||||||
|
let val = Math.floor( Math.random() * this.playlist.length );
|
||||||
|
while ( this.queue.includes( val ) ) {
|
||||||
|
val = Math.floor( Math.random() * this.playlist.length );
|
||||||
|
}
|
||||||
|
this.queue.push( val );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for ( const song in this.playlist ) {
|
||||||
|
this.queue.push( parseInt( song ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setRepeatMode ( mode: RepeatMode ) {
|
setRepeatMode ( mode: RepeatMode ) {
|
||||||
@@ -353,6 +392,15 @@ class MusicKitJSWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPlaying ( ): boolean {
|
||||||
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
|
return this.musicKit.isPlaying;
|
||||||
|
} else {
|
||||||
|
// TODO: Implement
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// findSongOnAppleMusic ( searchTerm: string ): Song => {
|
// findSongOnAppleMusic ( searchTerm: string ): Song => {
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
// }
|
// }
|
||||||
|
|||||||
43
MusicPlayerV2-GUI/src/scripts/song.d.ts
vendored
Normal file
43
MusicPlayerV2-GUI/src/scripts/song.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export type Origin = 'apple-music' | 'disk';
|
||||||
|
|
||||||
|
export interface Song {
|
||||||
|
/**
|
||||||
|
* The ID. Either the apple music ID, or if from local disk, an ID starting in local_
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Origin of the song
|
||||||
|
*/
|
||||||
|
origin: Origin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cover image as a URL
|
||||||
|
*/
|
||||||
|
cover: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The artist of the song
|
||||||
|
*/
|
||||||
|
artist: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the song
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration of the song in milliseconds
|
||||||
|
*/
|
||||||
|
duration: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (OPTIONAL) The genres this song belongs to. Can be displayed on the showcase screen, but requires settings there
|
||||||
|
*/
|
||||||
|
genres?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (OPTIONAL) This will be displayed in brackets on the showcase screens
|
||||||
|
*/
|
||||||
|
additionalInfo?: string;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-view">
|
<div class="app-view">
|
||||||
<div class="home-view" v-if="isLoggedIntoAppleMusic">
|
<div class="home-view" v-if="isLoggedIntoAppleMusic">
|
||||||
<libraryView class="library-view" :playlists="playlists"></libraryView>
|
<libraryView class="library-view" :playlists="playlists" @selected-playlist="( id ) => { selectPlaylist( id ) }"></libraryView>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="login-view">
|
<div v-else class="login-view">
|
||||||
<img src="@/assets/appleMusicIcon.svg" alt="Apple Music Icon">
|
<img src="@/assets/appleMusicIcon.svg" alt="Apple Music Icon">
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
if ( player.value.getAuth()[ 0 ] ) {
|
if ( player.value.getAuth()[ 0 ] ) {
|
||||||
isLoggedIntoAppleMusic.value = true;
|
isLoggedIntoAppleMusic.value = true;
|
||||||
player.value.getPlaylists( ( data ) => {
|
player.value.getPlaylists( ( data ) => {
|
||||||
console.log( data.data.data );
|
|
||||||
playlists.value = data.data.data;
|
playlists.value = data.data.data;
|
||||||
} );
|
} );
|
||||||
clearInterval( loginChecker );
|
clearInterval( loginChecker );
|
||||||
@@ -53,6 +52,10 @@
|
|||||||
isLoggedIntoAppleMusic.value = true;
|
isLoggedIntoAppleMusic.value = true;
|
||||||
player.value.skipLogin();
|
player.value.skipLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectPlaylist = ( id: string ) => {
|
||||||
|
player.value.selectPlaylist( id );
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user