mirror of
https://github.com/janishutz/MusicPlayer.git
synced 2026-09-10 14:25:24 +02:00
feat: improve sync
This commit is contained in:
@@ -41,7 +41,8 @@ const create = ( name: string, uid: string ) => {
|
|||||||
'index': 0,
|
'index': 0,
|
||||||
'lastUpdate': new Date().getTime(),
|
'lastUpdate': new Date().getTime(),
|
||||||
'playing': false,
|
'playing': false,
|
||||||
'start': new Date().getTime()
|
'start': new Date().getTime(),
|
||||||
|
'offset': 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,9 +113,10 @@ const close = ( name: string, uid: string ) => {
|
|||||||
* @param playing - Set to true if the player is playing currently
|
* @param playing - Set to true if the player is playing currently
|
||||||
* @param index - The current playback index
|
* @param index - The current playback index
|
||||||
* @param start - The timestamp where playback of the current song started
|
* @param start - The timestamp where playback of the current song started
|
||||||
|
* @param offset - The playback offset from the start in seconds
|
||||||
* @returns true if update succeeded
|
* @returns true if update succeeded
|
||||||
*/
|
*/
|
||||||
const updateState = ( room: string, uid: string, playing: boolean, index: number, start: number ) => {
|
const updateState = ( room: string, uid: string, playing: boolean, index: number, start: number, offset: number ) => {
|
||||||
if ( !rooms[room] || rooms[room].owner !== uid ) {
|
if ( !rooms[room] || rooms[room].owner !== uid ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -123,7 +125,8 @@ const updateState = ( room: string, uid: string, playing: boolean, index: number
|
|||||||
'playing': playing,
|
'playing': playing,
|
||||||
'index': index,
|
'index': index,
|
||||||
'lastUpdate': new Date().getTime(),
|
'lastUpdate': new Date().getTime(),
|
||||||
'start': start
|
'start': start,
|
||||||
|
'offset': offset
|
||||||
};
|
};
|
||||||
|
|
||||||
return sendUpdate( room, 'state' );
|
return sendUpdate( room, 'state' );
|
||||||
|
|||||||
Vendored
+1
@@ -20,6 +20,7 @@ export interface Room {
|
|||||||
'start': number;
|
'start': number;
|
||||||
'playing': boolean;
|
'playing': boolean;
|
||||||
'lastUpdate': number;
|
'lastUpdate': number;
|
||||||
|
'offset': number;
|
||||||
};
|
};
|
||||||
'owner': string;
|
'owner': string;
|
||||||
'clients': Client[];
|
'clients': Client[];
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ const routes = ( app: express.Application, foss: boolean, config: Config ) => {
|
|||||||
sdk.getUID( request )!,
|
sdk.getUID( request )!,
|
||||||
request.body.playing ?? false,
|
request.body.playing ?? false,
|
||||||
request.body.index ?? -1,
|
request.body.index ?? -1,
|
||||||
request.body.start ?? new Date().getTime()
|
request.body.start ?? new Date().getTime(),
|
||||||
|
request.body.offset ?? 0
|
||||||
) )
|
) )
|
||||||
response.sendStatus( 200 );
|
response.sendStatus( 200 );
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import {
|
import {
|
||||||
beautifyTime
|
beautifyTime
|
||||||
} from '@/ts/util/time';
|
} from '@/ts/util/time';
|
||||||
|
import messages from '@/ts/messages';
|
||||||
import player from '@/ts/player';
|
import player from '@/ts/player';
|
||||||
|
|
||||||
const playbackPercentage = player.playbackPercentage;
|
const playbackPercentage = player.playbackPercentage;
|
||||||
@@ -42,6 +43,8 @@
|
|||||||
const duration = computed( () => {
|
const duration = computed( () => {
|
||||||
return beautifyTime( player.duration.value );
|
return beautifyTime( player.duration.value );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
messages.useRoomWatchers();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -12,8 +12,13 @@
|
|||||||
const offset = ref( -1 );
|
const offset = ref( -1 );
|
||||||
const isMoving = ref( false );
|
const isMoving = ref( false );
|
||||||
const bar = useTemplateRef( 'bar' );
|
const bar = useTemplateRef( 'bar' );
|
||||||
|
const props = defineProps<{
|
||||||
|
'disallowMove'?: boolean
|
||||||
|
}>();
|
||||||
|
|
||||||
const start = ( ev: MouseEvent ) => {
|
const start = ( ev: MouseEvent ) => {
|
||||||
|
if ( props.disallowMove ) return;
|
||||||
|
|
||||||
offset.value = bar.value!.getBoundingClientRect().x;
|
offset.value = bar.value!.getBoundingClientRect().x;
|
||||||
isMoving.value = true;
|
isMoving.value = true;
|
||||||
moveVal.value = ( ev.x - offset.value ) / bar.value!.clientWidth;
|
moveVal.value = ( ev.x - offset.value ) / bar.value!.clientWidth;
|
||||||
@@ -47,7 +52,7 @@
|
|||||||
<div :style="`width: ${ isMoving ? moveVal * 100 : val * 100 }%;`"></div>
|
<div :style="`width: ${ isMoving ? moveVal * 100 : val * 100 }%;`"></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:class="['click-target', offset >= 0 ? 'active' : undefined]"
|
:class="['click-target', offset >= 0 ? 'active' : undefined, props.disallowMove ? 'disallowed' : undefined]"
|
||||||
@mousedown="start"
|
@mousedown="start"
|
||||||
@mousemove="move"
|
@mousemove="move"
|
||||||
@mouseup="end"
|
@mouseup="end"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
addPlaylist,
|
addPlaylist,
|
||||||
removePlaylist
|
removePlaylist,
|
||||||
|
selectPlaylist
|
||||||
} from '@/ts/userPlaylists';
|
} from '@/ts/userPlaylists';
|
||||||
import {
|
import {
|
||||||
editingPlaylists,
|
editingPlaylists,
|
||||||
playlistIdx as playlistIdx,
|
|
||||||
playlists
|
playlists
|
||||||
} from '@/ts/userPlaylists/state';
|
} from '@/ts/userPlaylists/state';
|
||||||
import {
|
import {
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
import {
|
import {
|
||||||
UnownedError
|
UnownedError
|
||||||
} from '@/ts/request';
|
} from '@/ts/request';
|
||||||
import player from '@/ts/player';
|
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
|
|
||||||
const checkingStatus = ref( true );
|
const checkingStatus = ref( true );
|
||||||
@@ -65,12 +64,6 @@
|
|||||||
const togglePlaylistEditing = ( idx: number ) => {
|
const togglePlaylistEditing = ( idx: number ) => {
|
||||||
editingPlaylists.value[ idx ] = !editingPlaylists.value[ idx ];
|
editingPlaylists.value[ idx ] = !editingPlaylists.value[ idx ];
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectPlaylist = ( idx: number ) => {
|
|
||||||
playlistIdx.value = idx;
|
|
||||||
player.clearQueue();
|
|
||||||
player.loadPlaylist( playlists.value[ idx ]!.songs );
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
|
||||||
computed,
|
|
||||||
ref
|
|
||||||
} from 'vue';
|
|
||||||
import {
|
import {
|
||||||
currentQueue,
|
currentQueue,
|
||||||
currentQueueIdx
|
currentQueueIdx,
|
||||||
|
playbackTime,
|
||||||
|
showArtworks
|
||||||
} from '@/ts/shared/state';
|
} from '@/ts/shared/state';
|
||||||
|
import {
|
||||||
|
computed
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
const songs = computed( () => currentQueue.value?.slice( currentQueueIdx.value + 1 ) ?? [] );
|
const songs = computed( () => currentQueue.value?.slice( currentQueueIdx.value + 1 ) ?? [] );
|
||||||
// TODO: Move this out of this file
|
|
||||||
const showArtworks = ref( false );
|
|
||||||
const timeToPlay = computed( () => {
|
const timeToPlay = computed( () => {
|
||||||
return ( idx: number ) => {
|
return ( idx: number ) => {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
@@ -20,6 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
total += currentQueue.value[ currentQueueIdx.value ]?.duration ?? 0;
|
total += currentQueue.value[ currentQueueIdx.value ]?.duration ?? 0;
|
||||||
|
total -= playbackTime.value;
|
||||||
|
|
||||||
return Math.round( total / 60 );
|
return Math.round( total / 60 );
|
||||||
};
|
};
|
||||||
@@ -27,24 +27,26 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="queue-container">
|
<div class="queue-viewer">
|
||||||
<div v-for="(song, index) in songs" :key="index" class="song-list-element">
|
<div class="queue-container">
|
||||||
<div class="song-cover-wrapper">
|
<div v-for="(song, index) in songs" :key="index" class="song-list-element">
|
||||||
<img
|
<div class="song-cover-wrapper">
|
||||||
v-if="song.artwork && showArtworks"
|
<img
|
||||||
:src="song.artwork"
|
v-if="song.artwork && showArtworks"
|
||||||
alt="Song cover"
|
:src="song.artwork"
|
||||||
class="song-cover"
|
alt="Song cover"
|
||||||
>
|
class="song-cover"
|
||||||
<i v-else class="fa-solid fa-music song-cover"></i>
|
>
|
||||||
</div>
|
<i v-else class="fa-solid fa-music song-cover"></i>
|
||||||
<div class="song-details">
|
</div>
|
||||||
<h3>{{ song.name }}</h3>
|
<div class="song-details">
|
||||||
<p>{{ song.artist }}</p>
|
<h3>{{ song.name }}</h3>
|
||||||
<p>{{ song['additional-info'] }}</p>
|
<p>{{ song.artist }}</p>
|
||||||
</div>
|
<p>{{ song['additional-info'] }}</p>
|
||||||
<div class="song-actions">
|
</div>
|
||||||
<p>In {{ timeToPlay( index ) }}min</p>
|
<div class="song-actions">
|
||||||
|
<p>In {{ timeToPlay( index ) }}min</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,5 +33,9 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disallowed {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
@@ -4,4 +4,9 @@ import type {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var MusicKit: MusicKitObject;
|
var MusicKit: MusicKitObject;
|
||||||
|
interface GlobalEventHandlersEventMap {
|
||||||
|
'musicplayer:playpause': CustomEvent<void>;
|
||||||
|
'musicplayer:playindex': CustomEvent<void>;
|
||||||
|
'musicplayer:seek': CustomEvent<void>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
queueIdx
|
queueIdx
|
||||||
} from '../player/state';
|
} from '../player/state';
|
||||||
import {
|
import {
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
ref,
|
ref,
|
||||||
watch
|
watch
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
@@ -116,7 +118,8 @@ const sendStateData = () => {
|
|||||||
request.post( `/room/${ room.value }/update/state`, JSON.stringify( {
|
request.post( `/room/${ room.value }/update/state`, JSON.stringify( {
|
||||||
'playing': isPlaying.value,
|
'playing': isPlaying.value,
|
||||||
'index': queueIdx.value,
|
'index': queueIdx.value,
|
||||||
'start': new Date().getTime() - ( playbackPercentage.value * ( queue.value[ queueIdx.value ]?.duration ?? 0 ) ) - 100
|
'start': new Date().getTime() - 100,
|
||||||
|
'offset': playbackPercentage.value * ( queue.value[ queueIdx.value ]?.duration ?? 0 )
|
||||||
} ) );
|
} ) );
|
||||||
|
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
@@ -128,10 +131,25 @@ const sendStateData = () => {
|
|||||||
const useRoomWatchers = () => {
|
const useRoomWatchers = () => {
|
||||||
watch( queue, sendPlaylistData );
|
watch( queue, sendPlaylistData );
|
||||||
|
|
||||||
watch( [
|
onMounted( () => {
|
||||||
isPlaying,
|
document.addEventListener( 'musicplayer:playindex', sendStateData );
|
||||||
queueIdx
|
document.addEventListener( 'musicplayer:seek', sendStateData );
|
||||||
], sendStateData );
|
document.addEventListener( 'musicplayer:playpause', sendStateData );
|
||||||
|
} );
|
||||||
|
|
||||||
|
onUnmounted( () => {
|
||||||
|
try {
|
||||||
|
document.addEventListener( 'musicplayer:play', sendStateData );
|
||||||
|
} catch { /* empty */ }
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.addEventListener( 'musicplayer:seek', sendStateData );
|
||||||
|
} catch { /* empty */ }
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.addEventListener( 'musicplayer:playpause', sendStateData );
|
||||||
|
} catch { /* empty */ }
|
||||||
|
} );
|
||||||
|
|
||||||
if ( room.value ) connect();
|
if ( room.value ) connect();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import type {
|
|||||||
import {
|
import {
|
||||||
load
|
load
|
||||||
} from './playlists/loader';
|
} from './playlists/loader';
|
||||||
import messages from '../messages';
|
|
||||||
import {
|
import {
|
||||||
playIndex
|
playIndex
|
||||||
} from './playlists';
|
} from './playlists';
|
||||||
@@ -52,6 +51,7 @@ const play = () => {
|
|||||||
|
|
||||||
sources[currentSource.value]?.play();
|
sources[currentSource.value]?.play();
|
||||||
startTracking();
|
startTracking();
|
||||||
|
document.dispatchEvent( new CustomEvent( 'musicplayer:playpause' ) );
|
||||||
};
|
};
|
||||||
|
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
@@ -61,6 +61,7 @@ const pause = () => {
|
|||||||
|
|
||||||
sources[currentSource.value]?.pause();
|
sources[currentSource.value]?.pause();
|
||||||
stopTracking();
|
stopTracking();
|
||||||
|
document.dispatchEvent( new CustomEvent( 'musicplayer:playpause' ) );
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,6 +72,7 @@ const seekTo = ( pos: number ) => {
|
|||||||
if ( currentSource.value === '' ) return;
|
if ( currentSource.value === '' ) return;
|
||||||
|
|
||||||
sources[currentSource.value]?.seekTo( pos );
|
sources[currentSource.value]?.seekTo( pos );
|
||||||
|
document.dispatchEvent( new CustomEvent( 'musicplayer:seek' ) );
|
||||||
};
|
};
|
||||||
|
|
||||||
const skip10 = () => {
|
const skip10 = () => {
|
||||||
@@ -153,8 +155,6 @@ const addSongFromSource = async ( source: string, cb?: ( songs: Song[] ) => void
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
messages.useRoomWatchers();
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
play,
|
play,
|
||||||
pause,
|
pause,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import type {
|
|||||||
Song
|
Song
|
||||||
} from '@/ts/dtype/playlist';
|
} from '@/ts/dtype/playlist';
|
||||||
import {
|
import {
|
||||||
playlistIdx
|
setPlaylistIdx
|
||||||
} from '@/ts/userPlaylists/state';
|
} from '@/ts/userPlaylists';
|
||||||
|
|
||||||
export const addSongList = ( songs: Song[] ) => {
|
export const addSongList = ( songs: Song[] ) => {
|
||||||
rawQueue.value = rawQueue.value.concat( songs );
|
rawQueue.value = rawQueue.value.concat( songs );
|
||||||
@@ -25,7 +25,7 @@ export const addSongList = ( songs: Song[] ) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const clearQueue = () => {
|
export const clearQueue = () => {
|
||||||
playlistIdx.value = -1;
|
setPlaylistIdx( 0 );
|
||||||
queue.value = [];
|
queue.value = [];
|
||||||
rawQueue.value = [];
|
rawQueue.value = [];
|
||||||
sources[currentSource.value]?.stop();
|
sources[currentSource.value]?.stop();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
* Play a song at the given index of the queue. Wraps to 0 and end if index below 0 or above end
|
* Play a song at the given index of the queue. Wraps to 0 and end if index below 0 or above end
|
||||||
* @param idx - The index in the queue to play at
|
* @param idx - The index in the queue to play at
|
||||||
*/
|
*/
|
||||||
export const playIndex = async ( idx: number ) => {
|
export const playIndex = ( idx: number ) => {
|
||||||
if ( idx >= queue.value.length ) {
|
if ( idx >= queue.value.length ) {
|
||||||
idx = repeat.value === 'all' ? 0 : -1;
|
idx = repeat.value === 'all' ? 0 : -1;
|
||||||
} else if ( idx < 0 ) {
|
} else if ( idx < 0 ) {
|
||||||
@@ -42,4 +42,7 @@ export const playIndex = async ( idx: number ) => {
|
|||||||
startTracking();
|
startTracking();
|
||||||
|
|
||||||
isPlaying.value = true;
|
isPlaying.value = true;
|
||||||
|
setTimeout( () => {
|
||||||
|
document.dispatchEvent( new CustomEvent( 'musicplayer:playindex', {} ) );
|
||||||
|
}, 500 );
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
currentQueue,
|
currentQueue,
|
||||||
currentQueueIdx,
|
currentQueueIdx,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
|
playbackOffset,
|
||||||
|
playbackTime,
|
||||||
startTime
|
startTime
|
||||||
} from './state';
|
} from './state';
|
||||||
import type {
|
import type {
|
||||||
@@ -40,6 +42,8 @@ const connect = (): Promise<void> => {
|
|||||||
currentQueueIdx.value = data.state.index;
|
currentQueueIdx.value = data.state.index;
|
||||||
isPlaying.value = data.state.playing;
|
isPlaying.value = data.state.playing;
|
||||||
startTime.value = data.state.start;
|
startTime.value = data.state.start;
|
||||||
|
playbackTime.value = data.state.offset;
|
||||||
|
playbackOffset.value = data.state.offset;
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import {
|
|||||||
currentQueue,
|
currentQueue,
|
||||||
currentQueueIdx,
|
currentQueueIdx,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
|
playbackOffset,
|
||||||
|
playbackTime,
|
||||||
startTime
|
startTime
|
||||||
} from './state';
|
} from './state';
|
||||||
import type {
|
import type {
|
||||||
@@ -17,6 +19,7 @@ export interface StateUpdate {
|
|||||||
'playing': boolean;
|
'playing': boolean;
|
||||||
'index': number;
|
'index': number;
|
||||||
'start': number;
|
'start': number;
|
||||||
|
'offset': number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const messageHandler = ( msg: string ) => {
|
export const messageHandler = ( msg: string ) => {
|
||||||
@@ -36,6 +39,8 @@ export const messageHandler = ( msg: string ) => {
|
|||||||
currentQueueIdx.value = state.index;
|
currentQueueIdx.value = state.index;
|
||||||
isPlaying.value = state.playing;
|
isPlaying.value = state.playing;
|
||||||
startTime.value = state.start;
|
startTime.value = state.start;
|
||||||
|
playbackTime.value = state.offset;
|
||||||
|
playbackOffset.value = state.offset;
|
||||||
} else {
|
} else {
|
||||||
console.log( '[SSE] Received unknown data', data.type );
|
console.log( '[SSE] Received unknown data', data.type );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,3 +13,9 @@ export const isPlaying = ref( false );
|
|||||||
export const currentQueueIdx = ref( -1 );
|
export const currentQueueIdx = ref( -1 );
|
||||||
|
|
||||||
export const startTime = ref( new Date().getTime() );
|
export const startTime = ref( new Date().getTime() );
|
||||||
|
|
||||||
|
export const showArtworks = ref( false );
|
||||||
|
|
||||||
|
export const playbackTime = ref( 0 );
|
||||||
|
|
||||||
|
export const playbackOffset = ref( 0 );
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
editingPlaylists,
|
editingPlaylists,
|
||||||
|
playlistIdx,
|
||||||
playlists
|
playlists
|
||||||
} from './state';
|
} from './state';
|
||||||
|
import player from '../player';
|
||||||
|
|
||||||
|
|
||||||
export const addPlaylist = ( name: string ) => {
|
export const addPlaylist = ( name: string ) => {
|
||||||
playlists.value.push( {
|
playlists.value.push( {
|
||||||
@@ -17,3 +20,13 @@ export const removePlaylist = ( idx: number ) => {
|
|||||||
editingPlaylists.value.splice( idx, 1 );
|
editingPlaylists.value.splice( idx, 1 );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const selectPlaylist = ( idx: number ) => {
|
||||||
|
playlistIdx.value = idx;
|
||||||
|
player.clearQueue();
|
||||||
|
player.loadPlaylist( playlists.value[ idx ]!.songs );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setPlaylistIdx = ( idx: number ) => {
|
||||||
|
playlistIdx.value = idx;
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,15 +16,17 @@ import {
|
|||||||
useNotification
|
useNotification
|
||||||
} from '@kyvg/vue3-notification';
|
} from '@kyvg/vue3-notification';
|
||||||
|
|
||||||
export const savePlaylist = async () => {
|
export const savePlaylist = () => {
|
||||||
rawQueue.value = queue.value;
|
rawQueue.value = queue.value;
|
||||||
shuffle.value = false;
|
shuffle.value = false;
|
||||||
|
|
||||||
if ( playlistIdx.value ) {
|
if ( playlistIdx.value < 0 ) {
|
||||||
let name: null | string = '';
|
let name: null | string = '';
|
||||||
|
|
||||||
while ( !name || name.length === 0 ) {
|
while ( !name || name.length === 0 ) {
|
||||||
name = prompt( 'You are trying to save a playlist that has not previously been created. Please enter a name for it' );
|
name = prompt( 'You are trying to save a playlist that has not previously been created. Please enter a name for it' );
|
||||||
|
|
||||||
|
if ( !name ) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPlaylist( name );
|
addPlaylist( name );
|
||||||
|
|||||||
@@ -1,21 +1,49 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
type ComputedRef,
|
type ComputedRef,
|
||||||
computed
|
computed,
|
||||||
|
onMounted,
|
||||||
|
ref
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import {
|
import {
|
||||||
currentQueue,
|
currentQueue,
|
||||||
currentQueueIdx
|
currentQueueIdx,
|
||||||
|
isPlaying,
|
||||||
|
playbackOffset,
|
||||||
|
playbackTime,
|
||||||
|
startTime
|
||||||
} from '@/ts/shared/state';
|
} from '@/ts/shared/state';
|
||||||
import CurrentSong from '@/components/player/CurrentSong.vue';
|
import CurrentSong from '@/components/player/CurrentSong.vue';
|
||||||
|
import ProgressBar from '@/components/player/ProgressBar.vue';
|
||||||
import SharedQueue from '@/components/shared/SharedQueue.vue';
|
import SharedQueue from '@/components/shared/SharedQueue.vue';
|
||||||
import type {
|
import type {
|
||||||
Song
|
Song
|
||||||
} from '@/ts/dtype/playlist';
|
} from '@/ts/dtype/playlist';
|
||||||
|
import {
|
||||||
|
beautifyTime
|
||||||
|
} from '@/ts/util/time';
|
||||||
import shared from '@/ts/shared';
|
import shared from '@/ts/shared';
|
||||||
|
|
||||||
shared.connect();
|
shared.connect();
|
||||||
|
const playbackProgress = ref( 0 );
|
||||||
|
|
||||||
|
onMounted( () => {
|
||||||
|
setInterval( () => {
|
||||||
|
if ( !isPlaying.value ) return;
|
||||||
|
|
||||||
|
playbackTime.value = ( ( new Date().getTime() - startTime.value ) / 1000 ) + playbackOffset.value;
|
||||||
|
playbackProgress.value = playbackTime.value / song.value.duration;
|
||||||
|
|
||||||
|
if ( playbackTime.value > song.value.duration ) {
|
||||||
|
playbackOffset.value -= song.value.duration;
|
||||||
|
|
||||||
|
if ( currentQueueIdx.value < currentQueue.value.length - 1 )
|
||||||
|
currentQueueIdx.value++;
|
||||||
|
else
|
||||||
|
isPlaying.value = false;
|
||||||
|
}
|
||||||
|
}, 250 );
|
||||||
|
} );
|
||||||
const song: ComputedRef<Song> = computed( () => {
|
const song: ComputedRef<Song> = computed( () => {
|
||||||
if ( currentQueueIdx.value >= 0 )
|
if ( currentQueueIdx.value >= 0 )
|
||||||
return currentQueue.value[currentQueueIdx.value]!;
|
return currentQueue.value[currentQueueIdx.value]!;
|
||||||
@@ -37,6 +65,15 @@
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<CurrentSong v-model="song" />
|
<CurrentSong v-model="song" />
|
||||||
|
<div class="time">
|
||||||
|
<p class="current">
|
||||||
|
{{ beautifyTime( playbackTime ) }}
|
||||||
|
</p>
|
||||||
|
<p class="duration">
|
||||||
|
{{ beautifyTime( song.duration ) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ProgressBar v-model="playbackProgress" :disallow-move="true" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<SharedQueue />
|
<SharedQueue />
|
||||||
|
|||||||
Reference in New Issue
Block a user