add media session integration
This commit is contained in:
parent
c0dfb5f853
commit
c7a3e98e91
55
src/global.d.ts
vendored
Normal file
55
src/global.d.ts
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
declare module '*.vue' {
|
||||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
|
||||
declare module "md5-es";
|
||||
|
||||
interface Navigator {
|
||||
readonly mediaSession?: MediaSession;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
MediaSession?: MediaSession;
|
||||
}
|
||||
|
||||
type MediaSessionPlaybackState = 'none' | 'paused' | 'playing';
|
||||
|
||||
type MediaSessionAction = 'play' | 'pause' | 'seekbackward' | 'seekforward' | 'seekto' | 'previoustrack' | 'nexttrack' | 'skipad' | 'stop';
|
||||
|
||||
interface MediaSessionActionDetails {
|
||||
action: MediaSessionAction;
|
||||
fastSeek?: boolean;
|
||||
seekOffset?: number;
|
||||
seekTime?: number;
|
||||
}
|
||||
|
||||
interface MediaPositionState {
|
||||
duration?: number;
|
||||
playbackRate?: number;
|
||||
position?: number;
|
||||
}
|
||||
|
||||
interface MediaSession {
|
||||
playbackState: MediaSessionPlaybackState;
|
||||
metadata: MediaMetadata | null;
|
||||
setActionHandler(action: MediaSessionAction, listener: ((details: MediaSessionActionDetails) => void)): void;
|
||||
setPositionState?(arg: MediaPositionState): void;
|
||||
}
|
||||
|
||||
interface MediaImage {
|
||||
src: string;
|
||||
sizes?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface MediaMetadataInit {
|
||||
title?: string;
|
||||
artist?: string;
|
||||
album?: string;
|
||||
artwork?: MediaImage[];
|
||||
}
|
||||
|
||||
declare class MediaMetadata {
|
||||
constructor(init?: MediaMetadataInit);
|
||||
}
|
@ -97,15 +97,14 @@ export default Vue.extend({
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
...mapActions({
|
||||
playPause: "player/playPause",
|
||||
}),
|
||||
play(index: number) {
|
||||
if ((this.tracks as any)[index].id === this.playingTrackId) {
|
||||
this.$store.commit("player/playPause")
|
||||
return;
|
||||
return this.$store.dispatch("player/playPause")
|
||||
}
|
||||
this.$store.dispatch('player/play', {
|
||||
return this.$store.dispatch('player/playQueue', {
|
||||
index,
|
||||
queue: this.tracks,
|
||||
})
|
||||
|
@ -10,7 +10,7 @@ import {setupRouter} from '@/shared/router'
|
||||
import {setupStore} from '@/shared/store'
|
||||
import { API } from '@/shared/api';
|
||||
import { AuthService } from '@/auth/service';
|
||||
import { connectAudioToStore } from './player/store'
|
||||
import { setupAudio } from './player/store'
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
@ -28,7 +28,7 @@ const authService = new AuthService();
|
||||
const api = new API(authService);
|
||||
const router = setupRouter(authService);
|
||||
const store = setupStore(authService, api);
|
||||
connectAudioToStore(store);
|
||||
setupAudio(store);
|
||||
|
||||
Vue.prototype.$auth = authService;
|
||||
Vue.prototype.$api = api;
|
||||
|
@ -75,20 +75,16 @@ export default Vue.extend({
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations("player", [
|
||||
"playPause",
|
||||
]),
|
||||
...mapActions("player", [
|
||||
"playPause",
|
||||
"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
|
||||
|
||||
const value = event.offsetX / width;
|
||||
return this.$store.dispatch("player/seek", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import { mapState, mapMutations } from 'vuex';
|
||||
import { mapState, mapMutations, mapActions } from 'vuex';
|
||||
import TrackList from "@/library/TrackList.vue";
|
||||
|
||||
export default Vue.extend({
|
||||
@ -25,7 +25,6 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
...mapMutations("player", {
|
||||
play: "playQueueIndex",
|
||||
remove: "removeFromQueue",
|
||||
}),
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { Store, Module } from 'vuex'
|
||||
|
||||
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;
|
||||
}
|
||||
const mediaSession: MediaSession | undefined = navigator.mediaSession;
|
||||
|
||||
interface State {
|
||||
queue: any[];
|
||||
@ -9,13 +16,6 @@ interface State {
|
||||
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: {
|
||||
@ -27,40 +27,44 @@ export const playerModule: Module<State, any> = {
|
||||
},
|
||||
|
||||
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);
|
||||
setPlaying(state) {
|
||||
state.isPlaying = true;
|
||||
audio.src = queue[index].url;
|
||||
audio.play();
|
||||
if (mediaSession) {
|
||||
mediaSession.playbackState = "playing";
|
||||
}
|
||||
},
|
||||
playQueueIndex(state, index) {
|
||||
setPaused(state) {
|
||||
state.isPlaying = false;
|
||||
if (mediaSession) {
|
||||
mediaSession.playbackState = "paused";
|
||||
}
|
||||
},
|
||||
setPosition(state, time: number) {
|
||||
audio.currentTime = time;
|
||||
},
|
||||
setQueue(state, queue) {
|
||||
state.queue = queue;
|
||||
state.queueIndex = -1;
|
||||
localStorage.setItem("queue", JSON.stringify(queue));
|
||||
},
|
||||
setQueueIndex(state, index) {
|
||||
if (state.queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
index = Math.max(0, index);
|
||||
index = index < state.queue.length ? index : 0;
|
||||
state.isPlaying = true;
|
||||
state.queueIndex = index;
|
||||
audio.src = state.queue[index].url;
|
||||
audio.play();
|
||||
localStorage.setItem("queueIndex", index);
|
||||
const track = state.queue[index];
|
||||
audio.src = track.url;
|
||||
if (mediaSession) {
|
||||
mediaSession.metadata = new MediaMetadata({
|
||||
title: track.title,
|
||||
artist: track.artist,
|
||||
album: track.album,
|
||||
artwork: track.image ? [{ src: track.image}] : undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
removeFromQueue(state, index) {
|
||||
state.queue.splice(index, 1);
|
||||
@ -68,23 +72,47 @@ export const playerModule: Module<State, any> = {
|
||||
state.queueIndex--;
|
||||
}
|
||||
},
|
||||
setProgress(state: State, value: any) {
|
||||
setProgress(state, value: any) {
|
||||
state.currentTime = value;
|
||||
},
|
||||
setDuration(state: State, value: any) {
|
||||
setDuration(state, value: any) {
|
||||
state.duration = value;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
play({ commit }, { queue, index }) {
|
||||
commit('setQueueAndPlay', { index, queue });
|
||||
async playQueue({ commit }, { queue, index }) {
|
||||
commit("setQueue", queue);
|
||||
commit("setQueueIndex", index);
|
||||
commit("setPlaying");
|
||||
await audio.play();
|
||||
},
|
||||
playNext({ commit, state }) {
|
||||
commit("playQueueIndex", state.queueIndex + 1);
|
||||
async play({ commit }) {
|
||||
commit("setPlaying");
|
||||
await audio.play();
|
||||
},
|
||||
playPrevious({ commit, state }) {
|
||||
commit("playQueueIndex", state.queueIndex - 1);
|
||||
pause({ commit }) {
|
||||
audio.pause();
|
||||
commit("setPaused");
|
||||
},
|
||||
async playNext({ commit, state }) {
|
||||
commit("setQueueIndex", state.queueIndex + 1);
|
||||
commit("setPlaying");
|
||||
await audio.play();
|
||||
},
|
||||
async playPrevious({ commit, state }) {
|
||||
commit("setQueueIndex", state.queueIndex - 1);
|
||||
commit("setPlaying");
|
||||
await audio.play();
|
||||
},
|
||||
playPause({ state, dispatch }) {
|
||||
if (state.isPlaying) {
|
||||
return dispatch("pause");
|
||||
}
|
||||
return dispatch("play");
|
||||
},
|
||||
seek({ commit, state }, value) {
|
||||
commit("setPosition", state.duration * value);
|
||||
},
|
||||
},
|
||||
|
||||
@ -114,7 +142,7 @@ export const playerModule: Module<State, any> = {
|
||||
};
|
||||
|
||||
|
||||
export function connectAudioToStore(store: Store<any>) {
|
||||
export function setupAudio(store: Store<any>) {
|
||||
audio.ontimeupdate = (event) => {
|
||||
store.commit("player/setProgress", audio.currentTime)
|
||||
};
|
||||
@ -125,6 +153,49 @@ export function connectAudioToStore(store: Store<any>) {
|
||||
store.dispatch("player/playNext");
|
||||
}
|
||||
audio.onerror = (event) => {
|
||||
store.commit("player/setPaused");
|
||||
store.commit("setError", audio.error);
|
||||
}
|
||||
audio.onwaiting = (event) => {
|
||||
console.log('audio is waiting for more data.');
|
||||
};
|
||||
|
||||
if (mediaSession) {
|
||||
mediaSession.setActionHandler('play', () => {
|
||||
store.dispatch("player/play");
|
||||
});
|
||||
mediaSession.setActionHandler('pause', () => {
|
||||
store.dispatch("player/pause");
|
||||
});
|
||||
mediaSession.setActionHandler('nexttrack', () => {
|
||||
store.dispatch("player/playNext");
|
||||
});
|
||||
mediaSession.setActionHandler('previoustrack', () => {
|
||||
store.dispatch("player/playPrevious");
|
||||
});
|
||||
mediaSession.setActionHandler('stop', () => {
|
||||
store.dispatch("player/pause");
|
||||
});
|
||||
mediaSession.setActionHandler("seekto", (details) => {
|
||||
store.commit("player/setPosition", details.seekTime);
|
||||
});
|
||||
mediaSession.setActionHandler("seekforward", (details) => {
|
||||
const offset = details.seekOffset || 10;
|
||||
store.commit("player/setPosition", Math.min(audio.currentTime + offset, audio.duration));
|
||||
});
|
||||
mediaSession.setActionHandler("seekbackward", (details) => {
|
||||
const offset = details.seekOffset || 10;
|
||||
store.commit("player/setPosition", Math.max(audio.currentTime - offset, 0));
|
||||
});
|
||||
// FIXME
|
||||
// function updatePositionState() {
|
||||
// if (mediaSession && mediaSession.setPositionState) {
|
||||
// mediaSession.setPositionState({
|
||||
// duration: audio.duration || 0,
|
||||
// playbackRate: audio.playbackRate,
|
||||
// position: audio.currentTime,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
6
src/shims-vue.d.ts
vendored
6
src/shims-vue.d.ts
vendored
@ -1,6 +0,0 @@
|
||||
declare module '*.vue' {
|
||||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
|
||||
declare module "md5-es";
|
Loading…
x
Reference in New Issue
Block a user