funkwhale/front/src/composables/audio/player.ts

217 wiersze
5.0 KiB
TypeScript
Czysty Zwykły widok Historia

2022-10-28 07:34:24 +00:00
import { createGlobalState, tryOnMounted, useIntervalFn, useRafFn, useStorage, useTimeoutFn, whenever } from '@vueuse/core'
2022-10-28 14:31:50 +00:00
import { computed, ref, watch, watchEffect, type Ref } from 'vue'
2022-10-28 07:34:24 +00:00
import { useTracks } from '~/composables/audio/tracks'
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(() => {
2022-10-28 14:31:50 +00:00
const { currentSound } = useTracks()
2022-10-28 07:34:24 +00:00
const { playNext } = useQueue()
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) {
return sound.play()
2022-10-28 07:34:24 +00:00
}
return sound.pause()
2022-10-18 22:21:14 +00:00
})
2022-11-24 00:32:57 +00:00
// Create first track when we initialize the page
2022-10-28 07:34:24 +00:00
// 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 10:40:55 +00:00
const { trackRadioPopulating } = useQueue()
trackRadioPopulating()
trackListenSubmissions()
2022-10-28 07:34:24 +00:00
}))
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, (gain) => setGain(gain), { immediate: true })
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-28 07:34:24 +00:00
currentTime.value = sound.currentTime
}, 1000)
// Submit listens
const listenSubmitted = ref(false)
2022-10-28 10:40:55 +00:00
const trackListenSubmissions = () => {
const store = useStore()
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))
})
}
2022-10-28 07:34:24 +00:00
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
const { start: startErrorTimeout, stop: stopErrorTimeout } = useTimeoutFn(() => playNext(), 3000, { immediate: false })
watch(currentIndex, stopErrorTimeout)
whenever(errored, startErrorTimeout)
2022-10-28 07:34:24 +00:00
return {
initializeFirstTrack,
isPlaying,
volume,
mute,
looping,
LoopingMode,
toggleLooping,
duration,
currentTime,
seekBy,
seekTo,
bufferProgress,
progress,
loading,
errored
}
})