mirror of
https://github.com/janishutz/MusicPlayer.git
synced 2026-09-10 14:25:24 +02:00
feat: basic player component
This commit is contained in:
@@ -0,0 +1,83 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ProgressBar from './ProgressBar.vue';
|
||||||
|
import {
|
||||||
|
beautifyTime
|
||||||
|
} from '@/ts/util/time';
|
||||||
|
import player from '@/ts/player';
|
||||||
|
|
||||||
|
const playbackPercentage = player.playbackPercentage;
|
||||||
|
const duration = player.duration;
|
||||||
|
const repeatMode = player.repeat;
|
||||||
|
const shuffleMode = player.shuffle;
|
||||||
|
|
||||||
|
const seek = () => {
|
||||||
|
player.seekTo( playbackPercentage.value );
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleRepeatMode = () => {
|
||||||
|
switch ( repeatMode.value ) {
|
||||||
|
case 'one':
|
||||||
|
player.setRepeat( 'all' );
|
||||||
|
break;
|
||||||
|
case 'off':
|
||||||
|
player.setRepeat( 'one' );
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
player.setRepeat( 'off' );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openShareMenu = () => {
|
||||||
|
alert( 'Share menu not yet implemented' );
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Button availability
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mp-player">
|
||||||
|
<div class="controls">
|
||||||
|
<i class="fa-solid fa-backward-step" @click="player.prev"></i>
|
||||||
|
<i class="fa-solid fa-arrow-rotate-left quick-seek" @click="player.back10"></i>
|
||||||
|
<div :class="['play-pause', 'paused']">
|
||||||
|
<i class="fa-solid fa-play" @click="player.play"></i>
|
||||||
|
<i class="fa-solid fa-pause" @click="player.pause"></i>
|
||||||
|
</div>
|
||||||
|
<i class="fa-solid fa-arrow-rotate-right quick-seek" @click="player.skip10"></i>
|
||||||
|
<i class="fa-solid fa-forward-step" @click="player.next"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="time">
|
||||||
|
<!-- TODO: Probably needs to be a computed -->
|
||||||
|
<p class="current">
|
||||||
|
{{ beautifyTime( playbackPercentage * duration ) }}
|
||||||
|
</p>
|
||||||
|
<p class="duration">
|
||||||
|
{{ beautifyTime( duration ) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ProgressBar v-model="playbackPercentage" @move-end="seek" />
|
||||||
|
<div class="bottom-bar">
|
||||||
|
<!-- FA being a POS means need to make own mods to it -->
|
||||||
|
<i
|
||||||
|
:class="[
|
||||||
|
'fa-solid',
|
||||||
|
'fa-repeat',
|
||||||
|
'repeat',
|
||||||
|
repeatMode === 'one' ? 'once' : undefined,
|
||||||
|
repeatMode === 'all' ? 'all' : undefined
|
||||||
|
]"
|
||||||
|
@click="toggleRepeatMode"
|
||||||
|
></i>
|
||||||
|
<i class="fa-solid fa-share-from-square" @click="openShareMenu"></i>
|
||||||
|
<i
|
||||||
|
:class="['fa-solid', 'fa-shuffle', shuffleMode ? 'active' : undefined]"
|
||||||
|
@click="player.setShuffle( !shuffleMode )"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/scss/components/player.scss';
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
useTemplateRef
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
const val = defineModel<number>( {
|
||||||
|
'required': true,
|
||||||
|
'default': 0.5
|
||||||
|
} );
|
||||||
|
const offset = ref( -1 );
|
||||||
|
const bar = useTemplateRef( 'bar' );
|
||||||
|
|
||||||
|
const start = ( ev: MouseEvent ) => {
|
||||||
|
offset.value = ev.x - bar.value!.offsetLeft;
|
||||||
|
val.value = offset.value / bar.value!.clientWidth;
|
||||||
|
emit( 'move-start' );
|
||||||
|
};
|
||||||
|
|
||||||
|
const move = ( ev: MouseEvent ) => {
|
||||||
|
if ( offset.value > -1 ) {
|
||||||
|
val.value = Math.max( 0, Math.min( ev.x / bar.value!.clientWidth, 1 ) );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const end = () => {
|
||||||
|
offset.value = -1;
|
||||||
|
emit( 'move-end' );
|
||||||
|
};
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
( e: 'move-end' ): void;
|
||||||
|
( e: 'move-start' ): void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="progressbar">
|
||||||
|
<div ref="bar" class="back">
|
||||||
|
<div :style="`width: ${ val * 100 }%;`"></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="['click-target', offset >= 0 ? 'active' : undefined]"
|
||||||
|
@mousedown="start"
|
||||||
|
@mousemove="move"
|
||||||
|
@mouseup="end"
|
||||||
|
@mouseleave="end"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/scss/components/progressbar.scss';
|
||||||
|
</style>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import '@fortawesome/fontawesome-free/css/all.css';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import {
|
import {
|
||||||
createApp
|
createApp
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
.mp-player {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.fa-solid {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: font-size ease 0.5s;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
font-size: 2.7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>.controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
>.play-pause {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.paused {
|
||||||
|
>.fa-pause {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
>.fa-play {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>.quick-seek {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "10";
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 40%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 5%;
|
||||||
|
height: 95%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>.time {
|
||||||
|
display: flex;
|
||||||
|
width: 85%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
>.duration {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
>.bottom-bar {
|
||||||
|
>.fa-shuffle {
|
||||||
|
&.active {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--secondary-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>.repeat {
|
||||||
|
position: relative;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
&.once,
|
||||||
|
&.all {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--secondary-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.once::after {
|
||||||
|
position: absolute;
|
||||||
|
content: "1";
|
||||||
|
font-size: 40%;
|
||||||
|
font-family: sans-serif;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 1em;
|
||||||
|
line-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
.progressbar {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
>.back {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--accent-background);
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>.click-target {
|
||||||
|
width: 100%;
|
||||||
|
height: 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
cursor: grabbing;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
transition: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+1
-1
@@ -45,5 +45,5 @@ export interface Song {
|
|||||||
/**
|
/**
|
||||||
* Song duration
|
* Song duration
|
||||||
*/
|
*/
|
||||||
'duration': string;
|
'duration': number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
|
import {
|
||||||
|
addSongList,
|
||||||
|
shuffleList
|
||||||
|
} from './playlists/add';
|
||||||
import {
|
import {
|
||||||
currentSource,
|
currentSource,
|
||||||
|
isPlaying,
|
||||||
|
queue,
|
||||||
queueIdx,
|
queueIdx,
|
||||||
|
rawQueue,
|
||||||
repeat,
|
repeat,
|
||||||
shuffle,
|
shuffle,
|
||||||
sources
|
sources
|
||||||
} from './state';
|
} from './state';
|
||||||
import {
|
import {
|
||||||
|
duration,
|
||||||
|
playbackPercentage,
|
||||||
startTracking,
|
startTracking,
|
||||||
stopTracking
|
stopTracking
|
||||||
} from './status-tracking';
|
} from './status-tracking';
|
||||||
import type {
|
import type {
|
||||||
RepeatMode
|
RepeatMode
|
||||||
} from '../dtype/player';
|
} from '../dtype/player';
|
||||||
import {
|
|
||||||
addSongList
|
|
||||||
} from './playlists/add';
|
|
||||||
import {
|
import {
|
||||||
playIndex
|
playIndex
|
||||||
} from './playlists';
|
} from './playlists';
|
||||||
@@ -31,6 +37,8 @@ const prev = () => {
|
|||||||
const play = () => {
|
const play = () => {
|
||||||
if ( currentSource.value === '' ) return;
|
if ( currentSource.value === '' ) return;
|
||||||
|
|
||||||
|
isPlaying.value = true;
|
||||||
|
|
||||||
sources[currentSource.value]?.play();
|
sources[currentSource.value]?.play();
|
||||||
startTracking();
|
startTracking();
|
||||||
};
|
};
|
||||||
@@ -38,6 +46,8 @@ const play = () => {
|
|||||||
const pause = () => {
|
const pause = () => {
|
||||||
if ( currentSource.value === '' ) return;
|
if ( currentSource.value === '' ) return;
|
||||||
|
|
||||||
|
isPlaying.value = false;
|
||||||
|
|
||||||
sources[currentSource.value]?.pause();
|
sources[currentSource.value]?.pause();
|
||||||
stopTracking();
|
stopTracking();
|
||||||
};
|
};
|
||||||
@@ -70,11 +80,34 @@ const back10 = () => {
|
|||||||
seekTo( ( ( ( source?.getPlaybackPos() ?? 0 ) * duration ) - 10 ) / duration );
|
seekTo( ( ( ( source?.getPlaybackPos() ?? 0 ) * duration ) - 10 ) / duration );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on or off shuffle
|
||||||
|
* @param enabled - Whether to enable or disable shuffle
|
||||||
|
*/
|
||||||
const setShuffle = ( enabled: boolean ) => {
|
const setShuffle = ( enabled: boolean ) => {
|
||||||
// TODO: Shuffle the order
|
|
||||||
shuffle.value = enabled;
|
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 ) => {
|
const setRepeat = ( mode: RepeatMode ) => {
|
||||||
repeat.value = mode;
|
repeat.value = mode;
|
||||||
};
|
};
|
||||||
@@ -99,5 +132,12 @@ export default {
|
|||||||
getSources,
|
getSources,
|
||||||
addSongFromSource,
|
addSongFromSource,
|
||||||
next,
|
next,
|
||||||
prev
|
prev,
|
||||||
|
queue,
|
||||||
|
queueIdx,
|
||||||
|
duration,
|
||||||
|
playbackPercentage,
|
||||||
|
repeat,
|
||||||
|
shuffle,
|
||||||
|
isPlaying
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,107 @@
|
|||||||
|
import {
|
||||||
|
queue,
|
||||||
|
queueIdx,
|
||||||
|
rawQueue,
|
||||||
|
shuffle
|
||||||
|
} from '../state';
|
||||||
import type {
|
import type {
|
||||||
Song
|
Song
|
||||||
} from '@/ts/dtype/playlist';
|
} from '@/ts/dtype/playlist';
|
||||||
import {
|
|
||||||
queue
|
|
||||||
} from '../state';
|
|
||||||
|
|
||||||
export const addSongList = ( songs: Song[] ) => {
|
export const addSongList = ( songs: Song[], first: boolean = false ) => {
|
||||||
for ( const song of songs ) {
|
if ( first ) {
|
||||||
queue.value.push( song );
|
// 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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export const sources: {
|
|||||||
|
|
||||||
export const currentSource = ref( '' );
|
export const currentSource = ref( '' );
|
||||||
|
|
||||||
|
export const rawQueue: Ref<Playlist> = ref( [] );
|
||||||
|
|
||||||
export const queueIdx = ref( 0 );
|
export const queueIdx = ref( 0 );
|
||||||
|
|
||||||
export const queue: Ref<Playlist> = ref( [] );
|
export const queue: Ref<Playlist> = ref( [] );
|
||||||
@@ -27,7 +29,3 @@ export const isPlaying = ref( false );
|
|||||||
export const shuffle = ref( false );
|
export const shuffle = ref( false );
|
||||||
|
|
||||||
export const repeat: Ref<RepeatMode> = ref( 'off' );
|
export const repeat: Ref<RepeatMode> = ref( 'off' );
|
||||||
|
|
||||||
export const playbackPercentage = ref( 0 );
|
|
||||||
|
|
||||||
export const duration = ref( 0 );
|
|
||||||
|
|||||||
@@ -1,4 +1,49 @@
|
|||||||
// Tracking of time and player status
|
import {
|
||||||
export const startTracking = () => {};
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Get a human-readable representation of time
|
||||||
|
* @param time - The time to convert, in seconds
|
||||||
|
* @returns The time, in human-readable format
|
||||||
|
*/
|
||||||
|
export const beautifyTime = ( time: number ): string => {
|
||||||
|
const helper = ( t: number, depth: number ): string => {
|
||||||
|
if ( depth > 2 )
|
||||||
|
return `${ Math.floor( t / 60 ) }:${ t % 60 < 10 ? '0' : '' }${ Math.floor( t % 60 ) }`;
|
||||||
|
else if ( Math.floor( t / 60 ) > 0 )
|
||||||
|
return `${ helper( Math.floor( t / 60 ), depth + 1 ) }:${ t % 60 < 10 ? '0' : '' }${ Math.floor( t % 60 ) }`;
|
||||||
|
else
|
||||||
|
return `${ t % 60 < 10 ? '0' : '' }${ Math.floor( t % 60 ) }`;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( time < 0 ) {
|
||||||
|
return '-:--';
|
||||||
|
} else if ( time == 0 ) {
|
||||||
|
return '0:00';
|
||||||
|
}
|
||||||
|
|
||||||
|
return helper( Math.round( time ), 1 );
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user