2
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-08-22 10:09:39 +00:00

Improve Kotlin converted from java in various places

This commit is contained in:
Stypox 2025-06-09 15:22:17 +02:00
parent 7330541499
commit 3f62ec7e53
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
5 changed files with 750 additions and 1206 deletions

View File

@ -37,7 +37,6 @@ import org.schabi.newpipe.player.notification.NotificationPlayerUi
import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.ThemeHelper
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.function.BiConsumer
import java.util.function.Consumer import java.util.function.Consumer
/** /**
@ -47,13 +46,13 @@ class PlayerService : MediaBrowserServiceCompat() {
// These objects are used to cleanly separate the Service implementation (in this file) and the // These objects are used to cleanly separate the Service implementation (in this file) and the
// media browser and playback preparer implementations. At the moment the playback preparer is // media browser and playback preparer implementations. At the moment the playback preparer is
// only used in conjunction with the media browser. // only used in conjunction with the media browser.
private var mediaBrowserImpl: MediaBrowserImpl? = null private lateinit var mediaBrowserImpl: MediaBrowserImpl
private var mediaBrowserPlaybackPreparer: MediaBrowserPlaybackPreparer? = null private lateinit var mediaBrowserPlaybackPreparer: MediaBrowserPlaybackPreparer
// these are instantiated in onCreate() as per // these are instantiated in onCreate() as per
// https://developer.android.com/training/cars/media#browser_workflow // https://developer.android.com/training/cars/media#browser_workflow
private var mediaSession: MediaSessionCompat? = null private lateinit var mediaSession: MediaSessionCompat
private var sessionConnector: MediaSessionConnector? = null private lateinit var sessionConnector: MediaSessionConnector
/** /**
* @return the current active player instance. May be null, since the player service can outlive * @return the current active player instance. May be null, since the player service can outlive
@ -68,7 +67,7 @@ class PlayerService : MediaBrowserServiceCompat() {
* The parameter taken by this [Consumer] can be null to indicate the player is being * The parameter taken by this [Consumer] can be null to indicate the player is being
* stopped. * stopped.
*/ */
private var onPlayerStartedOrStopped: Consumer<Player?>? = null private var onPlayerStartedOrStopped: ((player: Player?) -> Unit)? = null
//region Service lifecycle //region Service lifecycle
override fun onCreate() { override fun onCreate() {
@ -80,14 +79,7 @@ class PlayerService : MediaBrowserServiceCompat() {
Localization.assureCorrectAppLanguage(this) Localization.assureCorrectAppLanguage(this)
ThemeHelper.setTheme(this) ThemeHelper.setTheme(this)
mediaBrowserImpl = MediaBrowserImpl( mediaBrowserImpl = MediaBrowserImpl(this, this::notifyChildrenChanged)
this,
Consumer { parentId: String ->
this.notifyChildrenChanged(
parentId
)
}
)
// see https://developer.android.com/training/cars/media#browser_workflow // see https://developer.android.com/training/cars/media#browser_workflow
val session = MediaSessionCompat(this, "MediaSessionPlayerServ") val session = MediaSessionCompat(this, "MediaSessionPlayerServ")
@ -98,17 +90,10 @@ class PlayerService : MediaBrowserServiceCompat() {
connector.setMetadataDeduplicationEnabled(true) connector.setMetadataDeduplicationEnabled(true)
mediaBrowserPlaybackPreparer = MediaBrowserPlaybackPreparer( mediaBrowserPlaybackPreparer = MediaBrowserPlaybackPreparer(
this, context = this,
BiConsumer { message: String, code: Int -> setMediaSessionError = connector::setCustomErrorMessage,
connector.setCustomErrorMessage( clearMediaSessionError = { connector.setCustomErrorMessage(null) },
message, onPrepare = { player?.onPrepare() }
code
)
},
Runnable { connector.setCustomErrorMessage(null) },
Consumer { playWhenReady: Boolean? ->
player?.onPrepare()
}
) )
connector.setPlaybackPreparer(mediaBrowserPlaybackPreparer) connector.setPlaybackPreparer(mediaBrowserPlaybackPreparer)
@ -125,11 +110,8 @@ class PlayerService : MediaBrowserServiceCompat() {
if (DEBUG) { if (DEBUG) {
Log.d( Log.d(
TAG, TAG,
( "onStartCommand() called with: intent = [$intent], extras = [${
"onStartCommand() called with: intent = [" + intent + intent.extras.toDebugString()}], flags = [$flags], startId = [$startId]"
"], extras = [" + intent.extras.toDebugString() +
"], flags = [" + flags + "], startId = [" + startId + "]"
)
) )
} }
@ -140,7 +122,7 @@ class PlayerService : MediaBrowserServiceCompat() {
val playerWasNull = (player == null) val playerWasNull = (player == null)
if (playerWasNull) { if (playerWasNull) {
// make sure the player exists, in case the service was resumed // make sure the player exists, in case the service was resumed
player = Player(this, mediaSession!!, sessionConnector!!) player = Player(this, mediaSession, sessionConnector)
} }
// Be sure that the player notification is set and the service is started in foreground, // Be sure that the player notification is set and the service is started in foreground,
@ -150,35 +132,29 @@ class PlayerService : MediaBrowserServiceCompat() {
// no one already and starting the service in foreground should not create any issues. // no one already and starting the service in foreground should not create any issues.
// If the service is already started in foreground, requesting it to be started // If the service is already started in foreground, requesting it to be started
// shouldn't do anything. // shouldn't do anything.
player!!.UIs().get(NotificationPlayerUi::class.java) player?.UIs()?.get(NotificationPlayerUi::class)?.createNotificationAndStartForeground()
?.createNotificationAndStartForeground()
val startedOrStopped = onPlayerStartedOrStopped if (playerWasNull) {
if (playerWasNull && startedOrStopped != null) {
// notify that a new player was created (but do it after creating the foreground // notify that a new player was created (but do it after creating the foreground
// notification just to make sure we don't incur, due to slowness, in // notification just to make sure we don't incur, due to slowness, in
// "Context.startForegroundService() did not then call Service.startForeground()") // "Context.startForegroundService() did not then call Service.startForeground()")
startedOrStopped.accept(player) onPlayerStartedOrStopped?.invoke(player)
} }
} }
val p = player val p = player
if (Intent.ACTION_MEDIA_BUTTON == intent.action && if (Intent.ACTION_MEDIA_BUTTON == intent.action && p?.playQueue == null) {
(p == null || p.playQueue == null) // No need to process media button's actions if the player is not working, otherwise
) { // the player service would strangely start with nothing to play
/* // Stop the service in this case, which will be removed from the foreground and its
No need to process media button's actions if the player is not working, otherwise // notification cancelled in its destruction
the player service would strangely start with nothing to play
Stop the service in this case, which will be removed from the foreground and its
notification cancelled in its destruction
*/
destroyPlayerAndStopService() destroyPlayerAndStopService()
return START_NOT_STICKY return START_NOT_STICKY
} }
if (p != null) { if (p != null) {
p.handleIntent(intent) p.handleIntent(intent)
p.UIs().get(MediaSessionPlayerUi::class.java) p.UIs().get(MediaSessionPlayerUi::class)
?.handleMediaButtonIntent(intent) ?.handleMediaButtonIntent(intent)
} }
@ -218,22 +194,22 @@ class PlayerService : MediaBrowserServiceCompat() {
cleanup() cleanup()
mediaBrowserPlaybackPreparer?.dispose() mediaBrowserPlaybackPreparer.dispose()
mediaSession?.release() mediaSession.release()
mediaBrowserImpl?.dispose() mediaBrowserImpl.dispose()
} }
private fun cleanup() { private fun cleanup() {
val p = player val p = player
if (p != null) { if (p != null) {
// notify that the player is being destroyed // notify that the player is being destroyed
onPlayerStartedOrStopped?.accept(null) onPlayerStartedOrStopped?.invoke(null)
p.saveAndShutdown() p.saveAndShutdown()
player = null player = null
} }
// Should already be handled by MediaSessionPlayerUi, but just to be sure. // Should already be handled by MediaSessionPlayerUi, but just to be sure.
mediaSession?.setActive(false) mediaSession.setActive(false)
// Should already be handled by NotificationUtil.cancelNotificationAndStopForeground() in // Should already be handled by NotificationUtil.cancelNotificationAndStopForeground() in
// NotificationPlayerUi, but let's make sure that the foreground service is stopped. // NotificationPlayerUi, but let's make sure that the foreground service is stopped.
@ -273,24 +249,22 @@ class PlayerService : MediaBrowserServiceCompat() {
if (DEBUG) { if (DEBUG) {
Log.d( Log.d(
TAG, TAG,
( "onBind() called with: intent = [$intent], extras = [${
"onBind() called with: intent = [" + intent + intent.extras.toDebugString()}]"
"], extras = [" + intent.extras.toDebugString() + "]"
)
) )
} }
if (BIND_PLAYER_HOLDER_ACTION == intent.action) { return if (BIND_PLAYER_HOLDER_ACTION == intent.action) {
// Note that this binder might be reused multiple times while the service is alive, even // Note that this binder might be reused multiple times while the service is alive, even
// after unbind() has been called: https://stackoverflow.com/a/8794930 . // after unbind() has been called: https://stackoverflow.com/a/8794930 .
return mBinder mBinder
} else if (SERVICE_INTERFACE == intent.action) { } else if (SERVICE_INTERFACE == intent.action) {
// MediaBrowserService also uses its own binder, so for actions related to the media // MediaBrowserService also uses its own binder, so for actions related to the media
// browser service, pass the onBind to the superclass. // browser service, pass the onBind to the superclass.
return super.onBind(intent) super.onBind(intent)
} else { } else {
// This is an unknown request, avoid returning any binder to not leak objects. // This is an unknown request, avoid returning any binder to not leak objects.
return null null
} }
} }
@ -307,9 +281,9 @@ class PlayerService : MediaBrowserServiceCompat() {
* by the [Consumer] can be null to indicate that the player is stopping. * by the [Consumer] can be null to indicate that the player is stopping.
* @param listener the listener to set or unset * @param listener the listener to set or unset
*/ */
fun setPlayerListener(listener: Consumer<Player?>?) { fun setPlayerListener(listener: ((player: Player?) -> Unit)?) {
this.onPlayerStartedOrStopped = listener this.onPlayerStartedOrStopped = listener
listener?.accept(player) listener?.invoke(player)
} }
//endregion //endregion
@ -320,14 +294,14 @@ class PlayerService : MediaBrowserServiceCompat() {
rootHints: Bundle? rootHints: Bundle?
): BrowserRoot? { ): BrowserRoot? {
// TODO check if the accessing package has permission to view data // TODO check if the accessing package has permission to view data
return mediaBrowserImpl?.onGetRoot(clientPackageName, clientUid, rootHints) return mediaBrowserImpl.onGetRoot(clientPackageName, clientUid, rootHints)
} }
override fun onLoadChildren( override fun onLoadChildren(
parentId: String, parentId: String,
result: Result<List<MediaBrowserCompat.MediaItem>> result: Result<List<MediaBrowserCompat.MediaItem>>
) { ) {
mediaBrowserImpl?.onLoadChildren(parentId, result) mediaBrowserImpl.onLoadChildren(parentId, result)
} }
override fun onSearch( override fun onSearch(
@ -335,7 +309,7 @@ class PlayerService : MediaBrowserServiceCompat() {
extras: Bundle?, extras: Bundle?,
result: Result<List<MediaBrowserCompat.MediaItem>> result: Result<List<MediaBrowserCompat.MediaItem>>
) { ) {
mediaBrowserImpl?.onSearch(query, result) mediaBrowserImpl.onSearch(query, result)
} //endregion } //endregion
companion object { companion object {

View File

@ -20,7 +20,6 @@ import org.schabi.newpipe.player.event.PlayerServiceEventListener
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener
import org.schabi.newpipe.player.playqueue.PlayQueue import org.schabi.newpipe.player.playqueue.PlayQueue
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import java.util.function.Consumer
private val DEBUG = MainActivity.DEBUG private val DEBUG = MainActivity.DEBUG
private val TAG: String = PlayerHolder::class.java.getSimpleName() private val TAG: String = PlayerHolder::class.java.getSimpleName()
@ -40,9 +39,9 @@ object PlayerHolder {
private val player: Player? private val player: Player?
get() = playerService?.player get() = playerService?.player
// player play queue might be null e.g. while player is starting
private val playQueue: PlayQueue? private val playQueue: PlayQueue?
get() = // player play queue might be null e.g. while player is starting get() = this.player?.playQueue
this.player?.playQueue
val type: PlayerType? val type: PlayerType?
/** /**
@ -78,8 +77,8 @@ object PlayerHolder {
// Force reload data from service // Force reload data from service
newListener?.let { listener -> newListener?.let { listener ->
playerService?.let { playerService?.let { service ->
listener.onServiceConnected(it) listener.onServiceConnected(service)
startPlayerListener() startPlayerListener()
// ^ will call listener.onPlayerConnected() down the line if there is an active player // ^ will call listener.onPlayerConnected() down the line if there is an active player
} }
@ -103,7 +102,7 @@ object PlayerHolder {
newListener: PlayerServiceExtendedEventListener? newListener: PlayerServiceExtendedEventListener?
) { ) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "startService() called with playAfterConnect=" + playAfterConnect) Log.d(TAG, "startService() called with playAfterConnect=$playAfterConnect")
} }
val context = this.commonContext val context = this.commonContext
setListener(newListener) setListener(newListener)
@ -162,21 +161,15 @@ object PlayerHolder {
val s = localBinder.service val s = localBinder.service
requireNotNull(s) { requireNotNull(s) {
( "PlayerService.LocalBinder.getService() must never be" +
"PlayerService.LocalBinder.getService() must never be" + "null after the service connects"
"null after the service connects"
)
} }
playerService = s playerService = s
val l = listener listener?.let { l ->
if (l != null) {
l.onServiceConnected(s) l.onServiceConnected(s)
player?.let { player?.let { l.onPlayerConnected(it, playAfterConnect) }
l.onPlayerConnected(it, playAfterConnect)
}
} }
startPlayerListener() startPlayerListener()
// ^ will call listener.onPlayerConnected() down the line if there is an active player // ^ will call listener.onPlayerConnected() down the line if there is an active player
// notify the main activity that binding the service has completed, so that it can // notify the main activity that binding the service has completed, so that it can
@ -305,9 +298,8 @@ object PlayerHolder {
* or stopping. This is necessary since the service outlives the player e.g. to answer Android * or stopping. This is necessary since the service outlives the player e.g. to answer Android
* Auto media browser queries. * Auto media browser queries.
*/ */
private val playerStateListener = Consumer { player: Player? -> private val playerStateListener: (Player?) -> Unit = { player: Player? ->
val l = listener listener?.let { l ->
if (l != null) {
if (player == null) { if (player == null) {
// player.fragmentListener=null is already done by player.stopActivityBinding(), // player.fragmentListener=null is already done by player.stopActivityBinding(),
// which is called by player.destroy(), which is in turn called by PlayerService // which is called by player.destroy(), which is in turn called by PlayerService

View File

@ -36,7 +36,6 @@ import org.schabi.newpipe.local.playlist.RemotePlaylistManager
import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.image.ImageStrategy import org.schabi.newpipe.util.image.ImageStrategy
import java.util.function.Consumer
/** /**
* This class is used to cleanly separate the Service implementation (in * This class is used to cleanly separate the Service implementation (in
@ -46,16 +45,14 @@ import java.util.function.Consumer
*/ */
class MediaBrowserImpl( class MediaBrowserImpl(
private val context: Context, private val context: Context,
notifyChildrenChanged: Consumer<String>, // parentId notifyChildrenChanged: (parentId: String) -> Unit,
) { ) {
private val database = NewPipeDatabase.getInstance(context) private val database = NewPipeDatabase.getInstance(context)
private var disposables = CompositeDisposable() private var disposables = CompositeDisposable()
init { init {
// this will listen to changes in the bookmarks until this MediaBrowserImpl is dispose()d // this will listen to changes in the bookmarks until this MediaBrowserImpl is dispose()d
disposables.add( disposables.add(getMergedPlaylists().subscribe { notifyChildrenChanged(ID_BOOKMARKS) })
getMergedPlaylists().subscribe { notifyChildrenChanged.accept(ID_BOOKMARKS) }
)
} }
//region Cleanup //region Cleanup
@ -204,6 +201,7 @@ class MediaBrowserImpl(
val builder = MediaDescriptionCompat.Builder() val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForInfoItem(item)) builder.setMediaId(createMediaIdForInfoItem(item))
.setTitle(item.name) .setTitle(item.name)
.setIconUri(ImageStrategy.choosePreferredImage(item.thumbnails)?.toUri())
when (item.infoType) { when (item.infoType) {
InfoType.STREAM -> builder.setSubtitle((item as StreamInfoItem).uploaderName) InfoType.STREAM -> builder.setSubtitle((item as StreamInfoItem).uploaderName)
@ -212,10 +210,6 @@ class MediaBrowserImpl(
else -> return null else -> return null
} }
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
builder.setIconUri(imageUriOrNullIfDisabled(it))
}
return MediaBrowserCompat.MediaItem( return MediaBrowserCompat.MediaItem(
builder.build(), builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
@ -276,10 +270,7 @@ class MediaBrowserImpl(
builder.setMediaId(createMediaIdForPlaylistIndex(true, playlistId, index)) builder.setMediaId(createMediaIdForPlaylistIndex(true, playlistId, index))
.setTitle(item.name) .setTitle(item.name)
.setSubtitle(item.uploaderName) .setSubtitle(item.uploaderName)
.setIconUri(ImageStrategy.choosePreferredImage(item.thumbnails)?.toUri())
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
builder.setIconUri(imageUriOrNullIfDisabled(it))
}
return MediaBrowserCompat.MediaItem( return MediaBrowserCompat.MediaItem(
builder.build(), builder.build(),

View File

@ -1,6 +1,8 @@
package org.schabi.newpipe.player.ui package org.schabi.newpipe.player.ui
import org.schabi.newpipe.util.GuardedByMutex import org.schabi.newpipe.util.GuardedByMutex
import kotlin.reflect.KClass
import kotlin.reflect.safeCast
/** /**
* Creates a [PlayerUiList] starting with the provided player uis. The provided player uis * Creates a [PlayerUiList] starting with the provided player uis. The provided player uis
@ -84,20 +86,20 @@ class PlayerUiList(vararg initialPlayerUis: PlayerUi) {
* @param T the class type parameter * @param T the class type parameter
* @return the first player UI of the required type found in the list, or null * @return the first player UI of the required type found in the list, or null
</T> */ </T> */
fun <T : PlayerUi> get(playerUiType: Class<T>): T? = fun <T : PlayerUi> get(playerUiType: KClass<T>): T? =
playerUis.runWithLockSync { playerUis.runWithLockSync {
for (ui in lockData) { for (ui in lockData) {
if (playerUiType.isInstance(ui)) { if (playerUiType.isInstance(ui)) {
when (val r = playerUiType.cast(ui)) { // try all UIs before returning null
// try all UIs before returning null playerUiType.safeCast(ui)?.let { return@runWithLockSync it }
null -> continue
else -> return@runWithLockSync r
}
} }
} }
return@runWithLockSync null return@runWithLockSync null
} }
fun <T : PlayerUi> get(playerUiType: Class<T>): T? =
get(playerUiType.kotlin)
/** /**
* Calls the provided consumer on all player UIs in the list, in order of addition. * Calls the provided consumer on all player UIs in the list, in order of addition.
* @param consumer the consumer to call with player UIs * @param consumer the consumer to call with player UIs