feat: basic player component

This commit is contained in:
2026-08-31 14:28:18 +02:00
parent 5b223875bd
commit 4d9064f5c2
11 changed files with 504 additions and 20 deletions
+45 -5
View File
@@ -1,20 +1,26 @@
import {
addSongList,
shuffleList
} from './playlists/add';
import {
currentSource,
isPlaying,
queue,
queueIdx,
rawQueue,
repeat,
shuffle,
sources
} from './state';
import {
duration,
playbackPercentage,
startTracking,
stopTracking
} from './status-tracking';
import type {
RepeatMode
} from '../dtype/player';
import {
addSongList
} from './playlists/add';
import {
playIndex
} from './playlists';
@@ -31,6 +37,8 @@ const prev = () => {
const play = () => {
if ( currentSource.value === '' ) return;
isPlaying.value = true;
sources[currentSource.value]?.play();
startTracking();
};
@@ -38,6 +46,8 @@ const play = () => {
const pause = () => {
if ( currentSource.value === '' ) return;
isPlaying.value = false;
sources[currentSource.value]?.pause();
stopTracking();
};
@@ -70,11 +80,34 @@ const back10 = () => {
seekTo( ( ( ( source?.getPlaybackPos() ?? 0 ) * duration ) - 10 ) / duration );
};
/**
* Turn on or off shuffle
* @param enabled - Whether to enable or disable shuffle
*/
const setShuffle = ( enabled: boolean ) => {
// TODO: Shuffle the order
shuffle.value = enabled;
if ( enabled ) {
shuffleList();
queueIdx.value = 0;
} else {
const curr = queue.value[queueIdx.value];
queue.value = [];
for ( let i = 0; i < rawQueue.value.length; i++ ) {
if ( rawQueue.value[i] === curr )
queueIdx.value = i;
queue.value.push( rawQueue.value[i]! );
}
}
};
/**
* Change the repeat mode
* @param mode - The repeat mode to switch into
*/
const setRepeat = ( mode: RepeatMode ) => {
repeat.value = mode;
};
@@ -99,5 +132,12 @@ export default {
getSources,
addSongFromSource,
next,
prev
prev,
queue,
queueIdx,
duration,
playbackPercentage,
repeat,
shuffle,
isPlaying
};
+100 -7
View File
@@ -1,14 +1,107 @@
import {
queue,
queueIdx,
rawQueue,
shuffle
} from '../state';
import type {
Song
} from '@/ts/dtype/playlist';
import {
queue
} from '../state';
export const addSongList = ( songs: Song[] ) => {
for ( const song of songs ) {
queue.value.push( song );
export const addSongList = ( songs: Song[], first: boolean = false ) => {
if ( first ) {
// FIXME: Needs to insert at current index in queue, but where for rawQueue?
// Probably at end if shuffled, else insert into queue, then copy
rawQueue.value = songs.concat( rawQueue.value );
queue.value = songs.concat( queue.value );
} else {
rawQueue.value = rawQueue.value.concat( songs );
queue.value = queue.value.concat( songs );
}
};
export const shuffleList = () => {};
export const clearQueue = () => {
queue.value = [];
rawQueue.value = [];
};
/**
* Remove a song from the given queue index
* @param idx - The index to remove at
* @returns True if successful, False if not
*/
export const removeSong = ( idx: number ) => {
if ( idx <= queueIdx.value ) return false;
const removed = queue.value.splice( idx, 1 )[0];
// TODO: Check that this works
for ( let i = 0; i < rawQueue.value.length; i++ ) {
if ( rawQueue.value[ i ] === removed ) {
rawQueue.value.splice( i, 1 );
}
}
return true;
};
/**
* Move a song in the queue
* @param idx - The index to move
* @param newIdx - The index to move it to
* @returns True if successful, false if constraints were violated
*/
export const moveSong = ( idx: number, newIdx: number ) => {
if ( idx === newIdx ) return true;
if ( idx <= queueIdx.value || newIdx <= queueIdx.value || idx > queue.value.length || newIdx > queue.value.length )
return false;
// Affects only the queue if shuffled, else copy the queue into rawQueue
const movedEl = queue.value[ idx ]!;
if ( idx < newIdx )
for ( let i = idx + 1; i < Math.min( queue.value.length, newIdx + 1 ); i++ ) {
queue.value[ i - 1 ] = queue.value[ i ]!;
}
else if ( idx > newIdx )
for ( let i = idx + 1; i > Math.max( 0, newIdx + 1 ); i-- ) {
queue.value[ i ] = queue.value[ i - 1 ]!;
}
queue.value[ newIdx ] = movedEl;
if ( !shuffle.value ) {
// If we don't shuffle, the rawQueue should also be updated
rawQueue.value = [];
for ( const song of queue.value ) {
rawQueue.value.push( song );
}
}
return true;
};
/** Shuffle the song list */
export const shuffleList = () => {
const shuffled = rawQueue.value
.map( ( val, idx ) => {
return {
val,
idx,
'sort': Math.random()
};
} ).sort( ( a, b ) => a.sort - b.sort );
const q: Song[] = [];
// Make current song the first in the list
q.push( rawQueue.value[queueIdx.value]! );
for ( const el of shuffled ) {
if ( el.idx !== queueIdx.value )
q.push( el.val );
}
queue.value = q;
};
+2 -4
View File
@@ -18,6 +18,8 @@ export const sources: {
export const currentSource = ref( '' );
export const rawQueue: Ref<Playlist> = ref( [] );
export const queueIdx = ref( 0 );
export const queue: Ref<Playlist> = ref( [] );
@@ -27,7 +29,3 @@ export const isPlaying = ref( false );
export const shuffle = ref( false );
export const repeat: Ref<RepeatMode> = ref( 'off' );
export const playbackPercentage = ref( 0 );
export const duration = ref( 0 );
+48 -3
View File
@@ -1,4 +1,49 @@
// Tracking of time and player status
export const startTracking = () => {};
import {
currentSource,
queueIdx,
repeat,
sources
} from './state';
import {
playIndex
} from './playlists';
import {
ref
} from 'vue';
export const stopTracking = () => {};
// Tracking of time and player status
let interval = -1;
export const playbackPercentage = ref( 0 );
export const duration = ref( 0 );
export const startTracking = () => {
if ( interval === -1 ) {
interval = setInterval( tracker, 250 );
}
duration.value = sources[currentSource.value]?.getDuration() ?? -1;
};
const tracker = () => {
playbackPercentage.value = sources[currentSource.value]?.getPlaybackPos() ?? -1;
if ( playbackPercentage.value > duration.value ) {
if ( repeat.value === 'one' ) {
sources[currentSource.value]?.seekTo( 0 );
} else {
stopTracking();
playIndex( queueIdx.value + 1 );
}
}
};
export const stopTracking = () => {
try {
clearInterval( interval );
interval = -1;
} catch {
// empty
}
};