Initial commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="d-flex player">
|
||||
<div v-if="track" class="d-none d-sm-block">
|
||||
<b-img :src="track.image" block width="80px" height="80px"></b-img>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<!-- Progress --->
|
||||
<div class="progress2" @click="seek">
|
||||
<b-progress :value="progress" :max="100" height="4px"></b-progress>
|
||||
</div>
|
||||
<div class="row d-flex align-items-center p-2 m-0">
|
||||
<!-- Track info --->
|
||||
<div class="col d-flex p-0 text-truncate">
|
||||
<div class="d-flex align-items-center">
|
||||
<template v-if="track">
|
||||
<div class="ml-2 align-middle">
|
||||
<div>{{ track.title }}</div>
|
||||
<div>{{ track.artist }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls--->
|
||||
<div class="col-auto p-0">
|
||||
<b-button variant="link" class="m-2"
|
||||
:disabled="!hasPrevious" @click="playPrevious">
|
||||
<Icon>mdi-skip-previous</Icon>
|
||||
</b-button>
|
||||
<b-button variant="link" size="lg" class="m-2" @click="playPause">
|
||||
<Icon>{{ isPlaying ? 'mdi-pause' : 'mdi-play' }}</Icon>
|
||||
</b-button>
|
||||
<b-button variant="link" class="m-2"
|
||||
:disabled="!hasNext" @click="playNext">
|
||||
<Icon>mdi-skip-next</Icon>
|
||||
</b-button>
|
||||
</div>
|
||||
<div class="col p-0 text-truncate">
|
||||
<div v-if="track" class="text-right">
|
||||
<span>{{ currentTime | duration }} / {{ duration | duration }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.progress2 {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import { mapMutations, mapState, mapGetters, mapActions } from 'vuex';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState("player", {
|
||||
isPlaying: (state: any) => state.isPlaying,
|
||||
currentTime: (state: any) => state.currentTime,
|
||||
duration: (state: any) => state.duration,
|
||||
}),
|
||||
...mapGetters("player", [
|
||||
"track",
|
||||
"progress",
|
||||
"hasNext",
|
||||
"hasPrevious",
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations("player", [
|
||||
"playPause",
|
||||
]),
|
||||
...mapActions("player", [
|
||||
"playNext",
|
||||
"playPrevious",
|
||||
]),
|
||||
seek(event: any) {
|
||||
if (event.target) {
|
||||
const width = event.target.clientWidth;
|
||||
const value = event.offsetX / width
|
||||
this.$store.commit("player/seek", value)
|
||||
// this.internalValue = e.offsetX / width * 100
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<TrackList :tracks="tracks">
|
||||
<template #context-menu="{index}">
|
||||
<b-dropdown-item-button @click="remove(index)">
|
||||
Remove
|
||||
</b-dropdown-item-button>
|
||||
</template>
|
||||
</TrackList>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import { mapState, mapMutations } from 'vuex';
|
||||
import TrackList from "@/library/TrackList.vue";
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
TrackList,
|
||||
},
|
||||
computed: {
|
||||
...mapState("player", {
|
||||
tracks: (state: any) => state.queue,
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations("player", {
|
||||
play: "playQueueIndex",
|
||||
remove: "removeFromQueue",
|
||||
}),
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="col" d-flex fill-height grow>
|
||||
<h1>{{ track.name }}</h1>
|
||||
<div>{{ track.artist }}</div>
|
||||
<v-card color=blue tile height="100%" width="100%"></v-card>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.on-hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.show-btns {
|
||||
color: rgba(255, 255, 255, 1) !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
album: Object
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,127 @@
|
||||
import { Store, Module } from 'vuex'
|
||||
|
||||
|
||||
interface State {
|
||||
queue: any[];
|
||||
queueIndex: number;
|
||||
isPlaying: boolean;
|
||||
duration: number; // duration of current track in seconds
|
||||
currentTime: number; // position of current track in seconds
|
||||
}
|
||||
|
||||
const audio = new Audio();
|
||||
const storedQueue = JSON.parse(localStorage.getItem("queue") || "[]");
|
||||
const storedQueueIndex = parseInt(localStorage.getItem("queueIndex") || "-1");
|
||||
if (storedQueueIndex > -1 && storedQueueIndex < storedQueue.length) {
|
||||
audio.src = storedQueue[storedQueueIndex].url;
|
||||
}
|
||||
|
||||
export const playerModule: Module<State, any> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
queue: storedQueue,
|
||||
queueIndex: storedQueueIndex,
|
||||
isPlaying: false,
|
||||
duration: 0,
|
||||
currentTime: 0,
|
||||
},
|
||||
|
||||
mutations: {
|
||||
playPause(state: State) {
|
||||
if (state.isPlaying) {
|
||||
state.isPlaying = false;
|
||||
audio.pause();
|
||||
} else {
|
||||
state.isPlaying = true;
|
||||
audio.play();
|
||||
}
|
||||
},
|
||||
seek(state, value: number) {
|
||||
console.log("seek: " + value);
|
||||
if (state.queueIndex != -1) {
|
||||
const seconds = state.duration * value;
|
||||
audio.currentTime = seconds;
|
||||
}
|
||||
},
|
||||
setQueueAndPlay(state, { queue, index }) {
|
||||
state.queue = queue;
|
||||
state.queueIndex = index;
|
||||
localStorage.setItem("queue", JSON.stringify(queue));
|
||||
localStorage.setItem("queueIndex", index);
|
||||
state.isPlaying = true;
|
||||
audio.src = queue[index].url;
|
||||
audio.play();
|
||||
},
|
||||
playQueueIndex(state, index) {
|
||||
if (state.queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
index = index < state.queue.length ? index : 0;
|
||||
state.isPlaying = true;
|
||||
state.queueIndex = index;
|
||||
audio.src = state.queue[index].url;
|
||||
audio.play();
|
||||
},
|
||||
removeFromQueue(state, index) {
|
||||
state.queue.splice(index, 1);
|
||||
if (index < state.queueIndex) {
|
||||
state.queueIndex--;
|
||||
}
|
||||
},
|
||||
setProgress(state: State, value: any) {
|
||||
state.currentTime = value;
|
||||
},
|
||||
setDuration(state: State, value: any) {
|
||||
state.duration = value;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
play({ commit }, { queue, index }) {
|
||||
commit('setQueueAndPlay', { index, queue });
|
||||
},
|
||||
playNext({ commit, state }) {
|
||||
commit("playQueueIndex", state.queueIndex + 1);
|
||||
},
|
||||
playPrevious({ commit, state }) {
|
||||
commit("playQueueIndex", state.queueIndex - 1);
|
||||
},
|
||||
},
|
||||
|
||||
getters: {
|
||||
track(state) {
|
||||
if (state.queueIndex != -1) {
|
||||
return state.queue[state.queueIndex];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
trackId(state, getters): number {
|
||||
return getters.track ? getters.track.id : -1;
|
||||
},
|
||||
progress(state) {
|
||||
if (state.currentTime > -1 && state.duration > 0) {
|
||||
return (state.currentTime / state.duration) * 100;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
hasNext(state) {
|
||||
return state.queueIndex < state.queue.length - 1;
|
||||
},
|
||||
hasPrevious(state) {
|
||||
return state.queueIndex > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export function connectAudioToStore(store: Store<any>) {
|
||||
audio.ontimeupdate = (event) => {
|
||||
store.commit("player/setProgress", audio.currentTime)
|
||||
};
|
||||
audio.ondurationchange = (event) => {
|
||||
store.commit("player/setDuration", audio.duration);
|
||||
}
|
||||
audio.onended = (event) => {
|
||||
store.dispatch("player/playNext");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user