diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackMetadata.kt b/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackMetadata.kt
new file mode 100644
index 00000000..33cfa9d3
--- /dev/null
+++ b/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackMetadata.kt
@@ -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
+)
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
index b30c5007..c8f4817d 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt
@@ -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) {
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
index 2c5400ad..9251ba78 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt
@@ -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)
}
}
diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
index 25ef78ca..e3fc7e5b 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt
@@ -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))
}
//
diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt
index e27e3914..aa155b28 100644
--- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt
+++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt
@@ -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()
}
}
diff --git a/components/app/AudioPlayer.vue b/components/app/AudioPlayer.vue
index 32cc9bb4..c4b91a67 100644
--- a/components/app/AudioPlayer.vue
+++ b/components/app/AudioPlayer.vue
@@ -61,7 +61,7 @@
first_page
replay_10
- {{ seekLoading ? 'autorenew' : isPaused ? 'play_arrow' : 'pause' }}
+ {{ seekLoading ? 'autorenew' : !isPlaying ? 'play_arrow' : 'pause' }}
forward_10
@@ -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,71 +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()
- }
-
- console.log("received metadata update", data);
-
- if(data.currentRate && data.currentRate > 0) this.playbackSpeed = data.currentRate
-
- 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
@@ -585,13 +518,77 @@ 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
+
+ console.log("received metadata update", data);
+
+ if(data.currentRate && data.currentRate > 0) this.playbackSpeed = data.currentRate
+
+ 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() {
diff --git a/components/app/AudioPlayerContainer.vue b/components/app/AudioPlayerContainer.vue
index 14c96824..72444f9b 100644
--- a/components/app/AudioPlayerContainer.vue
+++ b/components/app/AudioPlayerContainer.vue
@@ -1,6 +1,6 @@
-
(currentTime = t)" @showSleepTimer="showSleepTimer" @showBookmarks="showBookmarks" />
+ (currentTime = t)" @showSleepTimer="showSleepTimer" @showBookmarks="showBookmarks" />
@@ -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) {
diff --git a/nuxt.config.js b/nuxt.config.js
index 59886525..ebd84b69 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -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'
diff --git a/plugins/capacitor/AbsAudioPlayer.js b/plugins/capacitor/AbsAudioPlayer.js
index 1d945c3e..eb74eee8 100644
--- a/plugins/capacitor/AbsAudioPlayer.js
+++ b/plugins/capacitor/AbsAudioPlayer.js
@@ -1,8 +1,231 @@
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()
}
}
@@ -10,4 +233,9 @@ const AbsAudioPlayer = registerPlugin('AbsAudioPlayer', {
web: () => new AbsAudioPlayerWeb()
})
-export { AbsAudioPlayer }
\ No newline at end of file
+export { AbsAudioPlayer }
+
+export default ({ app, store }, inject) => {
+ $axios = app.$axios
+ vuexStore = store
+}
\ No newline at end of file
diff --git a/plugins/constants.js b/plugins/constants.js
index acec919e..b1471010 100644
--- a/plugins/constants.js
+++ b/plugins/constants.js
@@ -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) => {