270 lines
8.0 KiB
Vue
270 lines
8.0 KiB
Vue
<template>
|
|
<div class="player__container flex flex-col">
|
|
<div class="cover-art__container flex justify-center rounded-lg">
|
|
<img :src="currentSong.cover_art_url" :alt="currentSong.album" height="64" width="64" class="shadow rounded-lg">
|
|
</div>
|
|
|
|
<div class="song-info flex flex-col items-center justify-center">
|
|
<div class="song-info_title font-bold text-2xl">{{ currentSong.title }}</div>
|
|
<div class="song-info_artist text-xl">{{ currentSong.artist }}</div>
|
|
</div>
|
|
|
|
<div class="controls">
|
|
<button type="button" @click="changeToPrevSong()">
|
|
<svg viewBox="0 0 24 24" width="28" height="28" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="">
|
|
<polygon points="19 20 9 12 19 4 19 20"></polygon>
|
|
<line x1="5" y1="19" x2="5" y2="5"></line>
|
|
</svg>
|
|
</button>
|
|
<button type="button" v-show="!isPlaying" @click="startPlaying">
|
|
<svg viewBox="0 0 24 24" width="48" height="48" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="">
|
|
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
|
</svg>
|
|
</button>
|
|
<button type="button" v-show="isPlaying" @click="pausePlaying">
|
|
<svg viewBox="0 0 24 24" width="48" height="48" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="">
|
|
<rect x="6" y="4" width="4" height="16"></rect>
|
|
<rect x="14" y="4" width="4" height="16"></rect>
|
|
</svg>
|
|
</button>
|
|
<button type="button" @click="changeToNextSong()">
|
|
<svg viewBox="0 0 24 24" width="28" height="28" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="">
|
|
<polygon points="5 4 15 12 5 20 5 4"></polygon>
|
|
<line x1="19" y1="5" x2="19" y2="19"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="progress__container flex flex-col mx-12">
|
|
<div class="flex flex-row items-center justify-between">
|
|
<div class="font-light">{{ timeElapsed }}</div>
|
|
<div class="font-light">{{ songLength }}</div>
|
|
</div>
|
|
<div class="progress-bars flex relative" ref="progressBars">
|
|
<progress class="progress-played absolute z-30 w-full" min="0" :max="duration" :value="audioPlayer.currentTime"></progress>
|
|
<progress class="progress-seeking absolute z-20 w-full" min="0" :max="duration" :value="seekingTime" ref="seekingBar"></progress>
|
|
<progress class="progress-buffered absolute z-10 w-full" min="0" :max="duration" :value="bufferedTime"></progress>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { defineComponent, ref } from "vue"
|
|
|
|
export default defineComponent({
|
|
emits: [
|
|
'startNextSong',
|
|
'startPrevSong',
|
|
],
|
|
|
|
props: {
|
|
currentSong: Object,
|
|
},
|
|
|
|
components: {},
|
|
|
|
setup(props) {
|
|
let audioPlayer = new Audio(props.currentSong.file_url)
|
|
let duration = ref(0)
|
|
let bufferedTime = ref(0)
|
|
let currentTime = ref(0)
|
|
let seekingTime = ref(0)
|
|
let canPlaying = ref(false)
|
|
let isPlaying = ref(false)
|
|
|
|
return {
|
|
audioPlayer,
|
|
duration,
|
|
bufferedTime,
|
|
currentTime,
|
|
seekingTime,
|
|
canPlaying,
|
|
isPlaying,
|
|
}
|
|
},
|
|
|
|
beforeMount() {},
|
|
|
|
mounted() {
|
|
this.audioPlayer.addEventListener("loadedmetadata", event => {
|
|
this.duration = event.path[0].duration
|
|
})
|
|
|
|
this.audioPlayer.addEventListener("canplay", event => {
|
|
this.canPlay = true
|
|
})
|
|
|
|
this.audioPlayer.addEventListener("canplaythrough", event => {
|
|
this.canPlay = true
|
|
this.bufferedTime = this.duration
|
|
})
|
|
|
|
this.audioPlayer.addEventListener("loadeddata", event => {
|
|
this.bufferedTime = event.path[0].buffered.end(0)
|
|
})
|
|
|
|
this.audioPlayer.addEventListener("progress", event => {
|
|
if (this.duration > 0) {
|
|
for (let i = 0; i < this.audioPlayer.buffered.length; i++) {
|
|
if (this.audioPlayer.buffered.start(this.audioPlayer.buffered.length - 1 - i) < this.currentTime) {
|
|
console.log(this.audioPlayer.buffered.end(this.audioPlayer.buffered.length - 1 - i) / this.duration)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
this.audioPlayer.addEventListener("timeupdate", event => {
|
|
this.currentTime = event.path[0].currentTime
|
|
})
|
|
|
|
/*this.audioPlayer.addEventListener("seeking", event => {
|
|
//
|
|
})
|
|
|
|
this.audioPlayer.addEventListener("seeked", event => {
|
|
//
|
|
})*/
|
|
|
|
this.$refs.progressBars.addEventListener("mousemove", event => {
|
|
this.$refs.seekingBar.value = this.duration * (event.layerX / event.path[0].clientWidth)
|
|
})
|
|
|
|
this.$refs.progressBars.addEventListener("mousedown", event => {
|
|
let newCurrentTime = this.duration * (event.layerX / event.path[0].clientWidth)
|
|
this.pausePlaying()
|
|
this.audioPlayer.currentTime = newCurrentTime
|
|
this.currentTime = newCurrentTime
|
|
this.startPlaying()
|
|
})
|
|
|
|
//this.audioPlayer.addEventListener("complete", this.changeToNextSong())
|
|
|
|
//this.audioPlayer.addEventListener("ended", this.changeToNextSong())
|
|
},
|
|
|
|
data() {
|
|
return {}
|
|
},
|
|
|
|
computed: {
|
|
timeElapsed() {
|
|
return this.secondsToHuman(this.currentTime)
|
|
},
|
|
|
|
songLength() {
|
|
return this.secondsToHuman(this.duration)
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
startPlaying() {
|
|
this.isPlaying = true
|
|
this.audioPlayer.play()
|
|
},
|
|
|
|
pausePlaying() {
|
|
this.isPlaying = false
|
|
this.audioPlayer.pause()
|
|
},
|
|
|
|
changeToNextSong() {
|
|
console.log('Player: starting the next song...')
|
|
this.$emit('startNextSong')
|
|
},
|
|
|
|
changeToPrevSong() {
|
|
console.log('Player: starting the previous song...')
|
|
this.$emit('startPrevSong')
|
|
},
|
|
|
|
// helpers
|
|
|
|
secondsToHuman(lengthInSeconds) {
|
|
lengthInSeconds = Number.parseInt(lengthInSeconds)
|
|
let minutes = Number.parseInt(lengthInSeconds / 60)
|
|
let seconds = Number.parseInt(lengthInSeconds - Number.parseInt(minutes * 60))
|
|
if (seconds < 10) {
|
|
seconds = "0" + seconds
|
|
}
|
|
return minutes + ":" + seconds
|
|
},
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.player__container {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: repeat(4, min-content);
|
|
gap: 2em 0em;
|
|
grid-template-areas:
|
|
"."
|
|
"."
|
|
"."
|
|
".";
|
|
}
|
|
|
|
.cover-art__container img {
|
|
height: 256px;
|
|
width: 256px;
|
|
}
|
|
|
|
.controls {
|
|
align-items: center;
|
|
justify-content: center;
|
|
display: grid;
|
|
grid-template-columns: repeat(3, min-content);
|
|
grid-template-rows: 1fr;
|
|
gap: 0em 4em;
|
|
grid-template-areas: ". . .";
|
|
}
|
|
|
|
progress.progress-played,
|
|
progress.progress-seeking,
|
|
progress.progress-buffered {
|
|
height: 24px;
|
|
}
|
|
|
|
progress.progress-played::-webkit-progress-bar,
|
|
progress.progress-seeking::-webkit-progress-bar,
|
|
progress.progress-buffered::-webkit-progress-bar,
|
|
progress.progress-played::-webkit-progress-value,
|
|
progress.progress-seeking::-webkit-progress-value,
|
|
progress.progress-buffered::-webkit-progress-value {
|
|
border-radius: 0.75em;
|
|
}
|
|
|
|
progress.progress-played::-webkit-progress-bar,
|
|
progress.progress-seeking::-webkit-progress-bar,
|
|
progress.progress-buffered::-webkit-progress-bar {
|
|
padding: 0.25em;
|
|
}
|
|
|
|
progress.progress-played::-webkit-progress-bar {
|
|
background: transparent;
|
|
}
|
|
|
|
progress.progress-seeking::-webkit-progress-bar {
|
|
background: transparent;
|
|
}
|
|
|
|
progress.progress-buffered::-webkit-progress-bar {
|
|
background-color: #ffffff;
|
|
}
|
|
|
|
progress.progress-played::-webkit-progress-value {
|
|
background-color: #00ff00;
|
|
min-width: 15px;
|
|
}
|
|
|
|
progress.progress-seeking::-webkit-progress-value {
|
|
background-color: rgba(255, 0, 0, 0.5);
|
|
}
|
|
|
|
progress.progress-buffered::-webkit-progress-value {
|
|
background-color: rgba(0, 128, 64, 0.25);
|
|
}
|
|
</style>
|