mirror of
https://github.com/janishutz/MusicPlayerV2.git
synced 2025-11-25 13:04:23 +00:00
almost complete player
This commit is contained in:
@@ -36,6 +36,7 @@
|
|||||||
--footer-background: rgb(233, 233, 233);
|
--footer-background: rgb(233, 233, 233);
|
||||||
--accent-background: rgb(195, 235, 243);
|
--accent-background: rgb(195, 235, 243);
|
||||||
--loading-color: rgb(167, 167, 167);
|
--loading-color: rgb(167, 167, 167);
|
||||||
|
--slider-color: rgb(119, 132, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark {
|
:root.dark {
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
--footer-background: rgb(53, 53, 53);
|
--footer-background: rgb(53, 53, 53);
|
||||||
--accent-background: rgb(24, 12, 58);
|
--accent-background: rgb(24, 12, 58);
|
||||||
--loading-color: rgb(65, 65, 65);
|
--loading-color: rgb(65, 65, 65);
|
||||||
|
--slider-color: rgb(119, 132, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media ( prefers-color-scheme: dark ) {
|
@media ( prefers-color-scheme: dark ) {
|
||||||
@@ -65,6 +67,7 @@
|
|||||||
--footer-background: rgb(53, 53, 53);
|
--footer-background: rgb(53, 53, 53);
|
||||||
--accent-background: rgb(24, 12, 58);
|
--accent-background: rgb(24, 12, 58);
|
||||||
--loading-color: rgb(65, 65, 65);
|
--loading-color: rgb(65, 65, 65);
|
||||||
|
--slider-color: rgb(119, 132, 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div :class="'player' + ( isShowingFullScreenPlayer ? '' : ' player-hidden' )">
|
<div :class="'player' + ( isShowingFullScreenPlayer ? '' : ' player-hidden' )">
|
||||||
|
<div class="main-player">
|
||||||
<!-- 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' )" v-if="coverArt === ''">
|
<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>
|
<img :src="coverArt" alt="MusicPlayer Logo" class="logo-player" @click="controlUI( 'show' )" v-else>
|
||||||
<p class="song-name" @click="controlUI( 'show' )">{{ currentlyPlayingSongName }}</p>
|
<div class="song-name-wrapper">
|
||||||
<p>{{ nicePlaybackPos }} / {{ niceDuration }}</p>
|
<p class="song-name" @click="controlUI( 'show' )">{{ currentlyPlayingSongName }} <i v-if="currentlyPlayingSongArtist">by {{ currentlyPlayingSongArtist }}</i></p>
|
||||||
|
<div :class="'playback' + ( isShowingFullScreenPlayer ? ' full-screen' : '' )">
|
||||||
|
<sliderView :position="pos" :active="true" :duration="duration" name="main" @pos="( pos ) => player.goToPos( pos )"
|
||||||
|
v-if="isShowingFullScreenPlayer"></sliderView>
|
||||||
|
<div :class="'playback-pos-wrapper' + ( isShowingFullScreenPlayer ? ' full-screen' : '' )">
|
||||||
|
<p class="playback-pos">{{ nicePlaybackPos }}</p>
|
||||||
|
<p v-if="!isShowingFullScreenPlayer"> / </p>
|
||||||
|
<p class="playback-duration">{{ niceDuration }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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,9 +29,11 @@
|
|||||||
<span class="material-symbols-outlined controls" @click="control( 'shuffle' )">shuffle{{ shuffleMode }}</span>
|
<span class="material-symbols-outlined controls" @click="control( 'shuffle' )">shuffle{{ shuffleMode }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 :playlist="playlist" class="pl-wrapper"></playlistView>
|
<playlistView :playlist="playlist" class="pl-wrapper" :currently-playing="currentlyPlayingSongIndex" :is-playing="isPlaying"
|
||||||
|
@control="( action ) => { control( action ) }" @play-song="( song ) => { playSong( song ) }"></playlistView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -32,27 +45,31 @@
|
|||||||
import { ref, type 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 sliderView from './sliderView.vue';
|
||||||
import type { Song } from '@/scripts/song';
|
import type { Song } from '@/scripts/song';
|
||||||
|
|
||||||
const isPlaying = ref( false );
|
const isPlaying = ref( false );
|
||||||
const repeatMode = ref( '' );
|
const repeatMode = ref( '' );
|
||||||
const shuffleMode = ref( '' );
|
const shuffleMode = ref( '' );
|
||||||
const currentlyPlayingSongName = ref( 'Not playing' );
|
const currentlyPlayingSongName = ref( 'Not playing' );
|
||||||
|
const currentlyPlayingSongIndex = ref( 0 );
|
||||||
const clickCountForward = ref( 0 );
|
const clickCountForward = ref( 0 );
|
||||||
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 playlist: Ref<Song[]> = ref( [] );
|
||||||
const coverArt = ref( '' );
|
const coverArt = ref( '' );
|
||||||
const nicePlaybackPos = ref( '' );
|
const nicePlaybackPos = ref( '00:00' );
|
||||||
const niceDuration = ref( '' );
|
const niceDuration = ref( '00:00' );
|
||||||
const isShowingRemainingTime = ref( false );
|
const isShowingRemainingTime = ref( false );
|
||||||
|
const currentlyPlayingSongArtist = ref( '' );
|
||||||
|
const pos = ref( 0 );
|
||||||
|
const duration = ref( 0 );
|
||||||
|
|
||||||
const emits = defineEmits( [ 'playerStateChange' ] );
|
const emits = defineEmits( [ 'playerStateChange' ] );
|
||||||
|
|
||||||
const playPause = () => {
|
const playPause = () => {
|
||||||
isPlaying.value = !isPlaying.value;
|
isPlaying.value = !isPlaying.value;
|
||||||
// TODO: Execute function on player
|
|
||||||
if ( isPlaying.value ) {
|
if ( isPlaying.value ) {
|
||||||
player.control( 'play' );
|
player.control( 'play' );
|
||||||
startProgressTracker();
|
startProgressTracker();
|
||||||
@@ -63,7 +80,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const control = ( action: string ) => {
|
const control = ( action: string ) => {
|
||||||
if ( action === 'repeat' ) {
|
if ( action === 'pause' ) {
|
||||||
|
isPlaying.value = false;
|
||||||
|
player.control( 'pause' );
|
||||||
|
stopProgressTracker();
|
||||||
|
} else if ( action === 'play' ) {
|
||||||
|
isPlaying.value = true;
|
||||||
|
player.control( 'play' );
|
||||||
|
startProgressTracker();
|
||||||
|
} else if ( action === 'repeat' ) {
|
||||||
if ( repeatMode.value === '' ) {
|
if ( repeatMode.value === '' ) {
|
||||||
repeatMode.value = '_on';
|
repeatMode.value = '_on';
|
||||||
player.setRepeatMode( 'all' );
|
player.setRepeatMode( 'all' );
|
||||||
@@ -82,6 +107,7 @@
|
|||||||
shuffleMode.value = '';
|
shuffleMode.value = '';
|
||||||
player.setShuffle( false );
|
player.setShuffle( false );
|
||||||
}
|
}
|
||||||
|
getDetails();
|
||||||
} else if ( action === 'forward' ) {
|
} else if ( action === 'forward' ) {
|
||||||
clickCountForward.value += 1;
|
clickCountForward.value += 1;
|
||||||
player.control( 'skip-10' );
|
player.control( 'skip-10' );
|
||||||
@@ -91,6 +117,8 @@
|
|||||||
} else if ( action === 'next' ) {
|
} else if ( action === 'next' ) {
|
||||||
stopProgressTracker();
|
stopProgressTracker();
|
||||||
player.control( 'next' );
|
player.control( 'next' );
|
||||||
|
coverArt.value = '';
|
||||||
|
currentlyPlayingSongArtist.value = '';
|
||||||
currentlyPlayingSongName.value = 'Loading...';
|
currentlyPlayingSongName.value = 'Loading...';
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
getDetails();
|
getDetails();
|
||||||
@@ -99,6 +127,8 @@
|
|||||||
} else if ( action === 'previous' ) {
|
} else if ( action === 'previous' ) {
|
||||||
stopProgressTracker();
|
stopProgressTracker();
|
||||||
player.control( 'previous' );
|
player.control( 'previous' );
|
||||||
|
coverArt.value = '';
|
||||||
|
currentlyPlayingSongArtist.value = '';
|
||||||
currentlyPlayingSongName.value = 'Loading...';
|
currentlyPlayingSongName.value = 'Loading...';
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
getDetails();
|
getDetails();
|
||||||
@@ -135,6 +165,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectPlaylist = ( id: string ) => {
|
const selectPlaylist = ( id: string ) => {
|
||||||
|
currentlyPlayingSongArtist.value = '';
|
||||||
|
coverArt.value = '';
|
||||||
currentlyPlayingSongName.value = 'Loading...';
|
currentlyPlayingSongName.value = 'Loading...';
|
||||||
player.setPlaylistByID( id ).then( () => {
|
player.setPlaylistByID( id ).then( () => {
|
||||||
isPlaying.value = true;
|
isPlaying.value = true;
|
||||||
@@ -149,14 +181,36 @@
|
|||||||
const details = player.getPlayingSong();
|
const details = player.getPlayingSong();
|
||||||
currentlyPlayingSongName.value = details.title;
|
currentlyPlayingSongName.value = details.title;
|
||||||
coverArt.value = details.cover;
|
coverArt.value = details.cover;
|
||||||
// console.log( player.getQueue() );
|
currentlyPlayingSongIndex.value = player.getPlayingSongID();
|
||||||
playlist.value = player.getPlaylist();
|
playlist.value = player.getQueue();
|
||||||
|
console.log( playlist.value );
|
||||||
|
currentlyPlayingSongArtist.value = details.artist;
|
||||||
|
}
|
||||||
|
|
||||||
|
const playSong = ( id: string ) => {
|
||||||
|
const p = player.getPlaylist();
|
||||||
|
currentlyPlayingSongArtist.value = '';
|
||||||
|
coverArt.value = '';
|
||||||
|
currentlyPlayingSongName.value = 'Loading...';
|
||||||
|
stopProgressTracker();
|
||||||
|
for ( const s in p ) {
|
||||||
|
if ( p[ s ].id === id ) {
|
||||||
|
player.prepare( parseInt( s ) );
|
||||||
|
setTimeout( () => {
|
||||||
|
getDetails();
|
||||||
|
startProgressTracker();
|
||||||
|
}, 2000 );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let progressTracker = 0;
|
let progressTracker = 0;
|
||||||
const startProgressTracker = () => {
|
const startProgressTracker = () => {
|
||||||
|
isPlaying.value = true;
|
||||||
const playingSong = player.getPlayingSong();
|
const playingSong = player.getPlayingSong();
|
||||||
|
duration.value = playingSong.duration;
|
||||||
const minuteCounts = Math.floor( ( playingSong.duration ) / 60 );
|
const minuteCounts = Math.floor( ( playingSong.duration ) / 60 );
|
||||||
niceDuration.value = String( minuteCounts ) + ':';
|
niceDuration.value = String( minuteCounts ) + ':';
|
||||||
if ( ( '' + minuteCounts ).length === 1 ) {
|
if ( ( '' + minuteCounts ).length === 1 ) {
|
||||||
@@ -169,17 +223,18 @@
|
|||||||
niceDuration.value += secondCounts;
|
niceDuration.value += secondCounts;
|
||||||
}
|
}
|
||||||
progressTracker = setInterval( () => {
|
progressTracker = setInterval( () => {
|
||||||
const pos = player.getPlaybackPos();
|
pos.value = player.getPlaybackPos();
|
||||||
if ( pos > playingSong.duration - 1 ) {
|
if ( pos.value > playingSong.duration - 1 ) {
|
||||||
|
// TODO: repeat
|
||||||
control( 'next' );
|
control( 'next' );
|
||||||
}
|
}
|
||||||
|
|
||||||
const minuteCount = Math.floor( pos / 60 );
|
const minuteCount = Math.floor( pos.value / 60 );
|
||||||
nicePlaybackPos.value = minuteCount + ':';
|
nicePlaybackPos.value = minuteCount + ':';
|
||||||
if ( ( '' + minuteCount ).length === 1 ) {
|
if ( ( '' + minuteCount ).length === 1 ) {
|
||||||
nicePlaybackPos.value = '0' + minuteCount + ':';
|
nicePlaybackPos.value = '0' + minuteCount + ':';
|
||||||
}
|
}
|
||||||
const secondCount = Math.floor( pos - minuteCount * 60 );
|
const secondCount = Math.floor( pos.value - minuteCount * 60 );
|
||||||
if ( ( '' + secondCount ).length === 1 ) {
|
if ( ( '' + secondCount ).length === 1 ) {
|
||||||
nicePlaybackPos.value += '0' + secondCount;
|
nicePlaybackPos.value += '0' + secondCount;
|
||||||
} else {
|
} else {
|
||||||
@@ -187,12 +242,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( isShowingRemainingTime.value ) {
|
if ( isShowingRemainingTime.value ) {
|
||||||
const minuteCounts = Math.floor( ( playingSong.duration - pos ) / 60 );
|
const minuteCounts = Math.floor( ( playingSong.duration - pos.value ) / 60 );
|
||||||
niceDuration.value = '-' + String( minuteCounts ) + ':';
|
niceDuration.value = '-' + String( minuteCounts ) + ':';
|
||||||
if ( ( '' + minuteCounts ).length === 1 ) {
|
if ( ( '' + minuteCounts ).length === 1 ) {
|
||||||
niceDuration.value = '-0' + minuteCounts + ':';
|
niceDuration.value = '-0' + minuteCounts + ':';
|
||||||
}
|
}
|
||||||
const secondCounts = Math.floor( ( playingSong.duration - pos ) - minuteCounts * 60 );
|
const secondCounts = Math.floor( ( playingSong.duration - pos.value ) - minuteCounts * 60 );
|
||||||
if ( ( '' + secondCounts ).length === 1 ) {
|
if ( ( '' + secondCounts ).length === 1 ) {
|
||||||
niceDuration.value += '0' + secondCounts;
|
niceDuration.value += '0' + secondCounts;
|
||||||
} else {
|
} else {
|
||||||
@@ -206,6 +261,7 @@
|
|||||||
try {
|
try {
|
||||||
clearInterval( progressTracker );
|
clearInterval( progressTracker );
|
||||||
} catch ( _ ) { /* empty */ }
|
} catch ( _ ) { /* empty */ }
|
||||||
|
isPlaying.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose( {
|
defineExpose( {
|
||||||
@@ -229,7 +285,18 @@
|
|||||||
transition: all 1s;
|
transition: all 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-name {
|
.main-player {
|
||||||
|
height: 12vh;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
transition: all 1s;
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-name-wrapper {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -238,8 +305,12 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
flex-direction: row;
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-name {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-player {
|
.logo-player {
|
||||||
@@ -248,10 +319,6 @@
|
|||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-view {
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-hidden {
|
.player-hidden {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -325,4 +392,37 @@
|
|||||||
.pl-wrapper {
|
.pl-wrapper {
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playback {
|
||||||
|
width: fit-content;
|
||||||
|
bottom: -20px;
|
||||||
|
left: 7%;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback.full-screen {
|
||||||
|
left: 30%;
|
||||||
|
position: absolute;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback-pos-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback-pos-wrapper p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback-pos-wrapper.full-screen p {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback-pos-wrapper.full-screen .playback-duration {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,9 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>Playlist</h1>
|
<h1>Playlist</h1>
|
||||||
<div class="playlist-box">
|
<div class="playlist-box" id="pl-box">
|
||||||
<div class="song" v-for="song in $props.playlist" v-bind:key="song.id">
|
<div class="song" v-for="song in computedPlaylist" v-bind:key="song.id"
|
||||||
|
:class="( song.id === ( $props.playlist ? $props.playlist [ $props.currentlyPlaying ?? 0 ].id : '' ) && isPlaying ? 'playing' : ' not-playing' )
|
||||||
|
+ ( ( !isPlaying && ( song.id === ( $props.playlist ? $props.playlist [ $props.currentlyPlaying ?? 0 ].id : '' ) ) ) ? ' active-song' : '' )">
|
||||||
<img :src="song.cover" alt="Song cover" class="song-cover">
|
<img :src="song.cover" alt="Song cover" class="song-cover">
|
||||||
|
<div v-if="song.id === ( $props.playlist ? $props.playlist [ $props.currentlyPlaying ?? 0 ].id : '' ) && $props.isPlaying" class="playing-symbols">
|
||||||
|
<div class="playing-symbols-wrapper">
|
||||||
|
<div class="playing-bar" id="bar-1"></div>
|
||||||
|
<div class="playing-bar" id="bar-2"></div>
|
||||||
|
<div class="playing-bar" id="bar-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="material-symbols-outlined play-icon" @click="control( 'play' )" v-if="song.id === ( $props.playlist ? $props.playlist [ $props.currentlyPlaying ?? 0 ].id : '' )">play_arrow</span>
|
||||||
|
<span class="material-symbols-outlined play-icon" @click="play( song.id )" v-else>play_arrow</span>
|
||||||
|
<span class="material-symbols-outlined pause-icon" @click="control( 'pause' )">pause</span>
|
||||||
<h3 class="song-title">{{ song.title }}</h3>
|
<h3 class="song-title">{{ song.title }}</h3>
|
||||||
<p class="playing-in">playing in</p>
|
<p class="playing-in">playing in</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,30 +25,101 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Song } from '@/scripts/song';
|
import type { Song } from '@/scripts/song';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
defineProps( {
|
const props = defineProps( {
|
||||||
'playlist': {
|
'playlist': {
|
||||||
default: [],
|
default: [],
|
||||||
required: true,
|
required: true,
|
||||||
type: Array<Song>
|
type: Array<Song>
|
||||||
|
},
|
||||||
|
'currentlyPlaying': {
|
||||||
|
default: 0,
|
||||||
|
required: true,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
'isPlaying': {
|
||||||
|
default: true,
|
||||||
|
required: true,
|
||||||
|
type: Boolean,
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
const computedPlaylist = computed( () => {
|
||||||
|
let pl: Song[] = [];
|
||||||
|
// ( document.getElementById( 'pl-box' ) as HTMLDivElement ).scrollTo( { behavior: 'smooth', top: 0 } );
|
||||||
|
for ( let i = props.currentlyPlaying; i < props.playlist.length; i++ ) {
|
||||||
|
pl.push( props.playlist[ i ] );
|
||||||
|
}
|
||||||
|
return pl;
|
||||||
|
} );
|
||||||
|
|
||||||
|
// TODO: Implement
|
||||||
|
// const getTimeUntil = computed( () => {
|
||||||
|
// return ( song ) => {
|
||||||
|
// let timeRemaining = 0;
|
||||||
|
// for ( let i = this.queuePos; i < Object.keys( this.songs ).length; i++ ) {
|
||||||
|
// if ( this.songs[ i ] == song ) {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// timeRemaining += parseInt( this.songs[ i ].duration );
|
||||||
|
// }
|
||||||
|
// if ( this.isPlaying ) {
|
||||||
|
// if ( timeRemaining === 0 ) {
|
||||||
|
// return 'Currently playing';
|
||||||
|
// } else {
|
||||||
|
// return 'Playing in less than ' + Math.ceil( timeRemaining / 60 - this.pos / 60 ) + 'min';
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if ( timeRemaining === 0 ) {
|
||||||
|
// return 'Plays next';
|
||||||
|
// } else {
|
||||||
|
// return 'Playing less than ' + Math.ceil( timeRemaining / 60 - this.pos / 60 ) + 'min after starting to play';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } );
|
||||||
|
|
||||||
|
|
||||||
|
const control = ( action: string ) => {
|
||||||
|
emits( 'control', action );
|
||||||
|
}
|
||||||
|
|
||||||
|
const play = ( song: string ) => {
|
||||||
|
emits( 'play-song', song );
|
||||||
|
}
|
||||||
|
|
||||||
|
const emits = defineEmits( [ 'play-song', 'control' ] );
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.playlist-box {
|
.playlist-box {
|
||||||
height: 80vh !important;
|
height: 80vh !important;
|
||||||
|
width: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song {
|
.song {
|
||||||
border: solid var( --primary-color ) 1px;
|
border: solid var( --primary-color ) 1px;
|
||||||
padding: 10px;
|
|
||||||
margin: 5px;
|
|
||||||
width: 50%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 1vh;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song .song-cover {
|
||||||
|
width: 5vw;
|
||||||
|
height: 5vw;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
font-size: 5vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-title {
|
.song-title {
|
||||||
@@ -44,7 +127,99 @@
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-cover {
|
.playing-symbols {
|
||||||
height: 70px;
|
position: absolute;
|
||||||
|
left: 1vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0;
|
||||||
|
width: 5vw;
|
||||||
|
height: 5vw;
|
||||||
|
background-color: rgba( 0, 0, 0, 0.6 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playing-symbols-wrapper {
|
||||||
|
width: 4vw;
|
||||||
|
height: 5vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing-bar {
|
||||||
|
height: 60%;
|
||||||
|
background-color: white;
|
||||||
|
width: 10%;
|
||||||
|
border-radius: 50px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bar-1 {
|
||||||
|
animation: music-playing 0.9s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bar-2 {
|
||||||
|
animation: music-playing 0.9s infinite ease-in-out;
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bar-3 {
|
||||||
|
animation: music-playing 0.9s infinite ease-in-out;
|
||||||
|
animation-delay: 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes music-playing {
|
||||||
|
0% {
|
||||||
|
transform: scaleY( 1 );
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scaleY( 0.5 );
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scaleY( 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-icon, .pause-icon {
|
||||||
|
display: none;
|
||||||
|
width: 5vw;
|
||||||
|
height: 5vw;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
font-size: 5vw;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing:hover .pause-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing:hover .playing-symbols {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song:hover .song-cover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-playing:hover .play-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-song .pause-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-song.not-playing .song-cover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-song .song-image, .active-song:hover .pause-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
145
MusicPlayerV2-GUI/src/components/sliderView.vue
Normal file
145
MusicPlayerV2-GUI/src/components/sliderView.vue
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 100%;">
|
||||||
|
<progress :id="'progress-slider-' + name" class="progress-slider" :value="sliderProgress" max="1000" @mousedown="( e ) => { setPos( e ) }"
|
||||||
|
:class="active ? '' : 'slider-inactive'"></progress>
|
||||||
|
<div v-if="active" id="slider-knob" @mousedown="( e ) => { startMove( e ) }"
|
||||||
|
:style="'left: ' + ( originalPos + sliderPos ) + 'px;'">
|
||||||
|
<div id="slider-knob-style"></div>
|
||||||
|
</div>
|
||||||
|
<div v-else id="slider-knob" class="slider-inactive" style="left: 0;">
|
||||||
|
<div id="slider-knob-style"></div>
|
||||||
|
</div>
|
||||||
|
<div id="drag-support" @mousemove="e => { handleDrag( e ) }" @mouseup="() => { stopMove(); }"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.progress-slider {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var( --slider-color );
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-slider::-webkit-progress-value {
|
||||||
|
background-color: var( --slider-color );
|
||||||
|
}
|
||||||
|
|
||||||
|
#slider-knob {
|
||||||
|
height: 20px;
|
||||||
|
width: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-end;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slider-knob-style {
|
||||||
|
background-color: var( --slider-color );
|
||||||
|
height: 15px;
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#drag-support {
|
||||||
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-support-active {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-inactive {
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps( {
|
||||||
|
style: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '1',
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
const offset = ref( 0 );
|
||||||
|
const isDragging = ref( false );
|
||||||
|
const sliderPos = ref( 0 );
|
||||||
|
const originalPos= ref( 0 );
|
||||||
|
const sliderProgress = ref( 0 );
|
||||||
|
|
||||||
|
const handleDrag = ( e: MouseEvent ) => {
|
||||||
|
if ( isDragging.value ) {
|
||||||
|
if ( 0 < originalPos.value + e.screenX - offset.value && originalPos.value + e.screenX - offset.value < ( document.getElementById( 'progress-slider-' + props.name ) as HTMLProgressElement ).clientWidth - 5 ) {
|
||||||
|
sliderPos.value = e.screenX - offset.value;
|
||||||
|
calcProgressPos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const startMove = ( e: MouseEvent ) => {
|
||||||
|
offset.value = e.screenX;
|
||||||
|
isDragging.value = true;
|
||||||
|
( document.getElementById( 'drag-support' ) as HTMLDivElement ).classList.add( 'drag-support-active' );
|
||||||
|
}
|
||||||
|
const stopMove = () => {
|
||||||
|
originalPos.value += sliderPos.value;
|
||||||
|
isDragging.value = false;
|
||||||
|
offset.value = 0;
|
||||||
|
sliderPos.value = 0;
|
||||||
|
( document.getElementById( 'drag-support' ) as HTMLDivElement ).classList.remove( 'drag-support-active' );
|
||||||
|
calcPlaybackPos();
|
||||||
|
}
|
||||||
|
const setPos = ( e: MouseEvent ) => {
|
||||||
|
if ( props.active ) {
|
||||||
|
originalPos.value = e.offsetX;
|
||||||
|
calcProgressPos();
|
||||||
|
calcPlaybackPos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const calcProgressPos = () => {
|
||||||
|
sliderProgress.value = Math.ceil( ( originalPos.value + sliderPos.value ) / ( ( document.getElementById( 'progress-slider-' + props.name ) as HTMLProgressElement ).clientWidth - 5 ) * 1000 );
|
||||||
|
}
|
||||||
|
const calcPlaybackPos = () => {
|
||||||
|
emits( 'pos', Math.round( ( originalPos.value + sliderPos.value ) / ( ( document.getElementById( 'progress-slider-' + props.name ) as HTMLProgressElement ).clientWidth - 5 ) * props.duration ) );
|
||||||
|
}
|
||||||
|
watch( () => props.position, () => {
|
||||||
|
if ( !isDragging.value ) {
|
||||||
|
sliderProgress.value = Math.ceil( props.position / props.duration * 1000 + 2 );
|
||||||
|
originalPos.value = Math.ceil( props.position / props.duration * ( ( document.getElementById( 'progress-slider-' + props.name ) as HTMLProgressElement ).scrollWidth - 5 ) );
|
||||||
|
}
|
||||||
|
} )
|
||||||
|
|
||||||
|
const emits = defineEmits( [ 'pos' ] );
|
||||||
|
</script>
|
||||||
@@ -189,6 +189,12 @@ class MusicKitJSWrapper {
|
|||||||
if ( this.playlist.length > 0 ) {
|
if ( this.playlist.length > 0 ) {
|
||||||
this.playingSongID = playlistID;
|
this.playingSongID = playlistID;
|
||||||
this.isPreparedToPlay = true;
|
this.isPreparedToPlay = true;
|
||||||
|
for ( const el in this.queue ) {
|
||||||
|
if ( this.queue[ el ] === playlistID ) {
|
||||||
|
this.queuePos = parseInt( el );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
if ( this.playlist[ this.playingSongID ].origin === 'apple-music' ) {
|
||||||
this.musicKit.setQueue( { 'song': this.playlist[ this.playingSongID ].id } ).then( () => {
|
this.musicKit.setQueue( { 'song': this.playlist[ this.playingSongID ].id } ).then( () => {
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
@@ -303,13 +309,16 @@ class MusicKitJSWrapper {
|
|||||||
this.queue = [];
|
this.queue = [];
|
||||||
if ( enabled ) {
|
if ( enabled ) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
for ( const _ in this.playlist ) {
|
const d = [];
|
||||||
let val = Math.floor( Math.random() * this.playlist.length );
|
for ( const el in this.playlist ) {
|
||||||
while ( this.queue.includes( val ) ) {
|
d.push( parseInt( el ) );
|
||||||
val = Math.floor( Math.random() * this.playlist.length );
|
|
||||||
}
|
|
||||||
this.queue.push( val );
|
|
||||||
}
|
}
|
||||||
|
this.queue = d.map( value => ( { value, sort: Math.random() } ) )
|
||||||
|
.sort( ( a, b ) => a.sort - b.sort )
|
||||||
|
.map( ( { value } ) => value );
|
||||||
|
this.queue.splice( this.queue.indexOf( this.playingSongID ), 1 );
|
||||||
|
this.queue.push( this.playingSongID );
|
||||||
|
this.queue.reverse();
|
||||||
} else {
|
} else {
|
||||||
for ( const song in this.playlist ) {
|
for ( const song in this.playlist ) {
|
||||||
this.queue.push( parseInt( song ) );
|
this.queue.push( parseInt( song ) );
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.player-view {
|
.player-view {
|
||||||
height: 10vh;
|
height: 13vh;
|
||||||
width: calc( 100vw - 20px );
|
width: calc( 100vw - 20px );
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
|
|||||||
Reference in New Issue
Block a user