diff --git a/Server.js b/Server.js index 1204fc1f..0dc717eb 100644 --- a/Server.js +++ b/Server.js @@ -64,7 +64,7 @@ class Server extends EventEmitter { var res = await this.ping(serverUrl) if (!res || !res.success) { - this.setServerUrl(null) + //this.setServerUrl(null) return false } var authRes = await this.authorize(serverUrl, token) diff --git a/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt b/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt index 94919d5e..8cab491c 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt @@ -35,7 +35,6 @@ class MyNativeAudio : Plugin() { jsobj.put("playWhenReady", playWhenReady) notifyListeners("onPrepareMedia", jsobj) } - override fun onCar() {} }) } mainActivity.pluginCallback = foregroundServiceReady diff --git a/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt index 4018dbcd..22fab7db 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt @@ -32,6 +32,11 @@ import com.google.android.exoplayer2.source.hls.HlsMediaSource import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.upstream.* import kotlinx.coroutines.* +import android.view.KeyEvent +import java.io.File +import java.util.* +import kotlin.concurrent.schedule +import android.annotation.SuppressLint const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px @@ -46,7 +51,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { fun onPlayingUpdate(isPlaying: Boolean) fun onMetadata(metadata: JSObject) fun onPrepare(audiobookId:String, playWhenReady:Boolean) - fun onCar() } private val tag = "PlayerService" @@ -73,6 +77,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { private var audiobooks = mutableListOf() + private var mediaButtonClickCount: Int = 0 + var mediaButtonClickTimeout: Long = 1000 //ms + var seekAmount: Long = 20000 //ms + fun setCustomObjectListener(mylistener: MyCustomObjectListener) { listener = mylistener } @@ -296,6 +304,13 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { //attach player to playerNotificationManager playerNotificationManager.setPlayer(mPlayer) + + mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) + mediaSession.setCallback(object : MediaSessionCompat.Callback() { + override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { + return handleCallMediaButton(mediaButtonEvent) + } + }) } private inner class DescriptionAdapter(private val controller: MediaControllerCompat) : @@ -531,8 +546,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { // No further calls will be made to other media browsing methods. null } else { - listener.onCar() - val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, @@ -583,5 +596,71 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } result.sendResult(mediaItems) } + + fun handleCallMediaButton(intent: Intent): Boolean { + if(Intent.ACTION_MEDIA_BUTTON == intent.getAction()) { + var keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) + if (keyEvent?.getAction() == KeyEvent.ACTION_UP) { + when (keyEvent?.getKeyCode()) { + KeyEvent.KEYCODE_HEADSETHOOK -> { + if(0 == mediaButtonClickCount) { + if (mPlayer.isPlaying) + pause() + else + play() + } + handleMediaButtonClickCount() + } + KeyEvent.KEYCODE_MEDIA_PLAY -> { + if(0 == mediaButtonClickCount) play() + handleMediaButtonClickCount() + } + KeyEvent.KEYCODE_MEDIA_PAUSE -> { + if(0 == mediaButtonClickCount) pause() + handleMediaButtonClickCount() + } + KeyEvent.KEYCODE_MEDIA_NEXT -> { + seekForward(seekAmount) + } + KeyEvent.KEYCODE_MEDIA_PREVIOUS -> { + seekBackward(seekAmount) + } + KeyEvent.KEYCODE_MEDIA_STOP -> { + terminateStream() + } + else -> { + Log.d(tag, "KeyCode:${keyEvent?.getKeyCode()}") + return false + } + } + } + } + return true + } + + fun handleMediaButtonClickCount() { + mediaButtonClickCount++ + if (1 == mediaButtonClickCount) { + Timer().schedule(mediaButtonClickTimeout) { + handler.sendEmptyMessage(mediaButtonClickCount) + mediaButtonClickCount = 0 + } + } + } + + private val handler : Handler = @SuppressLint("HandlerLeak") + object : Handler(){ + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + if (2 == msg.what) { + seekBackward(seekAmount) + play() + } + else if (msg.what >= 3) { + seekForward(seekAmount) + play() + } + } + } } diff --git a/components/app/StreamContainer.vue b/components/app/StreamContainer.vue index b768f753..05febdd2 100644 --- a/components/app/StreamContainer.vue +++ b/components/app/StreamContainer.vue @@ -47,6 +47,9 @@ export default { } }, computed: { + userToken() { + return this.$store.getters['user/getToken'] + }, currentChapter() { if (!this.audiobook || !this.chapters.length) return null return this.chapters.find((ch) => ch.start <= this.currentTime && ch.end > this.currentTime) @@ -112,7 +115,9 @@ export default { var _clean = this.cover.replace(/\\/g, '/') if (_clean.startsWith('/local')) { var _cover = process.env.NODE_ENV !== 'production' && process.env.PROD !== '1' ? _clean.replace('/local', '') : _clean - return `${this.$store.state.serverUrl}${_cover}` + return `${this.$store.state.serverUrl}${_cover}?token=${this.userToken}&ts=${Date.now()}` + } else if (_clean.startsWith('/metadata')) { + return `${this.$store.state.serverUrl}${_clean}?token=${this.userToken}&ts=${Date.now()}` } return _clean } @@ -257,7 +262,7 @@ export default { cover: this.download.coverUrl || null, duration: String(Math.floor(this.duration * 1000)), series: this.seriesTxt, - token: this.$store.getters['user/getToken'], + token: this.userToken, contentUrl: this.playingDownload.contentUrl, isLocal: true } diff --git a/components/cards/BookCover.vue b/components/cards/BookCover.vue index 95fabcc4..0ddbc014 100644 --- a/components/cards/BookCover.vue +++ b/components/cards/BookCover.vue @@ -60,6 +60,9 @@ export default { } }, computed: { + userToken() { + return this.$store.getters['user/getToken'] + }, book() { return this.audiobook.book || {} }, @@ -99,7 +102,9 @@ export default { var _clean = this.cover.replace(/\\/g, '/') if (_clean.startsWith('/local')) { var _cover = process.env.NODE_ENV !== 'production' && process.env.PROD !== '1' ? _clean.replace('/local', '') : _clean - return `${this.serverUrl}${_cover}` + return `${this.$store.state.serverUrl}${_cover}?token=${this.userToken}&ts=${Date.now()}` + } else if (_clean.startsWith('/metadata')) { + return `${this.$store.state.serverUrl}${_clean}?token=${this.userToken}&ts=${Date.now()}` } return _clean }, diff --git a/pages/audiobook/_id/index.vue b/pages/audiobook/_id/index.vue index e1978e6c..bdbf4048 100644 --- a/pages/audiobook/_id/index.vue +++ b/pages/audiobook/_id/index.vue @@ -117,6 +117,9 @@ export default { userAudiobook() { return this.userAudiobooks[this.audiobookId] || null }, + userToken() { + return this.$store.getters['user/getToken'] + }, localUserAudiobooks() { return this.$store.state.user.localUserAudiobooks || {} }, @@ -291,7 +294,9 @@ export default { var _clean = cover.replace(/\\/g, '/') if (_clean.startsWith('/local')) { var _cover = process.env.NODE_ENV !== 'production' && process.env.PROD !== '1' ? _clean.replace('/local', '') : _clean - return `${this.$store.state.serverUrl}${_cover}` + return `${this.$store.state.serverUrl}${_cover}?token=${this.userToken}&ts=${Date.now()}` + } else if (_clean.startsWith('/metadata')) { + return `${this.$store.state.serverUrl}${_clean}?token=${this.userToken}&ts=${Date.now()}` } return _clean },