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>