2022-10-28 07:34:24 +00:00
|
|
|
import { createGlobalState, tryOnMounted, useIntervalFn, useRafFn, useStorage, useTimeoutFn, whenever } from '@vueuse/core'
|
|
|
|
import { useTracks } from '~/composables/audio/tracks'
|
2022-10-18 21:48:47 +00:00
|
|
|
import { computed, ref, watch, watchEffect, type Ref } from 'vue'
|
2022-10-19 11:00:44 +00:00
|
|
|
import { setGain } from './audio-api'
|
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
import { useQueue, currentIndex, currentTrack } from './queue'
|
|
|
|
import { useStore } from '~/store'
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
import axios from 'axios'
|
2022-10-18 21:48:47 +00:00
|
|
|
|
|
|
|
// Looping
|
|
|
|
export enum LoopingMode {
|
|
|
|
None,
|
|
|
|
LoopTrack,
|
|
|
|
LoopQueue
|
|
|
|
}
|
|
|
|
|
|
|
|
const MODE_MAX = 1 + Math.max(...Object.values(LoopingMode).filter(mode => typeof mode === 'number') as number[])
|
|
|
|
|
|
|
|
export const looping: Ref<number> = useStorage('player:looping', LoopingMode.None)
|
|
|
|
export const toggleLooping = () => {
|
|
|
|
looping.value += 1
|
|
|
|
looping.value %= MODE_MAX
|
|
|
|
}
|
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// Is playing
|
|
|
|
export const isPlaying = ref(false)
|
|
|
|
|
|
|
|
// Use Player
|
|
|
|
export const usePlayer = createGlobalState(() => {
|
|
|
|
const { currentSound, createTrack } = useTracks()
|
|
|
|
const { playNext } = useQueue()
|
|
|
|
const store = useStore()
|
2022-10-18 22:21:14 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
watchEffect(() => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) return
|
|
|
|
|
|
|
|
if (isPlaying.value) {
|
|
|
|
sound.play()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sound.pause()
|
2022-10-18 22:21:14 +00:00
|
|
|
})
|
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// Create first track when we initalize the page
|
|
|
|
// NOTE: We want to have it called only once, hence we're using createGlobalState
|
|
|
|
const initializeFirstTrack = createGlobalState(() => tryOnMounted(() => {
|
|
|
|
const { initialize } = useTracks()
|
|
|
|
initialize()
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
createTrack(currentIndex.value)
|
|
|
|
}))
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// Volume
|
|
|
|
const lastVolume = useStorage('player:last-volume', 0.7)
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
const volume: Ref<number> = useStorage('player:volume', 0.7)
|
|
|
|
watch(volume, (to, from) => setGain(to))
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
const mute = () => {
|
|
|
|
if (volume.value > 0) {
|
|
|
|
lastVolume.value = volume.value
|
|
|
|
volume.value = 0
|
|
|
|
return
|
|
|
|
}
|
2022-10-19 11:00:44 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
if (lastVolume.value === 0) {
|
|
|
|
volume.value = 0.7
|
|
|
|
return
|
|
|
|
}
|
2022-10-19 11:00:44 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
volume.value = lastVolume.value
|
2022-10-19 11:00:44 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
watchEffect(() => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) return
|
|
|
|
sound.looping = looping.value === LoopingMode.LoopTrack
|
|
|
|
})
|
2022-10-19 11:00:44 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
watch(currentSound, sound => {
|
|
|
|
sound?.onSoundLoop(() => {
|
|
|
|
currentTime.value = 0
|
|
|
|
})
|
|
|
|
})
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// Duration
|
|
|
|
const duration = ref(0)
|
|
|
|
watchEffect(() => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (sound?.isLoaded.value === true) {
|
|
|
|
duration.value = sound.duration ?? 0
|
|
|
|
currentTime.value = sound.currentTime
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
duration.value = 0
|
|
|
|
})
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// Current time
|
|
|
|
const currentTime = ref(0)
|
|
|
|
useIntervalFn(() => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) {
|
|
|
|
currentTime.value = 0
|
|
|
|
return
|
|
|
|
}
|
2022-10-07 15:22:22 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
currentTime.value = sound.currentTime
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
// Submit listens
|
|
|
|
const listenSubmitted = ref(false)
|
|
|
|
whenever(listenSubmitted, async () => {
|
|
|
|
console.log('Listening submitted!')
|
|
|
|
if (!store.state.auth.authenticated) return
|
|
|
|
if (!currentTrack.value) return
|
|
|
|
|
|
|
|
await axios.post('history/listenings/', { track: currentTrack.value.id })
|
|
|
|
.catch((error) => console.error('Could not record track in history', error))
|
|
|
|
})
|
|
|
|
|
|
|
|
watch(currentTime, (time) => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) {
|
|
|
|
listenSubmitted.value = false
|
|
|
|
return
|
|
|
|
}
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html?highlight=half#post--1-submit-listens
|
|
|
|
listenSubmitted.value = time > Math.min(sound.duration / 2, 4 * 60)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Seeking
|
|
|
|
const seekBy = async (seconds: number) => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) return
|
|
|
|
|
|
|
|
await sound.seekBy(seconds)
|
|
|
|
currentTime.value = sound.currentTime
|
2022-10-18 21:48:47 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
const seekTo = async (seconds: number) => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) return
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
await sound.seekTo(seconds)
|
|
|
|
currentTime.value = sound.currentTime
|
2022-10-18 21:48:47 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// Buffer progress
|
|
|
|
const bufferProgress = ref(0)
|
|
|
|
useIntervalFn(() => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) {
|
|
|
|
bufferProgress.value = 0
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
bufferProgress.value = sound.buffered / sound.duration * 100
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
// Progress
|
|
|
|
const progress = ref(0)
|
|
|
|
useRafFn(() => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) {
|
|
|
|
progress.value = 0
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
progress.value = sound.currentTime / sound.duration * 100
|
|
|
|
})
|
2022-10-18 21:48:47 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// Loading
|
|
|
|
const loading = computed(() => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) return false
|
|
|
|
return !sound.isLoaded.value
|
|
|
|
})
|
2022-10-25 19:07:36 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
// Errored
|
|
|
|
const errored = computed(() => {
|
|
|
|
const sound = currentSound.value
|
|
|
|
if (!sound) return false
|
|
|
|
return sound.isErrored.value
|
|
|
|
})
|
2022-10-25 19:07:36 +00:00
|
|
|
|
2022-10-28 07:34:24 +00:00
|
|
|
const { start, stop } = useTimeoutFn(() => playNext(), 3000, { immediate: false })
|
|
|
|
watch(currentIndex, stop)
|
|
|
|
whenever(errored, start)
|
|
|
|
|
|
|
|
return {
|
|
|
|
initializeFirstTrack,
|
|
|
|
isPlaying,
|
|
|
|
volume,
|
|
|
|
mute,
|
|
|
|
looping,
|
|
|
|
LoopingMode,
|
|
|
|
toggleLooping,
|
|
|
|
duration,
|
|
|
|
currentTime,
|
|
|
|
seekBy,
|
|
|
|
seekTo,
|
|
|
|
bufferProgress,
|
|
|
|
progress,
|
|
|
|
loading,
|
|
|
|
errored
|
|
|
|
}
|
|
|
|
})
|