mirror of
https://github.com/janishutz/MusicPlayerV2.git
synced 2025-11-25 13:04:23 +00:00
add lots more features
This commit is contained in:
@@ -29,7 +29,6 @@ app.get( '/openSongs', ( req, res ) => {
|
|||||||
app.get( '/indexDirs', ( req, res ) => {
|
app.get( '/indexDirs', ( req, res ) => {
|
||||||
if ( req.query.dir ) {
|
if ( req.query.dir ) {
|
||||||
if ( indexedData[ req.query.dir ] ) {
|
if ( indexedData[ req.query.dir ] ) {
|
||||||
console.log( 'using cache' );
|
|
||||||
res.send( indexedData[ req.query.dir ] );
|
res.send( indexedData[ req.query.dir ] );
|
||||||
} else {
|
} else {
|
||||||
fs.readdir( req.query.dir, { encoding: 'utf-8' }, ( err, dat ) => {
|
fs.readdir( req.query.dir, { encoding: 'utf-8' }, ( err, dat ) => {
|
||||||
@@ -48,7 +47,7 @@ app.get( '/indexDirs', ( req, res ) => {
|
|||||||
'year': metadata[ 'common' ][ 'year' ],
|
'year': metadata[ 'common' ][ 'year' ],
|
||||||
'bpm': metadata[ 'common' ][ 'bpm' ],
|
'bpm': metadata[ 'common' ][ 'bpm' ],
|
||||||
'genre': metadata[ 'common' ][ 'genre' ],
|
'genre': metadata[ 'common' ][ 'genre' ],
|
||||||
'duration': metadata[ 'format' ][ 'duration' ],
|
'duration': Math.round( metadata[ 'format' ][ 'duration' ] ),
|
||||||
'isLossless': metadata[ 'format' ][ 'lossless' ],
|
'isLossless': metadata[ 'format' ][ 'lossless' ],
|
||||||
'sampleRate': metadata[ 'format' ][ 'sampleRate' ],
|
'sampleRate': metadata[ 'format' ][ 'sampleRate' ],
|
||||||
'bitrate': metadata[ 'format' ][ 'bitrate' ],
|
'bitrate': metadata[ 'format' ][ 'bitrate' ],
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="media-pool">
|
<div class="media-pool">
|
||||||
<div v-if="hasLoadedSongs" style="width: 100%;" class="song-list-wrapper">
|
<div v-if="hasLoadedSongs" style="width: 100%;" class="song-list-wrapper">
|
||||||
<div v-for="song in songQueue" class="song-list">
|
<div v-for="song in songQueue" class="song-list" :class="[ isPlaying ? ( currentlyPlaying === song.filename ? 'playing': 'not-playing' ) : 'not-playing', !isPlaying && currentlyPlaying === song.filename ? 'active-song': undefined ]">
|
||||||
<span class="material-symbols-outlined song-image" v-if="!loadCoverArtPreview || !song.hasCoverArt">music_note</span>
|
<span class="material-symbols-outlined song-image" v-if="!loadCoverArtPreview || !song.hasCoverArt">music_note</span>
|
||||||
<img v-else :src="'http://localhost:8081/getSongCover?filename=' + song.filename" class="song-image">
|
<img v-else :src="'http://localhost:8081/getSongCover?filename=' + song.filename" class="song-image">
|
||||||
|
<div v-if="currentlyPlaying === song.filename && 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="play( song )">play_arrow</span>
|
<span class="material-symbols-outlined play-icon" @click="play( song )">play_arrow</span>
|
||||||
|
<span class="material-symbols-outlined pause-icon" @click="pause( song )">pause</span>
|
||||||
<h3>{{ song.title }}</h3>
|
<h3>{{ song.title }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,6 +29,61 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.playing-symbols {
|
||||||
|
position: absolute;
|
||||||
|
left: 9.95vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
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: var( --primary-color );
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
animation: spin 2s infinite linear;
|
animation: spin 2s infinite linear;
|
||||||
}
|
}
|
||||||
@@ -84,7 +147,7 @@
|
|||||||
font-size: 5vw;
|
font-size: 5vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-icon {
|
.play-icon, .pause-icon {
|
||||||
display: none;
|
display: none;
|
||||||
width: 5vw;
|
width: 5vw;
|
||||||
height: 5vw;
|
height: 5vw;
|
||||||
@@ -92,15 +155,32 @@
|
|||||||
object-position: center;
|
object-position: center;
|
||||||
font-size: 5vw;
|
font-size: 5vw;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing:hover .pause-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing:hover .playing-symbols {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-list:hover .song-image {
|
.song-list:hover .song-image {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-list:hover .play-icon {
|
.not-playing:hover .play-icon {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.active-song .pause-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-song .song-image, .active-song:hover .pause-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -111,15 +191,72 @@
|
|||||||
hasLoadedSongs: false,
|
hasLoadedSongs: false,
|
||||||
isLoadingSongs: false,
|
isLoadingSongs: false,
|
||||||
loadCoverArtPreview: true,
|
loadCoverArtPreview: true,
|
||||||
|
allSongs: [],
|
||||||
songQueue: [],
|
songQueue: [],
|
||||||
loadedDirs: [],
|
loadedDirs: [],
|
||||||
allowedFiletypes: [ '.mp3', '.wav' ],
|
allowedFiletypes: [ '.mp3', '.wav' ],
|
||||||
currentlyPlaying: '',
|
currentlyPlaying: '',
|
||||||
|
isPlaying: false,
|
||||||
|
songPos: 0,
|
||||||
|
repeat: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getLoadedDirs () {
|
update( status ) {
|
||||||
|
if ( status.type === 'playback' ) {
|
||||||
|
this.isPlaying = status.status;
|
||||||
|
} else if ( status.type === 'next' ) {
|
||||||
|
if ( this.songPos < this.songQueue.length - 1 ) {
|
||||||
|
this.songPos += 1;
|
||||||
|
this.queueHandler( 'load' );
|
||||||
|
} else {
|
||||||
|
this.songPos = 0;
|
||||||
|
if ( this.repeat ) {
|
||||||
|
this.queueHandler( 'load' );
|
||||||
|
} else {
|
||||||
|
this.isPlaying = false;
|
||||||
|
this.currentlyPlaying = '';
|
||||||
|
this.$emit( 'com', { 'type': 'startPlayback', 'song': this.songQueue[ 0 ], 'autoplay': false } );
|
||||||
|
this.$emit( 'com', { 'type': 'pause' } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ( status.type === 'previous' ) {
|
||||||
|
if ( this.songPos > 0 ) {
|
||||||
|
this.songPos -= 1;
|
||||||
|
} else {
|
||||||
|
this.songPos = this.songQueue.length - 1;
|
||||||
|
}
|
||||||
|
this.queueHandler( 'load' );
|
||||||
|
} else if ( status.type === 'shuffle' ) {
|
||||||
|
this.queueHandler( 'shuffle' );
|
||||||
|
} else if ( status.type === 'shuffleOff' ) {
|
||||||
|
this.queueHandler( 'shuffleOff' );
|
||||||
|
} else if ( status.type === 'repeat' ) {
|
||||||
|
this.repeat = true;
|
||||||
|
} else if ( status.type === 'repeatOff' ) {
|
||||||
|
this.repeat = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
queueHandler ( command ) {
|
||||||
|
if ( command === 'load' ) {
|
||||||
|
this.play( this.songQueue[ this.songPos ] );
|
||||||
|
} else if ( command === 'shuffle' ) {
|
||||||
|
let processArray = JSON.parse( JSON.stringify( this.allSongs ) );
|
||||||
|
let newOrder = [];
|
||||||
|
for ( let i = 0; i < this.allSongs.length; i++ ) {
|
||||||
|
let randNum = Math.floor( Math.random() * this.allSongs.length );
|
||||||
|
while ( newOrder.includes( randNum ) ) {
|
||||||
|
randNum = Math.floor( Math.random() * this.allSongs.length );
|
||||||
|
}
|
||||||
|
newOrder.push( randNum );
|
||||||
|
}
|
||||||
|
this.songQueue = [];
|
||||||
|
for ( let el in newOrder ) {
|
||||||
|
this.songQueue.push( processArray[ newOrder[ el ] ] );
|
||||||
|
}
|
||||||
|
} else if ( command === 'shuffleOff' ) {
|
||||||
|
this.songQueue = JSON.parse( JSON.stringify( this.allSongs ) );
|
||||||
|
}
|
||||||
},
|
},
|
||||||
loadSongs() {
|
loadSongs() {
|
||||||
this.isLoadingSongs = true;
|
this.isLoadingSongs = true;
|
||||||
@@ -139,17 +276,35 @@
|
|||||||
res.json().then( json => {
|
res.json().then( json => {
|
||||||
for ( let song in json ) {
|
for ( let song in json ) {
|
||||||
this.songQueue.push( json[ song ] );
|
this.songQueue.push( json[ song ] );
|
||||||
|
this.allSongs.push( json[ song ] );
|
||||||
}
|
}
|
||||||
|
this.queueHandler();
|
||||||
this.isLoadingSongs = false;
|
this.isLoadingSongs = false;
|
||||||
this.hasLoadedSongs = true;
|
this.hasLoadedSongs = true;
|
||||||
|
this.$emit( 'com', { 'type': 'songsLoaded' } );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
play( song ) {
|
play( song ) {
|
||||||
this.$emit( 'playing', song );
|
if ( song.filename === this.currentlyPlaying ) {
|
||||||
|
this.$emit( 'com', { 'type': 'play', 'song': song } );
|
||||||
|
} else {
|
||||||
|
for ( let s in this.songQueue ) {
|
||||||
|
if ( this.songQueue[ s ][ 'filename' ] === song.filename ) {
|
||||||
|
this.songPos = parseInt( s );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.$emit( 'com', { 'type': 'startPlayback', 'song': song } );
|
||||||
|
}
|
||||||
|
this.currentlyPlaying = song.filename;
|
||||||
|
this.update( { 'type': 'playback', 'status': true } );
|
||||||
|
},
|
||||||
|
pause( song ) {
|
||||||
|
this.update( { 'type': 'playback', 'status': false } );
|
||||||
|
this.$emit( 'com', { 'type': 'pause', 'song': song } );
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,62 +1,263 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="player">
|
<div class="player">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div v-if="audioLoaded">
|
<span class="material-symbols-outlined control-icon" :class="audioLoaded ? 'active': 'inactive'" @click="control( 'previous' )">skip_previous</span>
|
||||||
<span class="material-symbols-outlined control-icon" v-if="!isPlaying" @click="control( 'play' )">play_arrow</span>
|
<span class="material-symbols-outlined control-icon" :class="audioLoaded ? 'active': 'inactive'" @click="control( 'replay10' )">replay_10</span>
|
||||||
<span class="material-symbols-outlined control-icon" v-else @click="control( 'pause' )">pause</span>
|
<span class="material-symbols-outlined control-icon play-pause" v-if="!isPlaying && audioLoaded" @click="control( 'play' )">play_arrow</span>
|
||||||
</div>
|
<span class="material-symbols-outlined control-icon play-pause" v-else-if="isPlaying && audioLoaded" @click="control( 'pause' )">pause</span>
|
||||||
<span class="material-symbols-outlined control-icon" style="cursor: default;" v-else>play_disabled</span>
|
<span class="material-symbols-outlined control-icon play-pause" style="cursor: default;" v-else>play_disabled</span>
|
||||||
|
<span class="material-symbols-outlined control-icon" :class="audioLoaded ? 'active': 'inactive'" @click="control( 'forward10' )">forward_10</span>
|
||||||
|
<span class="material-symbols-outlined control-icon" :class="audioLoaded ? 'active': 'inactive'" @click="control( 'next' )" style="margin-right: 1vw;">skip_next</span>
|
||||||
|
<span class="material-symbols-outlined control-icon" :class="hasLoadedSongs ? 'active': 'inactive'" v-if="!isShuffleEnabled" @click="control( 'shuffleOn' )">shuffle</span>
|
||||||
|
<span class="material-symbols-outlined control-icon" :class="hasLoadedSongs ? 'active': 'inactive'" v-else @click="control( 'shuffleOff' )">shuffle_on</span>
|
||||||
|
<span class="material-symbols-outlined control-icon" :class="hasLoadedSongs ? 'active': 'inactive'" v-if="repeatMode === 'off'" @click="control( 'repeatOne' )">repeat</span>
|
||||||
|
<span class="material-symbols-outlined control-icon" :class="hasLoadedSongs ? 'active': 'inactive'" v-else-if="repeatMode === 'one'" @click="control( 'repeatAll' )">repeat_one_on</span>
|
||||||
|
<span class="material-symbols-outlined control-icon" :class="hasLoadedSongs ? 'active': 'inactive'" v-else-if="repeatMode === 'all'" @click="control( 'repeatOff' )">repeat_on</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="song-info">
|
<div class="song-info">
|
||||||
<audio v-if="audioLoaded" :src="'http://localhost:8081/getSongFile?filename=' + playingSong.filename" id="music-player"></audio>
|
<audio v-if="audioLoaded" :src="'http://localhost:8081/getSongFile?filename=' + playingSong.filename" id="music-player"></audio>
|
||||||
|
<div class="song-info-wrapper">
|
||||||
|
<div v-if="audioLoaded">
|
||||||
|
<span class="material-symbols-outlined image" v-if="!playingSong.hasCoverArt">music_note</span>
|
||||||
|
<img v-else :src="'http://localhost:8081/getSongCover?filename=' + playingSong.filename" class="image">
|
||||||
|
</div>
|
||||||
|
<span class="material-symbols-outlined image" v-else>music_note</span>
|
||||||
|
<div class="name">
|
||||||
|
<h3>{{ playingSong.title ?? 'No song selected' }}</h3>
|
||||||
|
<p>{{ playingSong.artist }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="playback-pos-info">
|
||||||
|
<div style="margin-right: auto;">{{ playbackPosBeautified }}</div>
|
||||||
|
<div>{{ durationBeautified }}</div>
|
||||||
|
</div>
|
||||||
|
<sliderView :active="audioLoaded" :position="playbackPos" :duration="playingSong.duration" @pos="( p ) => { setPos( p ) }"></sliderView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.song-info {
|
.song-info {
|
||||||
background-color: var( --accent-background );
|
background-color: #8e9ced;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
width: 6vh;
|
||||||
|
height: 6vh;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
font-size: 6vh;
|
||||||
|
margin-left: 1vh;
|
||||||
|
margin-top: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-info-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-info-wrapper h3 {
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 0.5vh;
|
||||||
|
margin-top: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-left: 5%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-icon {
|
.control-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 3vh;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-pause {
|
||||||
|
font-size: 5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive {
|
||||||
|
color: gray;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback-pos-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 99%;
|
||||||
|
margin-left: 0.5%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 17px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import SliderView from './sliderView.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
playingSong: {},
|
playingSong: {},
|
||||||
audioLoaded: false,
|
audioLoaded: false,
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
|
isShuffleEnabled: false,
|
||||||
|
repeatMode: 'off',
|
||||||
|
progressTracker: null,
|
||||||
|
playbackPos: 0,
|
||||||
|
playbackPosBeautified: '00:00',
|
||||||
|
durationBeautified: '--:--',
|
||||||
|
hasLoadedSongs: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
SliderView,
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
play( song ) {
|
play( song, autoplay, doCrossFade = false ) {
|
||||||
this.playingSong = song;
|
this.playingSong = song;
|
||||||
this.audioLoaded = true;
|
this.audioLoaded = true;
|
||||||
this.init();
|
this.init( doCrossFade, autoplay );
|
||||||
},
|
},
|
||||||
init() {
|
init( doCrossFade, autoplay ) {
|
||||||
|
this.control( 'reset' );
|
||||||
|
// TODO: make it support cross-fade
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
document.getElementById( 'music-player' ).play();
|
if ( autoplay ) {
|
||||||
|
this.control( 'play' );
|
||||||
this.isPlaying = true;
|
this.isPlaying = true;
|
||||||
|
}
|
||||||
|
const minuteCount = Math.floor( this.playingSong.duration / 60 );
|
||||||
|
this.durationBeautified = minuteCount + ':';
|
||||||
|
if ( ( '' + minuteCount ).length === 1 ) {
|
||||||
|
this.durationBeautified = '0' + minuteCount + ':';
|
||||||
|
}
|
||||||
|
const secondCount = Math.floor( this.playingSong.duration - minuteCount * 60 );
|
||||||
|
if ( ( '' + secondCount ).length === 1 ) {
|
||||||
|
this.durationBeautified += '0' + secondCount;
|
||||||
|
} else {
|
||||||
|
this.durationBeautified += secondCount;
|
||||||
|
}
|
||||||
|
let musicPlayer = document.getElementById( 'music-player' );
|
||||||
|
musicPlayer.onended = () => {
|
||||||
|
if ( musicPlayer.currentTime >= this.playingSong.duration - 1 ) {
|
||||||
|
if ( this.repeatMode !== 'one' ) {
|
||||||
|
this.control( 'next' );
|
||||||
|
} else {
|
||||||
|
musicPlayer.currentTime = 0;
|
||||||
|
this.control( 'play' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}, 300 );
|
}, 300 );
|
||||||
},
|
},
|
||||||
control( action ) {
|
control( action ) {
|
||||||
if ( document.getElementById( 'music-player' ) ) {
|
// https://css-tricks.com/lets-create-a-custom-audio-player/
|
||||||
|
let musicPlayer = document.getElementById( 'music-player' );
|
||||||
|
if ( musicPlayer ) {
|
||||||
if ( action === 'play' ) {
|
if ( action === 'play' ) {
|
||||||
document.getElementById( 'music-player' ).play();
|
this.$emit( 'update', { 'type': 'playback', 'status': true } );
|
||||||
|
musicPlayer.play();
|
||||||
this.isPlaying = true;
|
this.isPlaying = true;
|
||||||
|
this.progressTracker = setInterval( () => {
|
||||||
|
this.playbackPos = musicPlayer.currentTime;
|
||||||
|
const minuteCount = Math.floor( this.playbackPos / 60 );
|
||||||
|
this.playbackPosBeautified = minuteCount + ':';
|
||||||
|
if ( ( '' + minuteCount ).length === 1 ) {
|
||||||
|
this.playbackPosBeautified = '0' + minuteCount + ':';
|
||||||
|
}
|
||||||
|
const secondCount = Math.floor( this.playbackPos - minuteCount * 60 );
|
||||||
|
if ( ( '' + secondCount ).length === 1 ) {
|
||||||
|
this.playbackPosBeautified += '0' + secondCount;
|
||||||
|
} else {
|
||||||
|
this.playbackPosBeautified += secondCount;
|
||||||
|
}
|
||||||
|
}, 0.02 );
|
||||||
} else if ( action === 'pause' ) {
|
} else if ( action === 'pause' ) {
|
||||||
document.getElementById( 'music-player' ).pause();
|
this.$emit( 'update', { 'type': 'playback', 'status': false } );
|
||||||
|
musicPlayer.pause();
|
||||||
|
try {
|
||||||
|
clearInterval( this.progressTracker );
|
||||||
|
} catch ( err ) {};
|
||||||
this.isPlaying = false;
|
this.isPlaying = false;
|
||||||
|
} else if ( action === 'replay10' ) {
|
||||||
|
musicPlayer.currentTime = musicPlayer.currentTime > 10 ? musicPlayer.currentTime - 10 : 0;
|
||||||
|
} else if ( action === 'forward10' ) {
|
||||||
|
if ( musicPlayer.currentTime < ( musicPlayer.duration - 10 ) ) {
|
||||||
|
musicPlayer.currentTime = musicPlayer.currentTime + 10;
|
||||||
|
} else {
|
||||||
|
if ( this.repeatMode !== 'one' ) {
|
||||||
|
this.control( 'next' );
|
||||||
|
} else {
|
||||||
|
musicPlayer.currentTime = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if ( action === 'reset' ) {
|
||||||
|
clearInterval( this.progressTracker );
|
||||||
|
this.playbackPos = 0;
|
||||||
|
musicPlayer.currentTime = 0;
|
||||||
|
} else if ( action === 'next' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'next' } );
|
||||||
|
} else if ( action === 'previous' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'previous' } );
|
||||||
|
} else if ( action === 'shuffleOff' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'shuffleOff' } );
|
||||||
|
this.isShuffleEnabled = false;
|
||||||
|
} else if ( action === 'shuffleOn' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'shuffle' } );
|
||||||
|
this.isShuffleEnabled = true;
|
||||||
|
} else if ( action === 'repeatOne' ) {
|
||||||
|
this.repeatMode = 'one';
|
||||||
|
} else if ( action === 'repeatAll' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'repeat' } );
|
||||||
|
this.repeatMode = 'all';
|
||||||
|
} else if ( action === 'repeatOff' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'repeatOff' } );
|
||||||
|
this.repeatMode = 'off';
|
||||||
}
|
}
|
||||||
|
} else if ( action === 'songsLoaded' ) {
|
||||||
|
this.hasLoadedSongs = true;
|
||||||
|
} else if ( action === 'shuffleOff' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'shuffleOff' } );
|
||||||
|
this.isShuffleEnabled = false;
|
||||||
|
} else if ( action === 'shuffleOn' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'shuffle' } );
|
||||||
|
this.isShuffleEnabled = true;
|
||||||
|
} else if ( action === 'repeatOne' ) {
|
||||||
|
this.repeatMode = 'one';
|
||||||
|
} else if ( action === 'repeatAll' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'repeat' } );
|
||||||
|
this.repeatMode = 'all';
|
||||||
|
} else if ( action === 'repeatOff' ) {
|
||||||
|
this.$emit( 'update', { 'type': 'repeatOff' } );
|
||||||
|
this.repeatMode = 'off';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setPos( pos ) {
|
||||||
|
let musicPlayer = document.getElementById( 'music-player' );
|
||||||
|
musicPlayer.currentTime = pos;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
144
frontend/src/components/sliderView.vue
Normal file
144
frontend/src/components/sliderView.vue
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 100%;">
|
||||||
|
<progress id="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: ' + ( parseInt( originalPos ) + parseInt( 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: #baf4c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-slider::-webkit-progress-value {
|
||||||
|
background-color: #baf4c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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: #baf4c9;
|
||||||
|
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>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
style: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
offset: 0,
|
||||||
|
isDragging: false,
|
||||||
|
sliderPos: 0,
|
||||||
|
originalPos: 0,
|
||||||
|
sliderProgress: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDrag( e ) {
|
||||||
|
if ( this.isDragging ) {
|
||||||
|
if ( 0 < this.originalPos + e.screenX - this.offset && this.originalPos + e.screenX - this.offset < document.getElementById( 'progress-slider' ).clientWidth - 5 ) {
|
||||||
|
this.sliderPos = e.screenX - this.offset;
|
||||||
|
this.calcProgressPos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startMove( e ) {
|
||||||
|
this.offset = e.screenX;
|
||||||
|
this.isDragging = true;
|
||||||
|
document.getElementById( 'drag-support' ).classList.add( 'drag-support-active' );
|
||||||
|
},
|
||||||
|
stopMove() {
|
||||||
|
this.originalPos += parseInt( this.sliderPos );
|
||||||
|
this.isDragging = false;
|
||||||
|
this.offset = 0;
|
||||||
|
this.sliderPos = 0;
|
||||||
|
document.getElementById( 'drag-support' ).classList.remove( 'drag-support-active' );
|
||||||
|
this.calcPlaybackPos();
|
||||||
|
},
|
||||||
|
setPos ( e ) {
|
||||||
|
if ( this.active ) {
|
||||||
|
this.originalPos = e.offsetX;
|
||||||
|
this.calcProgressPos();
|
||||||
|
this.calcPlaybackPos();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calcProgressPos() {
|
||||||
|
this.sliderProgress = Math.ceil( ( this.originalPos + parseInt( this.sliderPos ) ) / ( document.getElementById( 'progress-slider' ).clientWidth - 5 ) * 1000 );
|
||||||
|
},
|
||||||
|
calcPlaybackPos() {
|
||||||
|
this.$emit( 'pos', Math.round( ( this.originalPos + parseInt( this.sliderPos ) ) / ( document.getElementById( 'progress-slider' ).clientWidth - 5 ) * this.duration ) );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
position() {
|
||||||
|
if ( !this.isDragging ) {
|
||||||
|
this.sliderProgress = Math.ceil( this.position / this.duration * 1000 + 2 );
|
||||||
|
this.originalPos = Math.ceil( this.position / this.duration * ( document.getElementById( 'progress-slider' ).clientWidth - 5 ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<img src="@/assets/logo.png" alt="logo" class="logo">
|
<img src="@/assets/logo.png" alt="logo" class="logo">
|
||||||
<div class="player-wrapper">
|
<div class="player-wrapper">
|
||||||
<Player ref="player"></Player>
|
<Player ref="player" @update="( info ) => { handleUpdates( info ) }"></Player>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pool-wrapper">
|
<div class="pool-wrapper">
|
||||||
<mediaPool @playing="( song ) => { handlePlaying( song ) }"></mediaPool>
|
<mediaPool @com="( info ) => { handleCom( info ) }" ref="pool"></mediaPool>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,17 +20,21 @@
|
|||||||
|
|
||||||
.pool-wrapper {
|
.pool-wrapper {
|
||||||
height: 84vh;
|
height: 84vh;
|
||||||
|
margin-top: 16vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-bar {
|
.top-bar {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 8;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
height: 15vh;
|
height: 15vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
border: var( --primary-color ) 2px solid;
|
border: var( --primary-color ) 2px solid;
|
||||||
|
background-color: var( --background-color );
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-wrapper {
|
.player-wrapper {
|
||||||
@@ -66,9 +70,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handlePlaying ( song ) {
|
handleCom ( data ) {
|
||||||
this.$refs.player.play( song );
|
if ( data.type === 'startPlayback' ) {
|
||||||
|
this.$refs.player.play( data.song, data.autoplay === undefined ? true : data.autoplay );
|
||||||
|
} else {
|
||||||
|
this.$refs.player.control( data.type );
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
handleUpdates ( data ) {
|
||||||
|
this.$refs.pool.update( data );
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user