5.4.1 commit
This commit is contained in:
parent
36e1823d58
commit
797e9b64ab
|
@ -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 {
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue