import { registerPlugin, WebPlugin } from '@capacitor/core'; 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.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 } // PluginMethod async prepareLibraryItem({ libraryItemId, episodeId, playWhenReady }) { console.log('[AbsAudioPlayer] Prepare library item', libraryItemId) if (libraryItemId.startsWith('local_')) { // Fetch Local - local not implemented on web } else { var route = !episodeId ? `/api/items/${libraryItemId}/play` : `/api/items/${libraryItemId}/play/${episodeId}` var playbackSession = await $axios.$post(route, { mediaPlayer: 'html5-mobile', forceDirectPlay: true }) if (playbackSession) { this.setAudioPlayer(playbackSession, true) } } 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 }) { if (this.player) this.player.playbackRate = value } // PluginMethod async getCurrentTime() { return { value: this.overallCurrentTime, bufferedTime: 0 } } 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) { var currentTime = this.player ? this.player.currentTime || 0 : 0 this.notifyListeners('onMetadata', { duration: this.totalDuration, currentTime, 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() } 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 }