Skip to content

Commit

Permalink
5.4.1 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed May 25, 2024
1 parent 36e1823 commit 797e9b6
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 82 deletions.
9 changes: 5 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
id('com.android.application')
id 'com.android.application'
id 'kotlin-android'
// id 'kotlin-kapt'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp'
id('com.github.triplet.play') version '3.8.3' apply false
}
Expand Down Expand Up @@ -159,8 +159,8 @@ android {
// Version code schema (not used):
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
versionCode 3020147
versionName "5.4.0"
versionCode 3020148
versionName "5.4.1"

def commit = ""
try {
Expand Down Expand Up @@ -221,6 +221,7 @@ android {
dependencies {
implementation "androidx.core:core-ktx:1.12.0"
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'

constraints {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
tools:ignore="ExportedService">

<intent-filter>
<!-- <action android:name="androidx.media3.session.MediaLibraryService"/>-->
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
<action android:name="ac.mdiq.podcini.intents.PLAYBACK_SERVICE" />
Expand Down
21 changes: 7 additions & 14 deletions app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.os.IBinder
import android.util.Log
import android.util.Pair
import android.view.SurfaceHolder
import android.widget.MediaController
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi
Expand Down Expand Up @@ -114,31 +115,23 @@ abstract class PlaybackController(private val activity: FragmentActivity) {

try {
activity.unregisterReceiver(statusUpdate)
} catch (e: IllegalArgumentException) {
// ignore
}
} catch (e: IllegalArgumentException) { }

try {
activity.unregisterReceiver(notificationReceiver)
} catch (e: IllegalArgumentException) {
// ignore
}
} catch (e: IllegalArgumentException) { }

unbind()
// media = null
released = true

if (eventsRegistered) {

eventsRegistered = false
}
if (eventsRegistered) eventsRegistered = false
}

private fun unbind() {
try {
activity.unbindService(mConnection)
} catch (e: IllegalArgumentException) {
// ignore
}
} catch (e: IllegalArgumentException) { }

initialized = false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
try {
clearMediaPlayerListeners()
// TODO: should use: exoPlayer!!.playWhenReady ?
if (exoPlayer!!.isPlaying) exoPlayer?.stop()
if (exoPlayer?.isPlaying == true) exoPlayer?.stop()
} catch (e: Exception) {
e.printStackTrace()
}
Expand Down Expand Up @@ -686,7 +686,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
while (true) {
delay(bufferUpdateInterval)
withContext(Dispatchers.Main) {
if (bufferedPercentagePrev != exoPlayer!!.bufferedPercentage) {
if (exoPlayer != null && bufferedPercentagePrev != exoPlayer?.bufferedPercentage) {
bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage)
bufferedPercentagePrev = exoPlayer!!.bufferedPercentage
}
Expand Down
148 changes: 89 additions & 59 deletions app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import ac.mdiq.podcini.playback.cast.CastPsmp
import ac.mdiq.podcini.playback.cast.CastStateListener
import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.PSTMCallback
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.currentlyPlayingFeedMediaId
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.writeNoMediaPlaying
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writePlayerStatus
Expand Down Expand Up @@ -60,11 +60,14 @@ import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.bluetooth.BluetoothA2dp
import android.content.*
import android.content.Intent.EXTRA_KEY_EVENT
import android.media.AudioManager
import android.os.*
import android.os.Build.VERSION_CODES
Expand All @@ -74,13 +77,15 @@ import android.util.Log
import android.util.Pair
import android.view.KeyEvent
import android.view.SurfaceHolder
import android.view.ViewConfiguration
import android.webkit.URLUtil
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.media3.common.Player.STATE_ENDED
import androidx.media3.common.Player.STATE_IDLE
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
import androidx.media3.session.MediaSessionService
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult
Expand Down Expand Up @@ -123,6 +128,9 @@ class PlaybackService : MediaSessionService() {

private val mBinder: IBinder = LocalBinder()

private var clickCount = 0
private val clickHandler = Handler(Looper.getMainLooper())

val mPlayerInfo: MediaPlayerInfo
get() = mediaPlayer!!.playerInfo

Expand Down Expand Up @@ -164,7 +172,6 @@ class PlaybackService : MediaSessionService() {
val videoSize: Pair<Int, Int>?
get() = mediaPlayer?.getVideoSize()


inner class LocalBinder : Binder() {
val service: PlaybackService
get() = this@PlaybackService
Expand Down Expand Up @@ -214,6 +221,7 @@ class PlaybackService : MediaSessionService() {
recreateMediaPlayer()

if (LocalMediaPlayer.exoPlayer == null) LocalMediaPlayer.createStaticPlayer(applicationContext)

mediaSession = MediaSession.Builder(applicationContext, LocalMediaPlayer.exoPlayer!!)
.setCallback(MyCallback())
.setCustomLayout(notificationCustomButtons)
Expand Down Expand Up @@ -271,62 +279,65 @@ class PlaybackService : MediaSessionService() {
unregisterReceiver(bluetoothStateUpdated)
unregisterReceiver(audioBecomingNoisy)
taskManager.shutdown()

}

fun isServiceReady(): Boolean {
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 {
Logd(TAG, "in onConnect")
Logd(TAG, "in MyCallback onConnect")
val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
// .add(NotificationCustomButton.REWIND)
// .add(NotificationCustomButton.FORWARD)
if (session.isMediaNotificationController(controller)) {
val playerCommands = MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
// .remove(COMMAND_SEEK_TO_PREVIOUS)
// .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
// .remove(COMMAND_SEEK_TO_NEXT)
// .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
// .removeAll()

//
// // Custom layout and available commands to configure the legacy/framework session.
// return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
//// .setCustomLayout(
//// ImmutableList.of(
//// createSeekBackwardButton(NotificationCustomButton.REWIND),
//// createSeekForwardButton(customCommandSeekForward))
//// )
// .setAvailablePlayerCommands(playerCommands.build())
// .setAvailableSessionCommands(sessionCommands.build())
// .build()

// val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()

/* Registering custom player command buttons for player notification. */
notificationCustomButtons.forEach { commandButton ->
Logd(TAG, "onConnect commandButton ${commandButton.displayName}")
commandButton.sessionCommand?.let(sessionCommands::add)
}
when {
session.isMediaNotificationController(controller) -> {
val playerCommands = MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
// .remove(COMMAND_SEEK_TO_PREVIOUS)
// .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
// .remove(COMMAND_SEEK_TO_NEXT)
// .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
// .removeAll()

//
// // Custom layout and available commands to configure the legacy/framework session.
// return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
//// .setCustomLayout(
//// ImmutableList.of(
//// createSeekBackwardButton(NotificationCustomButton.REWIND),
//// createSeekForwardButton(customCommandSeekForward))
//// )
// .setAvailablePlayerCommands(playerCommands.build())
// .setAvailableSessionCommands(sessionCommands.build())
// .build()

// val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()

/* Registering custom player command buttons for player notification. */
notificationCustomButtons.forEach { commandButton ->
Logd(TAG, "MyCallback onConnect commandButton ${commandButton.displayName}")
commandButton.sessionCommand?.let(sessionCommands::add)
}

return MediaSession.ConnectionResult.accept(
sessionCommands.build(),
playerCommands.build()
)
} else if (session.isAutoCompanionController(controller)) {
// Available session commands to accept incoming custom commands from Auto.
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(sessionCommands.build())
.build()
return MediaSession.ConnectionResult.accept(
sessionCommands.build(),
playerCommands.build()
)
}
session.isAutoCompanionController(controller) -> {
// Available session commands to accept incoming custom commands from Auto.
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(sessionCommands.build())
.build()
}
// Default commands with default custom layout for all other controllers.
else -> return MediaSession.ConnectionResult.AcceptedResultBuilder(session).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) {
Logd(TAG, "MyCallback onPostConnect")
super.onPostConnect(session, controller)
if (notificationCustomButtons.isNotEmpty()) {
/* Setting custom player command buttons to mediaLibrarySession for player notification. */
Expand All @@ -337,6 +348,7 @@ class PlaybackService : MediaSessionService() {

override fun onCustomCommand(session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle): ListenableFuture<SessionResult> {
/* Handling custom command buttons from player notification. */
Logd(TAG, "onCustomCommand called ${customCommand.customAction}")
when (customCommand.customAction) {
NotificationCustomButton.REWIND.customAction -> mediaPlayer?.seekDelta(-rewindSecs * 1000)
NotificationCustomButton.FORWARD.customAction -> mediaPlayer?.seekDelta(fastForwardSecs * 1000)
Expand All @@ -345,8 +357,9 @@ class PlaybackService : MediaSessionService() {
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}

override fun onPlaybackResumption(mediaSession: MediaSession, controller: MediaSession.ControllerInfo): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
val settable = SettableFuture.create<MediaSession.MediaItemsWithStartPosition>()
override fun onPlaybackResumption(mediaSession: MediaSession, controller: MediaSession.ControllerInfo): ListenableFuture<MediaItemsWithStartPosition> {
Logd(TAG, "onPlaybackResumption called ")
val settable = SettableFuture.create<MediaItemsWithStartPosition>()
// scope.launch {
// // Your app is responsible for storing the playlist and the start position
// // to use here
Expand All @@ -355,6 +368,31 @@ class PlaybackService : MediaSessionService() {
// }
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? {
Expand All @@ -373,6 +411,9 @@ class PlaybackService : MediaSessionService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)

// val notification = createNotification()
// startForeground(NOTIFICATION_ID, notification)

val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1
val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false
Expand Down Expand Up @@ -405,20 +446,6 @@ class PlaybackService : MediaSessionService() {
val allowStreamAlways = intent.getBooleanExtra(PlaybackServiceConstants.EXTRA_ALLOW_STREAM_ALWAYS, false)
sendNotificationBroadcast(PlaybackServiceConstants.NOTIFICATION_TYPE_RELOAD, 0)
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 {
try {
val loadedPlayable = withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -1324,6 +1351,9 @@ class PlaybackService : MediaSessionService() {
companion object {
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

const val ACTION_PLAYER_STATUS_CHANGED: String = "action.ac.mdiq.podcini.service.playerStatusChanged"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
Loading

0 comments on commit 797e9b6

Please sign in to comment.