client/src/components/Player.vue

434 lines
11 KiB
Vue
Raw Normal View History

2023-02-08 12:37:55 +01:00
<template>
<div
id="player"
class="flex-column"
v-show="selectedTrack._id || selectedRadio._id"
>
<input
type="range"
id="slider"
min="0"
max="100"
step="0.1"
v-model="selectedTrack.percent"
@change="slideChanged"
/>
<div id="playerBar" class="flex-row">
<div class="flex-row grow">
<img
class="cover pointer"
:src="
selectedTrack.parent.covers.cover64 ||
selectedRadio.cover64 ||
'/static/icons/dummy/album.svg'
"
:title="selectedTrack.parent.title"
@click="gotoContainer"
/>
<div v-if="selectedTrack._id" class="flex-column">
<b>{{ selectedTrack.title }}</b>
from
<b>{{ selectedTrack.parent.title }}</b>
</div>
</div>
<div id="playerControls" class="flex-row center">
<button
@click="switchShuffle"
title="Shuffle mode"
v-if="selectedTrack._id"
>
<img
src="static/icons/media-shuffle-dark.svg"
v-show="$store.getters['player/shuffle']"
class="small"
/>
<img
src="static/icons/media-consecutive-dark.svg"
v-show="$store.getters['player/shuffle'] == false"
class="small"
/>
</button>
<button @click="prevTrack" title="Back" v-if="selectedTrack._id">
<awesome-icon icon="backward" />
</button>
<button @click="togglePlaying" :title="audio.paused ? 'Play' : 'Pause'">
<awesome-icon icon="play" size="2x" v-if="audio.paused" />
<awesome-icon icon="pause" size="2x" v-else />
</button>
<button @click="nextTrack" title="Forward" v-if="selectedTrack._id">
<awesome-icon icon="forward" />
</button>
<button
@click="switchRepeatType"
title="Repeat mode"
v-if="selectedTrack._id"
>
<img
src="static/icons/media-repeat-dark.svg"
class="small"
v-show="$store.getters['player/repeatType'] == 'all'"
/>
<img
src="static/icons/media-repeat-song-dark.svg"
class="small"
v-show="$store.getters['player/repeatType'] == 'one'"
/>
<img
src="static/icons/media-no-repeat-dark.svg"
class="small"
v-show="$store.getters['player/repeatType'] == 'none'"
/>
</button>
</div>
<div
class="flex-row ma-right hideOnMobilePortrait grow right"
v-show="selectedTrack.title"
>
{{ formatedP }}&nbsp;|&nbsp;{{ formatedD }}
</div>
</div>
<audio
preload="auto"
ref="audioControl"
type="audio/mpeg"
@ended="nextTrack"
@canplay="play"
@playing="playing"
@durationchange="durationChanged"
@timeupdate="timeUpdate"
src
></audio>
</div>
</template>
<script>
export default {
name: "PlayerControl",
data() {
return {
audio: {},
duration: 0,
progress: 0,
interval: 0,
preConvert: false,
};
},
mounted() {
this.$nextTick(() => {
this.audio = this.$refs.audioControl;
});
this.setMediaSession();
},
methods: {
play() {
if (this.audio.paused) {
this.audio.play();
}
},
durationChanged() {
this.duration = this.audio.duration;
},
playing() {
window.clearInterval(this.interval);
this.interval = setInterval(() => {
this.progress = this.audio.currentTime;
this.selectedTrack.percent = (100 / this.duration) * this.progress;
}, 500);
},
audioReset() {
this.audio.pause();
this.audio.src = "";
this.$store.commit("tracks/resetSelectedTrack");
},
slideChanged() {
this.audio.pause();
this.$store.dispatch("tracks/skip");
},
skipToPercent(percent) {
let was_paused = this.audio.paused;
this.audio.pause();
let currentTime = Math.floor((this.duration * percent) / 100);
this.audio.currentTime = currentTime;
this.progress = currentTime;
if (!was_paused) {
this.audio.play();
}
},
playRadio(radio) {
this.$store.commit("tracks/resetSelectedTrack");
this.audio.pause();
this.audio.src = radio.url;
let item = {
id: this.selectedRadio._id,
cover128: this.selectedRadio.cover128,
type: "radio",
};
this.$store.dispatch("user/saveHistoryItem", item);
},
playTrack(track) {
this.preConvert = false;
this.$store.commit("radios/resetSelectedRadio");
let url =
this.$store.getters.server +
"/api/tracks/" +
track._id +
"/stream/" +
this.audioBpm;
this.audio.pause();
this.audio.src = url;
2023-02-16 23:59:01 +01:00
this.pushHistoryItem();
},
pushHistoryItem() {
if (!this.currentUser._id) {
return;
}
2023-02-08 12:37:55 +01:00
let item = {
id: this.currentTrackParent._id,
type: this.currentTrackParentType,
};
if (item.type == "album") {
item.title = this.currentTrackParent.title;
item.covers = { cover128: this.currentTrackParent.covers.cover128 };
} else {
item.name = this.currentTrackParent.name;
item.covers = { cover256: this.currentTrackParent.covers.cover256 };
}
this.$store.dispatch("user/saveHistoryItem", item);
item = {
id: this.selectedTrack._id,
type: "track",
title: this.selectedTrack.title,
covers: { cover32: this.selectedTrack.parent.covers.cover32 },
parent: {
_id: this.selectedTrack.parent._id,
title: this.selectedTrack.parent.title,
},
};
this.$store.dispatch("user/saveHistoryItem", item);
},
nextTrack() {
if (this.$store.getters["player/repeatType"] == "one") {
this.skipToPercent(0);
this.audio.play();
} else {
this.$store.dispatch("tracks/playNextTo", this.selectedTrack);
}
},
prevTrack() {
this.$store.dispatch("tracks/playPrevTo", this.selectedTrack);
},
togglePlaying() {
if (!this.audio) {
return;
}
if (!this.audio.paused) {
this.audio.pause();
} else if (this.audio.src != "") {
this.audio.play();
}
},
reset() {
window.clearInterval(this.interval);
if (!this.audio.paused) {
this.audio.pause();
}
this.audio.src = "";
},
setMediaSession() {
if ("mediaSession" in navigator) {
let me = this;
navigator.mediaSession.setActionHandler("play", function () {
me.togglePlaying();
});
navigator.mediaSession.setActionHandler("pause", function () {
me.togglePlaying();
});
navigator.mediaSession.setActionHandler("seekto", function (details) {
if (details.fastSeek && "fastSeek" in me.audio) {
me.audio.fastSeek(details.seekTime);
return;
}
me.audio.currentTime = details.seekTime;
});
navigator.mediaSession.setActionHandler("previoustrack", function () {
me.prevTrack();
});
navigator.mediaSession.setActionHandler("nexttrack", function () {
me.nextTrack();
});
}
},
gotoContainer() {
2023-02-16 23:59:01 +01:00
if (this.currentUser._id) {
switch (this.selectedTrack.parentType) {
case "album":
this.$router.push("/albums?id=" + this.selectedTrack.parent._id);
break;
case "artist":
this.$router.push(
"/artists?id=" + this.selectedTrack.parent.parent._id
);
break;
}
2023-02-08 12:37:55 +01:00
}
},
switchShuffle() {
this.$store.dispatch("player/toggleShuffleMode");
this.saveUserSettings();
},
switchRepeatType() {
this.$store.dispatch("player/switchPlayerRepeatMode");
2023-02-16 23:59:01 +01:00
if (!this.currentUser._id) {
return;
}
2023-02-08 12:37:55 +01:00
this.saveUserSettings();
},
saveUserSettings() {
2023-02-16 23:59:01 +01:00
if (!this.currentUser._id) {
return;
}
2023-02-08 12:37:55 +01:00
this.$store.dispatch("user/savePlayerSettings");
},
timeUpdate(event) {
let percent = (event.target.currentTime / event.target.duration) * 100;
if (percent > 10 && !this.preConvert) {
this.preConvert = true;
this.$store.dispatch("tracks/convertNextTo", {
track: this.selectedTrack,
rate: this.audioBpm,
});
}
},
},
computed: {
selectedTrack() {
return this.$store.getters["tracks/selectedTrack"];
},
skipTo() {
return this.selectedTrack.skipTo;
},
selectedRadio() {
return this.$store.getters["radios/selectedRadio"];
},
currentTrackParent() {
return this.$store.getters["tracks/selectedTrackContainer"];
},
currentTrackParentType() {
let type = "album";
if (
this.selectedTrack.parent.parent &&
this.selectedTrack.parent.parent.tracks
) {
type = "artist";
}
return type;
},
2023-02-16 23:59:01 +01:00
currentUser() {
return this.$store.getters["user/user"];
},
2023-02-08 12:37:55 +01:00
formatedD() {
let m = Math.floor(this.duration / 60);
let s = Math.floor(this.duration - m * 60);
return (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
},
formatedP() {
let m = Math.floor(this.progress / 60);
let s = Math.floor(this.progress - m * 60);
return (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
},
requestReplayTrack() {
return this.$store.getters["player/requestReplayTrack"];
},
audioBpm() {
return this.$store.getters["user/settings"].desktop_bpm || "128";
},
},
watch: {
requestReplayTrack() {
this.skipToPercent(0);
},
skipTo(newVal) {
if (newVal) {
this.skipToPercent(newVal);
}
},
selectedRadio(newVal) {
if (newVal._id) {
this.playRadio(newVal);
} else {
this.reset();
}
},
selectedTrack(newVal) {
if (newVal._id) {
this.playTrack(newVal);
} else {
this.reset();
}
},
},
};
</script>
<style scoped>
#player {
background-color: var(--nav);
max-height: 60px;
height: 60px;
z-index: 1001;
box-shadow: 0 0px 4px var(--shadow);
}
#player .cover {
width: 52px;
margin-right: 4px;
}
#playerBar {
overflow: hidden;
max-height: 52px;
}
#playerBar > div {
align-items: center;
}
#playerControls button {
display: flex;
padding: 4px 12px;
align-self: stretch;
border: none;
}
@media (max-width: 480px) {
#player #playerControls {
justify-content: end;
}
}
input[type="range"] {
border: none;
overflow: hidden;
-webkit-appearance: none;
background-color: var(--secondary);
height: 8px;
padding: 0px;
}
input[type="range"]::-webkit-slider-runnable-track {
height: 8px;
-webkit-appearance: none;
color: var(--primary);
}
input[type="range"]::-webkit-slider-thumb {
width: 10px;
-webkit-appearance: none;
height: 8px;
cursor: ew-resize;
background: var(--dark);
box-shadow: -4000px 0 0 4000px var(--primary);
}
</style>