5.4.1 commit

This commit is contained in:
Xilin Jia 2024-05-25 09:42:10 +01:00
parent 36e1823d58
commit 797e9b64ab
9 changed files with 122 additions and 79 deletions

View File

@ -1,7 +1,7 @@
plugins { plugins {
id('com.android.application') id 'com.android.application'
id 'kotlin-android' id 'kotlin-android'
// id 'kotlin-kapt' id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp' id 'com.google.devtools.ksp'
id('com.github.triplet.play') version '3.8.3' apply false id('com.github.triplet.play') version '3.8.3' apply false
} }
@ -159,8 +159,8 @@ android {
// Version code schema (not used): // Version code schema (not used):
// "1.2.3-beta4" -> 1020304 // "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395 // "1.2.3" -> 1020395
versionCode 3020147 versionCode 3020148
versionName "5.4.0" versionName "5.4.1"
def commit = "" def commit = ""
try { try {
@ -221,6 +221,7 @@ android {
dependencies { dependencies {
implementation "androidx.core:core-ktx:1.12.0" implementation "androidx.core:core-ktx:1.12.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
implementation 'com.android.volley:volley:1.2.1' implementation 'com.android.volley:volley:1.2.1'
constraints { constraints {

View File

@ -56,6 +56,7 @@
tools:ignore="ExportedService"> tools:ignore="ExportedService">
<intent-filter> <intent-filter>
<!-- <action android:name="androidx.media3.session.MediaLibraryService"/>-->
<action android:name="androidx.media3.session.MediaSessionService"/> <action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/> <action android:name="android.media.browse.MediaBrowserService"/>
<action android:name="ac.mdiq.podcini.intents.PLAYBACK_SERVICE" /> <action android:name="ac.mdiq.podcini.intents.PLAYBACK_SERVICE" />

View File

@ -22,6 +22,7 @@ import android.os.IBinder
import android.util.Log import android.util.Log
import android.util.Pair import android.util.Pair
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.widget.MediaController
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@ -114,31 +115,23 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
try { try {
activity.unregisterReceiver(statusUpdate) activity.unregisterReceiver(statusUpdate)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) { }
// ignore
}
try { try {
activity.unregisterReceiver(notificationReceiver) activity.unregisterReceiver(notificationReceiver)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) { }
// ignore
}
unbind() unbind()
// media = null
released = true released = true
if (eventsRegistered) { if (eventsRegistered) eventsRegistered = false
eventsRegistered = false
}
} }
private fun unbind() { private fun unbind() {
try { try {
activity.unbindService(mConnection) activity.unbindService(mConnection)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) { }
// ignore
}
initialized = false initialized = false
} }

View File

@ -574,7 +574,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
try { try {
clearMediaPlayerListeners() clearMediaPlayerListeners()
// TODO: should use: exoPlayer!!.playWhenReady ? // TODO: should use: exoPlayer!!.playWhenReady ?
if (exoPlayer!!.isPlaying) exoPlayer?.stop() if (exoPlayer?.isPlaying == true) exoPlayer?.stop()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@ -686,7 +686,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
while (true) { while (true) {
delay(bufferUpdateInterval) delay(bufferUpdateInterval)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (bufferedPercentagePrev != exoPlayer!!.bufferedPercentage) { if (exoPlayer != null && bufferedPercentagePrev != exoPlayer?.bufferedPercentage) {
bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage) bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage)
bufferedPercentagePrev = exoPlayer!!.bufferedPercentage bufferedPercentagePrev = exoPlayer!!.bufferedPercentage
} }

View File

@ -11,10 +11,10 @@ import ac.mdiq.podcini.playback.cast.CastPsmp
import ac.mdiq.podcini.playback.cast.CastStateListener import ac.mdiq.podcini.playback.cast.CastStateListener
import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.PSTMCallback import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.PSTMCallback
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.clearCurrentlyPlayingTemporaryPlaybackSpeed import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.clearCurrentlyPlayingTemporaryPlaybackSpeed
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentEpisodeIsVideo import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentEpisodeIsVideo
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingTemporaryPlaybackSpeed import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingTemporaryPlaybackSpeed
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeMediaPlaying import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeMediaPlaying
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeNoMediaPlaying import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeNoMediaPlaying
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writePlayerStatus import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writePlayerStatus
@ -60,11 +60,14 @@ import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE import android.app.PendingIntent.FLAG_IMMUTABLE
import android.bluetooth.BluetoothA2dp import android.bluetooth.BluetoothA2dp
import android.content.* import android.content.*
import android.content.Intent.EXTRA_KEY_EVENT
import android.media.AudioManager import android.media.AudioManager
import android.os.* import android.os.*
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
@ -74,6 +77,7 @@ import android.util.Log
import android.util.Pair import android.util.Pair
import android.view.KeyEvent import android.view.KeyEvent
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.ViewConfiguration
import android.webkit.URLUtil import android.webkit.URLUtil
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
@ -81,6 +85,7 @@ import androidx.media3.common.Player.STATE_ENDED
import androidx.media3.common.Player.STATE_IDLE import androidx.media3.common.Player.STATE_IDLE
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
import androidx.media3.session.MediaSessionService import androidx.media3.session.MediaSessionService
import androidx.media3.session.SessionCommand import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult import androidx.media3.session.SessionResult
@ -123,6 +128,9 @@ class PlaybackService : MediaSessionService() {
private val mBinder: IBinder = LocalBinder() private val mBinder: IBinder = LocalBinder()
private var clickCount = 0
private val clickHandler = Handler(Looper.getMainLooper())
val mPlayerInfo: MediaPlayerInfo val mPlayerInfo: MediaPlayerInfo
get() = mediaPlayer!!.playerInfo get() = mediaPlayer!!.playerInfo
@ -164,7 +172,6 @@ class PlaybackService : MediaSessionService() {
val videoSize: Pair<Int, Int>? val videoSize: Pair<Int, Int>?
get() = mediaPlayer?.getVideoSize() get() = mediaPlayer?.getVideoSize()
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
val service: PlaybackService val service: PlaybackService
get() = this@PlaybackService get() = this@PlaybackService
@ -214,6 +221,7 @@ class PlaybackService : MediaSessionService() {
recreateMediaPlayer() recreateMediaPlayer()
if (LocalMediaPlayer.exoPlayer == null) LocalMediaPlayer.createStaticPlayer(applicationContext) if (LocalMediaPlayer.exoPlayer == null) LocalMediaPlayer.createStaticPlayer(applicationContext)
mediaSession = MediaSession.Builder(applicationContext, LocalMediaPlayer.exoPlayer!!) mediaSession = MediaSession.Builder(applicationContext, LocalMediaPlayer.exoPlayer!!)
.setCallback(MyCallback()) .setCallback(MyCallback())
.setCustomLayout(notificationCustomButtons) .setCustomLayout(notificationCustomButtons)
@ -271,62 +279,65 @@ class PlaybackService : MediaSessionService() {
unregisterReceiver(bluetoothStateUpdated) unregisterReceiver(bluetoothStateUpdated)
unregisterReceiver(audioBecomingNoisy) unregisterReceiver(audioBecomingNoisy)
taskManager.shutdown() taskManager.shutdown()
} }
fun isServiceReady(): Boolean { fun isServiceReady(): Boolean {
return mediaSession?.player?.playbackState != STATE_IDLE && mediaSession?.player?.playbackState != STATE_ENDED return mediaSession?.player?.playbackState != STATE_IDLE && mediaSession?.player?.playbackState != STATE_ENDED
} }
private inner class MyCallback : MediaSession.Callback { inner class MyCallback : MediaSession.Callback {
override fun onConnect(session: MediaSession, controller: MediaSession.ControllerInfo): MediaSession.ConnectionResult { override fun onConnect(session: MediaSession, controller: MediaSession.ControllerInfo): MediaSession.ConnectionResult {
Logd(TAG, "in onConnect") Logd(TAG, "in MyCallback onConnect")
val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
// .add(NotificationCustomButton.REWIND) // .add(NotificationCustomButton.REWIND)
// .add(NotificationCustomButton.FORWARD) // .add(NotificationCustomButton.FORWARD)
if (session.isMediaNotificationController(controller)) { when {
val playerCommands = MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() session.isMediaNotificationController(controller) -> {
// .remove(COMMAND_SEEK_TO_PREVIOUS) val playerCommands = MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
// .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) // .remove(COMMAND_SEEK_TO_PREVIOUS)
// .remove(COMMAND_SEEK_TO_NEXT) // .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
// .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) // .remove(COMMAND_SEEK_TO_NEXT)
// .removeAll() // .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
// .removeAll()
// //
// // Custom layout and available commands to configure the legacy/framework session. // // Custom layout and available commands to configure the legacy/framework session.
// return MediaSession.ConnectionResult.AcceptedResultBuilder(session) // return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
//// .setCustomLayout( //// .setCustomLayout(
//// ImmutableList.of( //// ImmutableList.of(
//// createSeekBackwardButton(NotificationCustomButton.REWIND), //// createSeekBackwardButton(NotificationCustomButton.REWIND),
//// createSeekForwardButton(customCommandSeekForward)) //// createSeekForwardButton(customCommandSeekForward))
//// ) //// )
// .setAvailablePlayerCommands(playerCommands.build()) // .setAvailablePlayerCommands(playerCommands.build())
// .setAvailableSessionCommands(sessionCommands.build()) // .setAvailableSessionCommands(sessionCommands.build())
// .build() // .build()
// val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon() // val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
/* Registering custom player command buttons for player notification. */ /* Registering custom player command buttons for player notification. */
notificationCustomButtons.forEach { commandButton -> notificationCustomButtons.forEach { commandButton ->
Logd(TAG, "onConnect commandButton ${commandButton.displayName}") Logd(TAG, "MyCallback onConnect commandButton ${commandButton.displayName}")
commandButton.sessionCommand?.let(sessionCommands::add) commandButton.sessionCommand?.let(sessionCommands::add)
}
return MediaSession.ConnectionResult.accept(
sessionCommands.build(),
playerCommands.build()
)
} }
session.isAutoCompanionController(controller) -> {
return MediaSession.ConnectionResult.accept( // Available session commands to accept incoming custom commands from Auto.
sessionCommands.build(), return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
playerCommands.build() .setAvailableSessionCommands(sessionCommands.build())
) .build()
} else if (session.isAutoCompanionController(controller)) { }
// Available session commands to accept incoming custom commands from Auto. // Default commands with default custom layout for all other controllers.
return MediaSession.ConnectionResult.AcceptedResultBuilder(session) else -> return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
.setAvailableSessionCommands(sessionCommands.build())
.build()
} }
// Default commands with default custom layout for all other controllers.
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
} }
override fun onPostConnect(session: MediaSession, controller: MediaSession.ControllerInfo) { override fun onPostConnect(session: MediaSession, controller: MediaSession.ControllerInfo) {
Logd(TAG, "MyCallback onPostConnect")
super.onPostConnect(session, controller) super.onPostConnect(session, controller)
if (notificationCustomButtons.isNotEmpty()) { if (notificationCustomButtons.isNotEmpty()) {
/* Setting custom player command buttons to mediaLibrarySession for player notification. */ /* Setting custom player command buttons to mediaLibrarySession for player notification. */
@ -337,6 +348,7 @@ class PlaybackService : MediaSessionService() {
override fun onCustomCommand(session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle): ListenableFuture<SessionResult> { override fun onCustomCommand(session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle): ListenableFuture<SessionResult> {
/* Handling custom command buttons from player notification. */ /* Handling custom command buttons from player notification. */
Logd(TAG, "onCustomCommand called ${customCommand.customAction}")
when (customCommand.customAction) { when (customCommand.customAction) {
NotificationCustomButton.REWIND.customAction -> mediaPlayer?.seekDelta(-rewindSecs * 1000) NotificationCustomButton.REWIND.customAction -> mediaPlayer?.seekDelta(-rewindSecs * 1000)
NotificationCustomButton.FORWARD.customAction -> mediaPlayer?.seekDelta(fastForwardSecs * 1000) NotificationCustomButton.FORWARD.customAction -> mediaPlayer?.seekDelta(fastForwardSecs * 1000)
@ -345,8 +357,9 @@ class PlaybackService : MediaSessionService() {
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
} }
override fun onPlaybackResumption(mediaSession: MediaSession, controller: MediaSession.ControllerInfo): ListenableFuture<MediaSession.MediaItemsWithStartPosition> { override fun onPlaybackResumption(mediaSession: MediaSession, controller: MediaSession.ControllerInfo): ListenableFuture<MediaItemsWithStartPosition> {
val settable = SettableFuture.create<MediaSession.MediaItemsWithStartPosition>() Logd(TAG, "onPlaybackResumption called ")
val settable = SettableFuture.create<MediaItemsWithStartPosition>()
// scope.launch { // scope.launch {
// // Your app is responsible for storing the playlist and the start position // // Your app is responsible for storing the playlist and the start position
// // to use here // // to use here
@ -355,6 +368,31 @@ class PlaybackService : MediaSessionService() {
// } // }
return settable return settable
} }
override fun onMediaButtonEvent(mediaSession: MediaSession, controller: MediaSession.ControllerInfo, intent: Intent): Boolean {
val keyEvent =if (Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU)
intent.extras!!.getParcelable(EXTRA_KEY_EVENT, KeyEvent::class.java)
else intent.extras!!.getParcelable(EXTRA_KEY_EVENT) as? KeyEvent
Logd(TAG, "onMediaButtonEvent ${keyEvent?.keyCode}")
if (keyEvent != null && keyEvent.action == KeyEvent.ACTION_DOWN && keyEvent.repeatCount == 0) {
val keyCode = keyEvent.keyCode
if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
clickCount++
clickHandler.removeCallbacksAndMessages(null)
clickHandler.postDelayed({
when (clickCount) {
1 -> handleKeycode(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false)
2 -> mediaPlayer?.seekDelta(fastForwardSecs * 1000)
3 -> mediaPlayer?.seekDelta(-rewindSecs * 1000)
}
clickCount = 0
}, ViewConfiguration.getDoubleTapTimeout().toLong())
return true
} else return handleKeycode(keyCode, false)
}
return false
}
} }
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? { override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
@ -373,6 +411,9 @@ class PlaybackService : MediaSessionService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
// val notification = createNotification()
// startForeground(NOTIFICATION_ID, notification)
val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1 val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1
val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION) val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false
@ -405,20 +446,6 @@ class PlaybackService : MediaSessionService() {
val allowStreamAlways = intent.getBooleanExtra(PlaybackServiceConstants.EXTRA_ALLOW_STREAM_ALWAYS, false) val allowStreamAlways = intent.getBooleanExtra(PlaybackServiceConstants.EXTRA_ALLOW_STREAM_ALWAYS, false)
sendNotificationBroadcast(PlaybackServiceConstants.NOTIFICATION_TYPE_RELOAD, 0) sendNotificationBroadcast(PlaybackServiceConstants.NOTIFICATION_TYPE_RELOAD, 0)
if (allowStreamAlways) isAllowMobileStreaming = true if (allowStreamAlways) isAllowMobileStreaming = true
// Observable.fromCallable {
// if (playable is FeedMedia) return@fromCallable DBReader.getFeedMedia(playable.id)
// else return@fromCallable playable
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { loadedPlayable: Playable? -> startPlaying(loadedPlayable, allowStreamThisTime) },
// { error: Throwable ->
// Logd(TAG, "Playable was not found. Stopping service.")
// error.printStackTrace()
// })
scope.launch { scope.launch {
try { try {
val loadedPlayable = withContext(Dispatchers.IO) { val loadedPlayable = withContext(Dispatchers.IO) {
@ -1324,6 +1351,9 @@ class PlaybackService : MediaSessionService() {
companion object { companion object {
private const val TAG = "PlaybackService" private const val TAG = "PlaybackService"
private const val NOTIFICATION_ID = 5326
private const val CHANNEL_ID = "podcini_session_notification_channel_id"
private const val POSITION_EVENT_INTERVAL = 5L private const val POSITION_EVENT_INTERVAL = 5L
const val ACTION_PLAYER_STATUS_CHANGED: String = "action.ac.mdiq.podcini.service.playerStatusChanged" const val ACTION_PLAYER_STATUS_CHANGED: String = "action.ac.mdiq.podcini.service.playerStatusChanged"

View File

@ -66,7 +66,13 @@ class OpmlReader {
} }
} }
} }
eventType = xpp.next() try {
// TODO: on first install app: java.io.IOException: Underlying input stream returned zero bytes
eventType = xpp.next()
} catch(e: Exception) {
Log.e(TAG, "xpp.next() invalid: $e")
break
}
} }
Logd(TAG, "Parsing finished.") Logd(TAG, "Parsing finished.")

View File

@ -74,6 +74,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.MoreExecutors
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -93,6 +94,7 @@ class MainActivity : CastEnabledActivity() {
private lateinit var mainView: View private lateinit var mainView: View
private lateinit var audioPlayerFragment: AudioPlayerFragment private lateinit var audioPlayerFragment: AudioPlayerFragment
private lateinit var audioPlayerFragmentView: View private lateinit var audioPlayerFragmentView: View
private lateinit var controllerFuture: ListenableFuture<MediaController>
private lateinit var navDrawer: View private lateinit var navDrawer: View
private lateinit var dummyView : View private lateinit var dummyView : View
lateinit var bottomSheet: LockableBottomSheetBehavior<*> lateinit var bottomSheet: LockableBottomSheetBehavior<*>
@ -503,7 +505,7 @@ class MainActivity : CastEnabledActivity() {
RatingDialog.init(this) RatingDialog.init(this)
val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java)) val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync() controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
controllerFuture.addListener({ controllerFuture.addListener({
// Call controllerFuture.get() to retrieve the MediaController. // Call controllerFuture.get() to retrieve the MediaController.
// MediaController implements the Player interface, so it can be // MediaController implements the Player interface, so it can be
@ -533,7 +535,7 @@ class MainActivity : CastEnabledActivity() {
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
MediaController.releaseFuture(controllerFuture)
} }
override fun onTrimMemory(level: Int) { override fun onTrimMemory(level: Int) {

View File

@ -1,4 +1,9 @@
## 5.4.1
* fixed occasional crash of detecting existing OPML file for new install
* should have fixed the mal-functioning earphone buttons
## 5.4.0 ## 5.4.0
* replaced thread with coroutines in DBWrite * replaced thread with coroutines in DBWrite

View File

@ -0,0 +1,5 @@
Version 5.4.1 brings several changes:
* fixed occasional crash of detecting existing OPML file for new install
* should have fixed the mal-functioning earphone buttons