mirror of
https://github.com/janishutz/MusicPlayer.git
synced 2026-09-10 14:25:24 +02:00
feat: basic rendering of queue on share screen
This commit is contained in:
@@ -152,9 +152,9 @@ const sendUpdate = ( room: string, kind: 'state' | 'playlist' ) => {
|
|||||||
|
|
||||||
if ( !roomObject ) return false;
|
if ( !roomObject ) return false;
|
||||||
|
|
||||||
roomObject.clients.forEach( client => client.response.write( `data: ${ JSON.stringify( {
|
roomObject.clients.forEach( client => client.response.write( `data: json:${ JSON.stringify( {
|
||||||
'type': kind,
|
'type': kind,
|
||||||
'data': JSON.stringify( roomObject[kind] )
|
'data': roomObject[kind]
|
||||||
} ) }\n\n` ) );
|
} ) }\n\n` ) );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const routes = ( app: express.Application, foss: boolean, config: Config ) => {
|
|||||||
response.send(
|
response.send(
|
||||||
JSON.stringify( {
|
JSON.stringify( {
|
||||||
'state': room?.state,
|
'state': room?.state,
|
||||||
'playlist': lastRequest < room!.playlist!.lastUpdate ? room!.playlist : undefined
|
'playlist': ( lastRequest < room!.playlist!.lastUpdate || isNaN( lastRequest ) ) ? room!.playlist.playlist : undefined
|
||||||
} )
|
} )
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
|
|||||||
@@ -3,17 +3,27 @@
|
|||||||
computed,
|
computed,
|
||||||
ref
|
ref
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import type {
|
import {
|
||||||
Song
|
currentQueue,
|
||||||
} from '@/ts/dtype/playlist';
|
currentQueueIdx
|
||||||
|
} from '@/ts/shared/state';
|
||||||
|
|
||||||
const props = defineProps<{
|
const songs = computed( () => currentQueue.value?.slice( currentQueueIdx.value + 1 ) ?? [] );
|
||||||
'songs': Song[],
|
|
||||||
'idx': number
|
|
||||||
}>();
|
|
||||||
const songs = computed( () => props.songs?.slice( props.idx + 1 ) ?? [] );
|
|
||||||
// TODO: Move this out of this file
|
// TODO: Move this out of this file
|
||||||
const showArtworks = ref( false );
|
const showArtworks = ref( false );
|
||||||
|
const timeToPlay = computed( () => {
|
||||||
|
return ( idx: number ) => {
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
for ( let i = 0; i < idx; i++ ) {
|
||||||
|
total += songs.value[ i ]!.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += currentQueue.value[ currentQueueIdx.value ]?.duration ?? 0;
|
||||||
|
|
||||||
|
return Math.round( total / 60 );
|
||||||
|
};
|
||||||
|
} );
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -34,7 +44,7 @@
|
|||||||
<p>{{ song['additional-info'] }}</p>
|
<p>{{ song['additional-info'] }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="song-actions">
|
<div class="song-actions">
|
||||||
<p>In {{ song.duration }}min</p>
|
<p>In {{ timeToPlay( index ) }}min</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
import {
|
|
||||||
duration,
|
|
||||||
playbackPercentage
|
|
||||||
} from '../player/status-tracking';
|
|
||||||
import {
|
import {
|
||||||
isPlaying,
|
isPlaying,
|
||||||
queue,
|
queue,
|
||||||
@@ -11,6 +7,9 @@ import {
|
|||||||
ref,
|
ref,
|
||||||
watch
|
watch
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
import {
|
||||||
|
playbackPercentage
|
||||||
|
} from '../player/status-tracking';
|
||||||
import request from '../request';
|
import request from '../request';
|
||||||
|
|
||||||
const RETRY_CAP = 10;
|
const RETRY_CAP = 10;
|
||||||
@@ -56,6 +55,8 @@ const connect = (): Promise<boolean> => {
|
|||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
if ( !useAntiTamper.value ) {
|
if ( !useAntiTamper.value ) {
|
||||||
isConnected.value = true;
|
isConnected.value = true;
|
||||||
|
sendPlaylistData();
|
||||||
|
sendStateData();
|
||||||
|
|
||||||
return resolve( true );
|
return resolve( true );
|
||||||
}
|
}
|
||||||
@@ -67,6 +68,8 @@ const connect = (): Promise<boolean> => {
|
|||||||
connection.onopen = () => {
|
connection.onopen = () => {
|
||||||
isConnected.value = true;
|
isConnected.value = true;
|
||||||
console.log( '[SSE] Connection established successfully' );
|
console.log( '[SSE] Connection established successfully' );
|
||||||
|
sendPlaylistData();
|
||||||
|
sendStateData();
|
||||||
resolve( true );
|
resolve( true );
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,25 +94,45 @@ const connect = (): Promise<boolean> => {
|
|||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
const useRoomWatchers = () => {
|
// FIXME: This is not a sensible solution, but easy for now (i.e. solve properly)
|
||||||
watch( queue, () => {
|
let playlistLock = false;
|
||||||
if ( isConnected.value )
|
let stateLock = false;
|
||||||
|
|
||||||
|
const sendPlaylistData = () => {
|
||||||
|
if ( isConnected.value && !playlistLock ) {
|
||||||
|
playlistLock = true;
|
||||||
request.post( `/room/${ room.value }/update/playlist`, JSON.stringify( {
|
request.post( `/room/${ room.value }/update/playlist`, JSON.stringify( {
|
||||||
'playlist': queue.value
|
'playlist': queue.value
|
||||||
} ) );
|
} ) );
|
||||||
} );
|
|
||||||
|
setTimeout( () => {
|
||||||
|
playlistLock = false;
|
||||||
|
}, 500 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendStateData = () => {
|
||||||
|
if ( isConnected.value && !stateLock ) {
|
||||||
|
stateLock = true;
|
||||||
|
request.post( `/room/${ room.value }/update/state`, JSON.stringify( {
|
||||||
|
'playing': isPlaying.value,
|
||||||
|
'index': queueIdx.value,
|
||||||
|
'start': new Date().getTime() - ( playbackPercentage.value * ( queue.value[ queueIdx.value ]?.duration ?? 0 ) ) - 100
|
||||||
|
} ) );
|
||||||
|
|
||||||
|
setTimeout( () => {
|
||||||
|
stateLock = false;
|
||||||
|
}, 500 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useRoomWatchers = () => {
|
||||||
|
watch( queue, sendPlaylistData );
|
||||||
|
|
||||||
watch( [
|
watch( [
|
||||||
isPlaying,
|
isPlaying,
|
||||||
queueIdx
|
queueIdx
|
||||||
], () => {
|
], sendStateData );
|
||||||
if ( isConnected.value )
|
|
||||||
request.post( `/room/${ room.value }/update/state`, JSON.stringify( {
|
|
||||||
'playing': isPlaying.value,
|
|
||||||
'index': queueIdx.value,
|
|
||||||
'start': new Date().getTime() - ( playbackPercentage.value * duration.value ) - 100
|
|
||||||
} ) );
|
|
||||||
} );
|
|
||||||
|
|
||||||
if ( room.value ) connect();
|
if ( room.value ) connect();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
import {
|
||||||
|
type StateUpdate,
|
||||||
|
messageHandler
|
||||||
|
} from './messageHandler';
|
||||||
|
import {
|
||||||
|
currentQueue,
|
||||||
|
currentQueueIdx,
|
||||||
|
isPlaying,
|
||||||
|
startTime
|
||||||
|
} from './state';
|
||||||
|
import type {
|
||||||
|
Song
|
||||||
|
} from '../dtype/playlist';
|
||||||
import request from '../request';
|
import request from '../request';
|
||||||
|
|
||||||
const RETRY_CAP = 10;
|
const RETRY_CAP = 10;
|
||||||
@@ -8,21 +21,38 @@ let hasConnected = false;
|
|||||||
let retries = 0;
|
let retries = 0;
|
||||||
|
|
||||||
// TODO: Persist settings in local storage
|
// TODO: Persist settings in local storage
|
||||||
|
// TODO: Polling instead of sse
|
||||||
|
|
||||||
const connect = (): Promise<void> => {
|
const connect = (): Promise<void> => {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
room = location.pathname.substring( location.pathname.lastIndexOf( '/' ) + 1 );
|
room = location.pathname.substring( location.pathname.lastIndexOf( '/' ) + 1 );
|
||||||
connection = new EventSource( request.backendURL + `/room/${ room }/connect` );
|
connection = new EventSource( request.backendURL + `/room/${ room }/connect` );
|
||||||
|
|
||||||
connection.onopen = () => {
|
connection.onopen = async () => {
|
||||||
hasConnected = true;
|
hasConnected = true;
|
||||||
console.log( '[SSE] Connection established successfully' );
|
console.log( '[SSE] Connection established successfully' );
|
||||||
|
const data = await ( await request.get( `/room/${ room }/poll` ) ).json() as {
|
||||||
|
'playlist': Song[],
|
||||||
|
'state': StateUpdate
|
||||||
|
};
|
||||||
|
|
||||||
|
currentQueue.value = data.playlist;
|
||||||
|
currentQueueIdx.value = data.state.index;
|
||||||
|
isPlaying.value = data.state.playing;
|
||||||
|
startTime.value = data.state.start;
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
connection.onmessage = msg => {
|
connection.onmessage = msg => {
|
||||||
console.log( msg.data );
|
if ( msg.data === 'close' ) {
|
||||||
// TODO: On connect, retrieve data from poll endpoint
|
connection?.close();
|
||||||
|
|
||||||
|
// TODO: Show popup informing user that share was closed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
messageHandler( msg.data );
|
||||||
};
|
};
|
||||||
|
|
||||||
connection.onerror = () => {
|
connection.onerror = () => {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
currentQueue,
|
||||||
|
currentQueueIdx,
|
||||||
|
isPlaying,
|
||||||
|
startTime
|
||||||
|
} from './state';
|
||||||
|
import type {
|
||||||
|
Song
|
||||||
|
} from '../dtype/playlist';
|
||||||
|
|
||||||
|
interface ReceivedJSONMessage {
|
||||||
|
'type': 'state' | 'playlist';
|
||||||
|
'data': unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StateUpdate {
|
||||||
|
'playing': boolean;
|
||||||
|
'index': number;
|
||||||
|
'start': number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const messageHandler = ( msg: string ) => {
|
||||||
|
if ( msg.startsWith( 'json:' ) ) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse( msg.substring( 5 ) ) as ReceivedJSONMessage;
|
||||||
|
|
||||||
|
if ( data.type === 'playlist' ) {
|
||||||
|
currentQueue.value = ( data.data as {
|
||||||
|
'playlist': Song[]
|
||||||
|
} ?? {
|
||||||
|
'playlist': []
|
||||||
|
} ).playlist;
|
||||||
|
} else if ( data.type === 'state' ) {
|
||||||
|
const state = data.data as StateUpdate;
|
||||||
|
|
||||||
|
currentQueueIdx.value = state.index;
|
||||||
|
isPlaying.value = state.playing;
|
||||||
|
startTime.value = state.start;
|
||||||
|
} else {
|
||||||
|
console.log( '[SSE] Received unknown data', data.type );
|
||||||
|
}
|
||||||
|
} catch ( err ) {
|
||||||
|
console.error( 'JSON DECODE failed with error', err );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import {
|
||||||
|
type Ref,
|
||||||
|
ref
|
||||||
|
} from 'vue';
|
||||||
|
import type {
|
||||||
|
Song
|
||||||
|
} from '../dtype/playlist';
|
||||||
|
|
||||||
|
export const currentQueue: Ref<Song[]> = ref( [] );
|
||||||
|
|
||||||
|
export const isPlaying = ref( false );
|
||||||
|
|
||||||
|
export const currentQueueIdx = ref( -1 );
|
||||||
|
|
||||||
|
export const startTime = ref( new Date().getTime() );
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<CurrentSong />
|
<CurrentSong />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<SharedQueue :songs="[]" :idx="-1" />
|
<SharedQueue />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user