mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-01 17:44:45 +02:00
270 lines
7.6 KiB
JavaScript
270 lines
7.6 KiB
JavaScript
import { registerPlugin, WebPlugin } from '@capacitor/core'
|
|
import { nanoid } from 'nanoid'
|
|
const { PlayerState } = require('../constants')
|
|
|
|
var $axios = null
|
|
var vuexStore = null
|
|
|
|
class AbsAudioPlayerWeb extends WebPlugin {
|
|
constructor() {
|
|
super()
|
|
|
|
this.player = null
|
|
this.playWhenReady = false
|
|
this.playableMimeTypes = {}
|
|
this.playbackSession = null
|
|
this.playbackRate = 1
|
|
this.audioTracks = []
|
|
this.startTime = 0
|
|
this.trackStartTime = 0
|
|
}
|
|
|
|
// Use startTime to find current track index
|
|
get currentTrackIndex() {
|
|
return Math.max(0, this.audioTracks.findIndex(t => Math.floor(t.startOffset) <= this.startTime && Math.floor(t.startOffset + t.duration) > this.startTime))
|
|
}
|
|
get currentTrack() {
|
|
return this.audioTracks[this.currentTrackIndex]
|
|
}
|
|
get playerCurrentTime() {
|
|
return this.player ? this.player.currentTime : 0
|
|
}
|
|
get currentTrackStartOffset() {
|
|
return this.currentTrack ? this.currentTrack.startOffset : 0
|
|
}
|
|
get overallCurrentTime() {
|
|
return this.currentTrackStartOffset + this.playerCurrentTime
|
|
}
|
|
get totalDuration() {
|
|
var total = 0
|
|
this.audioTracks.forEach(at => total += at.duration)
|
|
return total
|
|
}
|
|
get playerPlaying() {
|
|
return this.player && !this.player.paused
|
|
}
|
|
|
|
getDeviceId() {
|
|
let deviceId = localStorage.getItem('absDeviceId')
|
|
if (!deviceId) {
|
|
deviceId = nanoid()
|
|
localStorage.setItem('absDeviceId', deviceId)
|
|
}
|
|
return deviceId
|
|
}
|
|
|
|
// PluginMethod
|
|
async prepareLibraryItem({ libraryItemId, episodeId, playWhenReady, startTime, playbackRate }) {
|
|
console.log('[AbsAudioPlayer] Prepare library item', libraryItemId)
|
|
|
|
if (!isNaN(playbackRate) && playbackRate) this.playbackRate = playbackRate
|
|
|
|
if (libraryItemId.startsWith('local_')) {
|
|
// Fetch Local - local not implemented on web
|
|
} else {
|
|
const route = !episodeId ? `/api/items/${libraryItemId}/play` : `/api/items/${libraryItemId}/play/${episodeId}`
|
|
const deviceInfo = {
|
|
deviceId: this.getDeviceId()
|
|
}
|
|
const playbackSession = await $axios.$post(route, { deviceInfo, mediaPlayer: 'html5-mobile', forceDirectPlay: true })
|
|
if (playbackSession) {
|
|
if (startTime !== undefined && startTime !== null) playbackSession.currentTime = startTime
|
|
this.setAudioPlayer(playbackSession, playWhenReady)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// PluginMethod
|
|
async playPause() {
|
|
if (!this.player) return
|
|
if (this.player.ended) {
|
|
this.startTime = 0
|
|
this.playWhenReady = true
|
|
this.loadCurrentTrack()
|
|
return
|
|
}
|
|
|
|
if (this.player.paused) this.player.play()
|
|
else this.player.pause()
|
|
return {
|
|
playing: !this.player.paused
|
|
}
|
|
}
|
|
|
|
// PluginMethod
|
|
playPlayer() {
|
|
if (this.player) this.player.play()
|
|
}
|
|
|
|
// PluginMethod
|
|
pausePlayer() {
|
|
if (this.player) this.player.pause()
|
|
}
|
|
|
|
// PluginMethod
|
|
async closePlayback() {
|
|
this.playbackSession = null
|
|
this.audioTracks = []
|
|
this.playWhenReady = false
|
|
if (this.player) {
|
|
this.player.remove()
|
|
this.player = null
|
|
}
|
|
this.notifyListeners('onClosePlayback')
|
|
}
|
|
|
|
// PluginMethod
|
|
seek({ value }) {
|
|
this.startTime = value
|
|
this.playWhenReady = this.playerPlaying
|
|
this.loadCurrentTrack()
|
|
}
|
|
|
|
// PluginMethod
|
|
seekForward({ value }) {
|
|
this.startTime = Math.min(this.overallCurrentTime + value, this.totalDuration)
|
|
this.playWhenReady = this.playerPlaying
|
|
this.loadCurrentTrack()
|
|
}
|
|
|
|
// PluginMethod
|
|
seekBackward({ value }) {
|
|
this.startTime = Math.max(0, this.overallCurrentTime - value)
|
|
this.playWhenReady = this.playerPlaying
|
|
this.loadCurrentTrack()
|
|
}
|
|
|
|
// PluginMethod
|
|
setPlaybackSpeed({ value }) {
|
|
this.playbackRate = value
|
|
if (this.player) this.player.playbackRate = value
|
|
}
|
|
|
|
// PluginMethod
|
|
setChapterTrack({ enabled }) {
|
|
this.useChapterTrack = enabled
|
|
}
|
|
|
|
// PluginMethod
|
|
async getCurrentTime() {
|
|
return {
|
|
value: this.overallCurrentTime,
|
|
bufferedTime: 0
|
|
}
|
|
}
|
|
|
|
// PluginMethod
|
|
async getIsCastAvailable() {
|
|
return false
|
|
}
|
|
|
|
initializePlayer() {
|
|
if (document.getElementById('audio-player')) {
|
|
document.getElementById('audio-player').remove()
|
|
}
|
|
var audioEl = document.createElement('audio')
|
|
audioEl.id = 'audio-player'
|
|
audioEl.style.display = 'none'
|
|
document.body.appendChild(audioEl)
|
|
this.player = audioEl
|
|
|
|
this.player.addEventListener('play', this.evtPlay.bind(this))
|
|
this.player.addEventListener('pause', this.evtPause.bind(this))
|
|
this.player.addEventListener('progress', this.evtProgress.bind(this))
|
|
this.player.addEventListener('ended', this.evtEnded.bind(this))
|
|
this.player.addEventListener('error', this.evtError.bind(this))
|
|
this.player.addEventListener('loadedmetadata', this.evtLoadedMetadata.bind(this))
|
|
this.player.addEventListener('timeupdate', this.evtTimeupdate.bind(this))
|
|
|
|
var mimeTypes = ['audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/aac']
|
|
mimeTypes.forEach((mt) => {
|
|
this.playableMimeTypes[mt] = this.player.canPlayType(mt)
|
|
})
|
|
console.log(`[LocalPlayer] Supported mime types`, this.playableMimeTypes)
|
|
}
|
|
|
|
evtPlay() {
|
|
this.notifyListeners('onPlayingUpdate', { value: true })
|
|
}
|
|
evtPause() {
|
|
this.notifyListeners('onPlayingUpdate', { value: false })
|
|
}
|
|
evtProgress() {
|
|
// var lastBufferTime = this.getLastBufferedTime()
|
|
}
|
|
evtEnded() {
|
|
if (this.currentTrackIndex < this.audioTracks.length - 1) { // Has next track
|
|
console.log(`[AbsAudioPlayer] Track ended - loading next track ${this.currentTrackIndex + 1}`)
|
|
var nextTrack = this.audioTracks[this.currentTrackIndex + 1]
|
|
this.playWhenReady = true
|
|
this.startTime = Math.floor(nextTrack.startOffset)
|
|
this.loadCurrentTrack()
|
|
} else {
|
|
console.log(`[LocalPlayer] Ended`)
|
|
this.sendPlaybackMetadata(PlayerState.ENDED)
|
|
}
|
|
}
|
|
evtError(error) {
|
|
console.error('Player error', error)
|
|
}
|
|
evtLoadedMetadata(data) {
|
|
console.log(`[AbsAudioPlayer] Loaded metadata`, data)
|
|
if (!this.player) {
|
|
console.error('[AbsAudioPlayer] evtLoadedMetadata player not set')
|
|
return
|
|
}
|
|
this.player.currentTime = this.trackStartTime
|
|
|
|
this.sendPlaybackMetadata(PlayerState.READY)
|
|
if (this.playWhenReady) {
|
|
this.player.play()
|
|
}
|
|
}
|
|
evtTimeupdate() { }
|
|
|
|
sendPlaybackMetadata(playerState) {
|
|
this.notifyListeners('onMetadata', {
|
|
duration: this.totalDuration,
|
|
currentTime: this.overallCurrentTime,
|
|
playerState
|
|
})
|
|
}
|
|
|
|
loadCurrentTrack() {
|
|
if (!this.currentTrack) return
|
|
// When direct play track is loaded current time needs to be set
|
|
this.trackStartTime = Math.max(0, this.startTime - (this.currentTrack.startOffset || 0))
|
|
this.player.src = `${vuexStore.getters['user/getServerAddress']}${this.currentTrack.contentUrl}?token=${vuexStore.getters['user/getToken']}`
|
|
console.log(`[AbsAudioPlayer] Loading track src ${this.player.src}`)
|
|
this.player.load()
|
|
this.player.playbackRate = this.playbackRate
|
|
}
|
|
|
|
setAudioPlayer(playbackSession, playWhenReady = false) {
|
|
if (!this.player) {
|
|
this.initializePlayer()
|
|
}
|
|
|
|
// Notify client playback session set
|
|
this.notifyListeners('onPlaybackSession', playbackSession)
|
|
|
|
this.playbackSession = playbackSession
|
|
this.playWhenReady = playWhenReady
|
|
this.audioTracks = playbackSession.audioTracks || []
|
|
this.startTime = playbackSession.currentTime
|
|
|
|
this.loadCurrentTrack()
|
|
}
|
|
}
|
|
|
|
const AbsAudioPlayer = registerPlugin('AbsAudioPlayer', {
|
|
web: () => new AbsAudioPlayerWeb()
|
|
})
|
|
|
|
export { AbsAudioPlayer }
|
|
|
|
export default ({ app, store }, inject) => {
|
|
$axios = app.$axios
|
|
vuexStore = store
|
|
}
|