mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-05 19:44:49 +02:00
Updates to audio player plugin to method naming and using seconds for time values. Adding web audio player to run in browser.
This commit is contained in:
parent
c94e57f55e
commit
6e8d84496b
10 changed files with 367 additions and 119 deletions
|
@ -0,0 +1,11 @@
|
|||
package com.audiobookshelf.app.data
|
||||
|
||||
enum class PlayerState {
|
||||
IDLE, BUFFERING, READY, ENDED
|
||||
}
|
||||
|
||||
data class PlaybackMetadata(
|
||||
val duration:Double,
|
||||
val currentTime:Double,
|
||||
val playerState:PlayerState
|
||||
)
|
|
@ -141,7 +141,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
|
|||
playerNotificationService.seekBackward(seekAmount)
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_STOP -> {
|
||||
playerNotificationService.terminateStream()
|
||||
playerNotificationService.closePlayback()
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
||||
if (playerNotificationService.mPlayer.isPlaying) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.audiobookshelf.app.player
|
||||
|
||||
import android.util.Log
|
||||
import com.audiobookshelf.app.data.PlayerState
|
||||
import com.google.android.exoplayer2.PlaybackException
|
||||
import com.google.android.exoplayer2.Player
|
||||
|
||||
|
@ -32,22 +33,21 @@ class PlayerListener(var playerNotificationService:PlayerNotificationService) :
|
|||
Log.d(tag, "STATE_READY : " + playerNotificationService.mPlayer.duration.toString())
|
||||
|
||||
if (lastPauseTime == 0L) {
|
||||
playerNotificationService.sendClientMetadata("ready_no_sync")
|
||||
lastPauseTime = -1;
|
||||
} else playerNotificationService.sendClientMetadata("ready")
|
||||
}
|
||||
playerNotificationService.sendClientMetadata(PlayerState.READY)
|
||||
}
|
||||
if (playerNotificationService.currentPlayer.playbackState == Player.STATE_BUFFERING) {
|
||||
Log.d(tag, "STATE_BUFFERING : " + playerNotificationService.mPlayer.currentPosition.toString())
|
||||
if (lastPauseTime == 0L) playerNotificationService.sendClientMetadata("buffering_no_sync")
|
||||
else playerNotificationService.sendClientMetadata("buffering")
|
||||
playerNotificationService.sendClientMetadata(PlayerState.BUFFERING)
|
||||
}
|
||||
if (playerNotificationService.currentPlayer.playbackState == Player.STATE_ENDED) {
|
||||
Log.d(tag, "STATE_ENDED")
|
||||
playerNotificationService.sendClientMetadata("ended")
|
||||
playerNotificationService.sendClientMetadata(PlayerState.ENDED)
|
||||
}
|
||||
if (playerNotificationService.currentPlayer.playbackState == Player.STATE_IDLE) {
|
||||
Log.d(tag, "STATE_IDLE")
|
||||
playerNotificationService.sendClientMetadata("idle")
|
||||
playerNotificationService.sendClientMetadata(PlayerState.IDLE)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,9 +18,7 @@ import androidx.annotation.RequiresApi
|
|||
import androidx.core.app.NotificationCompat
|
||||
import androidx.media.MediaBrowserServiceCompat
|
||||
import androidx.media.utils.MediaConstants
|
||||
import com.audiobookshelf.app.data.LibraryItem
|
||||
import com.audiobookshelf.app.data.LocalMediaProgress
|
||||
import com.audiobookshelf.app.data.PlaybackSession
|
||||
import com.audiobookshelf.app.data.*
|
||||
import com.audiobookshelf.app.device.DeviceManager
|
||||
import com.audiobookshelf.app.media.MediaManager
|
||||
import com.audiobookshelf.app.server.ApiHandler
|
||||
|
@ -52,7 +50,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
fun onPlaybackSession(playbackSession:PlaybackSession)
|
||||
fun onPlaybackClosed()
|
||||
fun onPlayingUpdate(isPlaying: Boolean)
|
||||
fun onMetadata(metadata: JSObject)
|
||||
fun onMetadata(metadata: PlaybackMetadata)
|
||||
fun onPrepare(audiobookId: String, playWhenReady: Boolean)
|
||||
fun onSleepTimerEnded(currentPosition: Long)
|
||||
fun onSleepTimerSet(sleepTimeRemaining: Int)
|
||||
|
@ -375,6 +373,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
fun getBufferedTimeSeconds() : Double {
|
||||
return getBufferedTime() / 1000.0
|
||||
}
|
||||
|
||||
fun getDuration() : Long {
|
||||
return currentPlayer.duration
|
||||
}
|
||||
|
@ -441,20 +443,16 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
currentPlayer.setPlaybackSpeed(speed)
|
||||
}
|
||||
|
||||
fun terminateStream() {
|
||||
fun closePlayback() {
|
||||
currentPlayer.clearMediaItems()
|
||||
currentPlaybackSession = null
|
||||
clientEventEmitter?.onPlaybackClosed()
|
||||
PlayerListener.lastPauseTime = 0
|
||||
}
|
||||
|
||||
fun sendClientMetadata(stateName: String) {
|
||||
var metadata = JSObject()
|
||||
var duration = currentPlaybackSession?.getTotalDuration() ?: 0
|
||||
metadata.put("duration", duration)
|
||||
metadata.put("currentTime", getCurrentTime())
|
||||
metadata.put("stateName", stateName)
|
||||
clientEventEmitter?.onMetadata(metadata)
|
||||
fun sendClientMetadata(playerState: PlayerState) {
|
||||
var duration = currentPlaybackSession?.getTotalDuration() ?: 0.0
|
||||
clientEventEmitter?.onMetadata(PlaybackMetadata(duration, getCurrentTimeSeconds(), playerState))
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.util.Log
|
|||
import androidx.core.content.ContextCompat
|
||||
import com.audiobookshelf.app.MainActivity
|
||||
import com.audiobookshelf.app.data.LocalMediaProgress
|
||||
import com.audiobookshelf.app.data.PlaybackMetadata
|
||||
import com.audiobookshelf.app.data.PlaybackSession
|
||||
import com.audiobookshelf.app.device.DeviceManager
|
||||
import com.audiobookshelf.app.player.CastManager
|
||||
|
@ -46,8 +47,8 @@ class AbsAudioPlayer : Plugin() {
|
|||
emit("onPlayingUpdate", isPlaying)
|
||||
}
|
||||
|
||||
override fun onMetadata(metadata: JSObject) {
|
||||
notifyListeners("onMetadata", metadata)
|
||||
override fun onMetadata(metadata: PlaybackMetadata) {
|
||||
notifyListeners("onMetadata", JSObject(jacksonObjectMapper().writeValueAsString(metadata)))
|
||||
}
|
||||
|
||||
override fun onPrepare(audiobookId: String, playWhenReady: Boolean) {
|
||||
|
@ -123,8 +124,8 @@ class AbsAudioPlayer : Plugin() {
|
|||
@PluginMethod
|
||||
fun getCurrentTime(call: PluginCall) {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
var currentTime = playerNotificationService.getCurrentTime()
|
||||
var bufferedTime = playerNotificationService.getBufferedTime()
|
||||
var currentTime = playerNotificationService.getCurrentTimeSeconds()
|
||||
var bufferedTime = playerNotificationService.getBufferedTimeSeconds()
|
||||
val ret = JSObject()
|
||||
ret.put("value", currentTime)
|
||||
ret.put("bufferedTime", bufferedTime)
|
||||
|
@ -157,35 +158,35 @@ class AbsAudioPlayer : Plugin() {
|
|||
}
|
||||
|
||||
@PluginMethod
|
||||
fun seekPlayer(call: PluginCall) {
|
||||
var time:Long = call.getString("timeMs", "0")!!.toLong()
|
||||
fun seek(call: PluginCall) {
|
||||
var time:Int = call.getInt("value", 0) ?: 0 // Value in seconds
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
playerNotificationService.seekPlayer(time)
|
||||
playerNotificationService.seekPlayer(time * 1000L) // convert to ms
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun seekForward(call: PluginCall) {
|
||||
var amount:Long = call.getString("amount", "0")!!.toLong()
|
||||
var amount:Int = call.getInt("value", 0) ?: 0
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
playerNotificationService.seekForward(amount)
|
||||
playerNotificationService.seekForward(amount * 1000L) // convert to ms
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun seekBackward(call: PluginCall) {
|
||||
var amount:Long = call.getString("amount", "0")!!.toLong()
|
||||
var amount:Int = call.getInt("value", 0) ?: 0 // Value in seconds
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
playerNotificationService.seekBackward(amount)
|
||||
playerNotificationService.seekBackward(amount * 1000L) // convert to ms
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun setPlaybackSpeed(call: PluginCall) {
|
||||
var playbackSpeed:Float = call.getFloat("speed", 1.0f)!!
|
||||
var playbackSpeed:Float = call.getFloat("value", 1.0f) ?: 1.0f
|
||||
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
playerNotificationService.setPlaybackSpeed(playbackSpeed)
|
||||
|
@ -194,9 +195,9 @@ class AbsAudioPlayer : Plugin() {
|
|||
}
|
||||
|
||||
@PluginMethod
|
||||
fun terminateStream(call: PluginCall) {
|
||||
fun closePlayback(call: PluginCall) {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
playerNotificationService.terminateStream()
|
||||
playerNotificationService.closePlayback()
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<span v-show="showFullscreen" class="material-icons next-icon text-white text-opacity-75 cursor-pointer" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="jumpChapterStart">first_page</span>
|
||||
<span class="material-icons jump-icon text-white cursor-pointer" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="backward10">replay_10</span>
|
||||
<div class="play-btn cursor-pointer shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-4" :class="seekLoading ? 'animate-spin' : ''" @mousedown.prevent @mouseup.prevent @click.stop="playPauseClick">
|
||||
<span v-if="!isLoading" class="material-icons">{{ seekLoading ? 'autorenew' : isPaused ? 'play_arrow' : 'pause' }}</span>
|
||||
<span v-if="!isLoading" class="material-icons">{{ seekLoading ? 'autorenew' : !isPlaying ? 'play_arrow' : 'pause' }}</span>
|
||||
<widgets-spinner-icon v-else class="h-8 w-8" />
|
||||
</div>
|
||||
<span class="material-icons jump-icon text-white cursor-pointer" :class="isLoading ? 'text-opacity-10' : 'text-opacity-75'" @click.stop="forward10">forward_10</span>
|
||||
|
@ -95,7 +95,6 @@ import { AbsAudioPlayer } from '@/plugins/capacitor'
|
|||
|
||||
export default {
|
||||
props: {
|
||||
playing: Boolean,
|
||||
bookmarks: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
|
@ -113,12 +112,10 @@ export default {
|
|||
currentPlaybackRate: 1,
|
||||
currentTime: 0,
|
||||
bufferedTime: 0,
|
||||
isResetting: false,
|
||||
stateName: 'idle',
|
||||
playInterval: null,
|
||||
trackWidth: 0,
|
||||
isPaused: true,
|
||||
src: null,
|
||||
isPlaying: false,
|
||||
isEnded: false,
|
||||
volume: 0.5,
|
||||
readyTrackWidth: 0,
|
||||
playedTrackWidth: 0,
|
||||
|
@ -136,14 +133,6 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
isPlaying: {
|
||||
get() {
|
||||
return this.playing
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:playing', val)
|
||||
}
|
||||
},
|
||||
menuItems() {
|
||||
var items = []
|
||||
items.push({
|
||||
|
@ -306,18 +295,18 @@ export default {
|
|||
setPlaybackSpeed(speed) {
|
||||
console.log(`[AudioPlayer] Set Playback Rate: ${speed}`)
|
||||
this.currentPlaybackRate = speed
|
||||
AbsAudioPlayer.setPlaybackSpeed({ speed: speed })
|
||||
AbsAudioPlayer.setPlaybackSpeed({ value: speed })
|
||||
},
|
||||
restart() {
|
||||
this.seek(0)
|
||||
},
|
||||
backward10() {
|
||||
if (this.isLoading) return
|
||||
AbsAudioPlayer.seekBackward({ amount: '10000' })
|
||||
AbsAudioPlayer.seekBackward({ value: 10 })
|
||||
},
|
||||
forward10() {
|
||||
if (this.isLoading) return
|
||||
AbsAudioPlayer.seekForward({ amount: '10000' })
|
||||
AbsAudioPlayer.seekForward({ value: 10 })
|
||||
},
|
||||
setStreamReady() {
|
||||
this.readyTrackWidth = this.trackWidth
|
||||
|
@ -387,6 +376,7 @@ export default {
|
|||
this.updateTrack()
|
||||
},
|
||||
updateTrack() {
|
||||
// Update progress track UI
|
||||
var percentDone = this.currentTime / this.totalDuration
|
||||
var totalPercentDone = percentDone
|
||||
var bufferedPercent = this.bufferedTime / this.totalDuration
|
||||
|
@ -419,7 +409,8 @@ export default {
|
|||
|
||||
this.seekedTime = time
|
||||
this.seekLoading = true
|
||||
AbsAudioPlayer.seekPlayer({ timeMs: String(Math.floor(time * 1000)) })
|
||||
|
||||
AbsAudioPlayer.seek({ value: Math.floor(time) })
|
||||
|
||||
if (this.$refs.playedTrack) {
|
||||
var perc = time / this.totalDuration
|
||||
|
@ -455,7 +446,9 @@ export default {
|
|||
},
|
||||
async playPauseClick() {
|
||||
if (this.isLoading) return
|
||||
|
||||
this.isPlaying = !!((await AbsAudioPlayer.playPause()) || {}).playing
|
||||
this.isEnded = false
|
||||
},
|
||||
play() {
|
||||
AbsAudioPlayer.playPlayer()
|
||||
|
@ -471,8 +464,8 @@ export default {
|
|||
clearInterval(this.playInterval)
|
||||
this.playInterval = setInterval(async () => {
|
||||
var data = await AbsAudioPlayer.getCurrentTime()
|
||||
this.currentTime = Number((data.value / 1000).toFixed(2))
|
||||
this.bufferedTime = Number((data.bufferedTime / 1000).toFixed(2))
|
||||
this.currentTime = Number(data.value.toFixed(2))
|
||||
this.bufferedTime = Number(data.bufferedTime.toFixed(2))
|
||||
console.log('[AudioPlayer] Got Current Time', this.currentTime)
|
||||
this.timeupdate()
|
||||
}, 1000)
|
||||
|
@ -481,67 +474,11 @@ export default {
|
|||
clearInterval(this.playInterval)
|
||||
},
|
||||
resetStream(startTime) {
|
||||
this.isResetting = true
|
||||
this.terminateStream()
|
||||
this.closePlayback()
|
||||
},
|
||||
terminateStream() {
|
||||
closePlayback() {
|
||||
if (!this.playbackSession) return
|
||||
AbsAudioPlayer.terminateStream()
|
||||
},
|
||||
onPlayingUpdate(data) {
|
||||
console.log('onPlayingUpdate', JSON.stringify(data))
|
||||
this.isPaused = !data.value
|
||||
this.$store.commit('setPlayerPlaying', !this.isPaused)
|
||||
if (!this.isPaused) {
|
||||
this.startPlayInterval()
|
||||
} else {
|
||||
this.stopPlayInterval()
|
||||
}
|
||||
},
|
||||
onMetadata(data) {
|
||||
console.log('onMetadata', JSON.stringify(data))
|
||||
this.isLoading = false
|
||||
|
||||
// this.totalDuration = Number((data.duration / 1000).toFixed(2))
|
||||
this.totalDuration = Number(data.duration.toFixed(2))
|
||||
this.currentTime = Number((data.currentTime / 1000).toFixed(2))
|
||||
this.stateName = data.stateName
|
||||
|
||||
if (this.stateName === 'ended' && this.isResetting) {
|
||||
this.setFromObj()
|
||||
}
|
||||
|
||||
this.timeupdate()
|
||||
},
|
||||
// When a playback session is started the native android/ios will send the session
|
||||
onPlaybackSession(playbackSession) {
|
||||
console.log('onPlaybackSession received', JSON.stringify(playbackSession))
|
||||
this.playbackSession = playbackSession
|
||||
|
||||
this.$store.commit('setPlayerItem', this.playbackSession)
|
||||
|
||||
// Set track width
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.track) {
|
||||
this.trackWidth = this.$refs.track.clientWidth
|
||||
} else {
|
||||
console.error('Track not loaded', this.$refs)
|
||||
}
|
||||
})
|
||||
},
|
||||
onPlaybackClosed() {
|
||||
console.log('Received onPlaybackClosed evt')
|
||||
this.$store.commit('setPlayerItem', null)
|
||||
this.showFullscreen = false
|
||||
this.playbackSession = null
|
||||
},
|
||||
async init() {
|
||||
this.useChapterTrack = await this.$localStore.getUseChapterTrack()
|
||||
|
||||
this.onPlaybackSessionListener = AbsAudioPlayer.addListener('onPlaybackSession', this.onPlaybackSession)
|
||||
this.onPlaybackClosedListener = AbsAudioPlayer.addListener('onPlaybackClosed', this.onPlaybackClosed)
|
||||
this.onPlayingUpdateListener = AbsAudioPlayer.addListener('onPlayingUpdate', this.onPlayingUpdate)
|
||||
this.onMetadataListener = AbsAudioPlayer.addListener('onMetadata', this.onMetadata)
|
||||
AbsAudioPlayer.closePlayback()
|
||||
},
|
||||
handleGesture() {
|
||||
var touchDistance = this.touchEndY - this.touchStartY
|
||||
|
@ -581,13 +518,73 @@ export default {
|
|||
})
|
||||
this.$localStore.setUseChapterTrack(this.useChapterTrack)
|
||||
} else if (action === 'close') {
|
||||
this.terminateStream()
|
||||
this.closePlayback()
|
||||
}
|
||||
},
|
||||
forceCloseDropdownMenu() {
|
||||
if (this.$refs.dropdownMenu && this.$refs.dropdownMenu.closeMenu) {
|
||||
this.$refs.dropdownMenu.closeMenu()
|
||||
}
|
||||
},
|
||||
//
|
||||
// Listeners from audio AbsAudioPlayer
|
||||
//
|
||||
onPlayingUpdate(data) {
|
||||
console.log('onPlayingUpdate', JSON.stringify(data))
|
||||
this.isPlaying = !!data.value
|
||||
this.$store.commit('setPlayerPlaying', this.isPlaying)
|
||||
if (this.isPlaying) {
|
||||
this.startPlayInterval()
|
||||
} else {
|
||||
this.stopPlayInterval()
|
||||
}
|
||||
},
|
||||
onMetadata(data) {
|
||||
console.log('onMetadata', JSON.stringify(data))
|
||||
this.isLoading = false
|
||||
|
||||
this.totalDuration = Number(data.duration.toFixed(2))
|
||||
this.currentTime = Number(data.currentTime.toFixed(2))
|
||||
// Also includes player state data.playerState
|
||||
|
||||
if (data.playerState == this.$constants.PlayerState.ENDED) {
|
||||
console.log('[AudioPlayer] Playback ended')
|
||||
}
|
||||
this.isEnded = data.playerState == this.$constants.PlayerState.ENDED
|
||||
|
||||
this.timeupdate()
|
||||
},
|
||||
// When a playback session is started the native android/ios will send the session
|
||||
onPlaybackSession(playbackSession) {
|
||||
console.log('onPlaybackSession received', JSON.stringify(playbackSession))
|
||||
this.playbackSession = playbackSession
|
||||
|
||||
this.isEnded = false
|
||||
this.$store.commit('setPlayerItem', this.playbackSession)
|
||||
|
||||
// Set track width
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.track) {
|
||||
this.trackWidth = this.$refs.track.clientWidth
|
||||
} else {
|
||||
console.error('Track not loaded', this.$refs)
|
||||
}
|
||||
})
|
||||
},
|
||||
onPlaybackClosed() {
|
||||
console.log('Received onPlaybackClosed evt')
|
||||
this.$store.commit('setPlayerItem', null)
|
||||
this.showFullscreen = false
|
||||
this.isEnded = false
|
||||
this.playbackSession = null
|
||||
},
|
||||
async init() {
|
||||
this.useChapterTrack = await this.$localStore.getUseChapterTrack()
|
||||
|
||||
this.onPlaybackSessionListener = AbsAudioPlayer.addListener('onPlaybackSession', this.onPlaybackSession)
|
||||
this.onPlaybackClosedListener = AbsAudioPlayer.addListener('onPlaybackClosed', this.onPlaybackClosed)
|
||||
this.onPlayingUpdateListener = AbsAudioPlayer.addListener('onPlayingUpdate', this.onPlayingUpdate)
|
||||
this.onMetadataListener = AbsAudioPlayer.addListener('onMetadata', this.onMetadata)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<app-audio-player ref="audioPlayer" :playing.sync="isPlaying" :bookmarks="bookmarks" :sleep-timer-running="isSleepTimerRunning" :sleep-time-remaining="sleepTimeRemaining" @selectPlaybackSpeed="showPlaybackSpeedModal = true" @updateTime="(t) => (currentTime = t)" @showSleepTimer="showSleepTimer" @showBookmarks="showBookmarks" />
|
||||
<app-audio-player ref="audioPlayer" :bookmarks="bookmarks" :sleep-timer-running="isSleepTimerRunning" :sleep-time-remaining="sleepTimeRemaining" @selectPlaybackSpeed="showPlaybackSpeedModal = true" @updateTime="(t) => (currentTime = t)" @showSleepTimer="showSleepTimer" @showBookmarks="showBookmarks" />
|
||||
|
||||
<modals-playback-speed-modal v-model="showPlaybackSpeedModal" :playback-rate.sync="playbackSpeed" @update:playbackRate="updatePlaybackSpeed" @change="changePlaybackSpeed" />
|
||||
<modals-sleep-timer-modal v-model="showSleepTimerModal" :current-time="sleepTimeRemaining" :sleep-timer-running="isSleepTimerRunning" :current-end-of-chapter-time="currentEndOfChapterTime" @change="selectSleepTimeout" @cancel="cancelSleepTimer" @increase="increaseSleepTimer" @decrease="decreaseSleepTimer" />
|
||||
|
@ -14,7 +14,6 @@ import { AbsAudioPlayer } from '@/plugins/capacitor'
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
isPlaying: false,
|
||||
audioPlayerReady: false,
|
||||
stream: null,
|
||||
download: null,
|
||||
|
@ -163,7 +162,7 @@ export default {
|
|||
closeStreamOnly() {
|
||||
// If user logs out or disconnects from server and not playing local
|
||||
if (this.$refs.audioPlayer && !this.$refs.audioPlayer.isLocalPlayMethod) {
|
||||
this.$refs.audioPlayer.terminateStream()
|
||||
this.$refs.audioPlayer.closePlayback()
|
||||
}
|
||||
},
|
||||
async playLibraryItem(payload) {
|
||||
|
|
|
@ -41,6 +41,7 @@ export default {
|
|||
'@/plugins/init.client.js',
|
||||
'@/plugins/axios.js',
|
||||
'@/plugins/capacitor/index.js',
|
||||
'@/plugins/capacitor/AbsAudioPlayer.js',
|
||||
'@/plugins/toast.js',
|
||||
'@/plugins/constants.js',
|
||||
'@/plugins/haptics.js'
|
||||
|
|
|
@ -1,8 +1,232 @@
|
|||
import { registerPlugin, WebPlugin } from '@capacitor/core';
|
||||
const { PlayerState } = require('../constants')
|
||||
|
||||
var $axios = null
|
||||
var vuexStore = null
|
||||
|
||||
class AbsAudioPlayerWeb extends WebPlugin {
|
||||
constructor() {
|
||||
super()
|
||||
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
|
||||
=]ikolp;awqsxcdz ffvb34seek({ val\
|
||||
ue }) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,3 +235,8 @@ const AbsAudioPlayer = registerPlugin('AbsAudioPlayer', {
|
|||
})
|
||||
|
||||
export { AbsAudioPlayer }
|
||||
|
||||
export default ({ app, store }, inject) => {
|
||||
$axios = app.$axios
|
||||
vuexStore = store
|
||||
}
|
|
@ -22,11 +22,23 @@ const PlayMethod = {
|
|||
LOCAL: 3
|
||||
}
|
||||
|
||||
const PlayerState = {
|
||||
IDLE: 0,
|
||||
BUFFERING: 1,
|
||||
READY: 2,
|
||||
ENDED: 3
|
||||
}
|
||||
|
||||
const Constants = {
|
||||
DownloadStatus,
|
||||
CoverDestination,
|
||||
BookCoverAspectRatio,
|
||||
PlayMethod
|
||||
PlayMethod,
|
||||
PlayerState
|
||||
}
|
||||
|
||||
export {
|
||||
PlayerState
|
||||
}
|
||||
|
||||
export default ({ app }, inject) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue