move
This commit is contained in:
416
src/components/Player.vue
Normal file
416
src/components/Player.vue
Normal file
@@ -0,0 +1,416 @@
|
||||
<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 }} | {{ 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;
|
||||
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
},
|
||||
switchShuffle() {
|
||||
this.$store.dispatch("player/toggleShuffleMode");
|
||||
this.saveUserSettings();
|
||||
},
|
||||
switchRepeatType() {
|
||||
this.$store.dispatch("player/switchPlayerRepeatMode");
|
||||
this.saveUserSettings();
|
||||
},
|
||||
saveUserSettings() {
|
||||
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;
|
||||
},
|
||||
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>
|
||||
Reference in New Issue
Block a user