From eb019c34be1711e1bcc3c8fa4b14162c20b97feb Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Mon, 14 Oct 2024 14:10:10 +0200 Subject: [PATCH 01/56] Implement lock-screen controls To implement lock-screen controls, a player instance must be 'kept alive', so that the player is not garbage-collected or destroyed by the system when the view is destroyed. There are 2 ways to do this: - init the player on the service itself (like in the Android SDK sample) - init the player as usual, and somehow pass it to the service, to store an additional reference This first version implements the second option. The app has 1 strong reference, and the service has 1 strong reference to the player. So that whenever the app dies, the background playback still goes on. Current Issues: - Whenever the app gets back, the player instance should get fetched back as well! - Another limitation with this implementation is that we're calling `setupMediaSession` on `player.ts`'s `inizitalize()`. But when changing sources (e.g. playback view, click back, playback view), the source in the media session does not get overridden with the source from the new view. --- .../player/reactnative/MediaSessionModule.kt | 78 ++++++++++++++++++ .../player/reactnative/RNPlayerViewPackage.kt | 1 + .../services/MediaSessionPlaybackService.kt | 79 +++++++++++++++++++ .../android/app/src/main/AndroidManifest.xml | 10 +++ example/src/screens/BasicPlayback.tsx | 3 +- src/index.ts | 1 + src/mediaSession/mediaSessionApi.ts | 19 +++++ src/player.ts | 4 + 8 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt create mode 100644 android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt create mode 100644 src/mediaSession/mediaSessionApi.ts diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt new file mode 100644 index 00000000..f71e39b8 --- /dev/null +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt @@ -0,0 +1,78 @@ +package com.bitmovin.player.reactnative + + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import com.bitmovin.player.api.Player +import com.bitmovin.player.reactnative.extensions.playerModule +import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService +import com.facebook.react.bridge.* +import com.facebook.react.module.annotations.ReactModule +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +private const val MODULE_NAME = "MediaSessionModule" +@ReactModule(name = MODULE_NAME) +class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { + override fun getName() = MODULE_NAME + private var playerId: NativeId? = null + + private val _player = MutableStateFlow(null) + val player = _player.asStateFlow() + + private val _serviceBinder = MutableStateFlow(null) + val serviceBinder = _serviceBinder.asStateFlow() + + private val connection = object : ServiceConnection { + override fun onServiceConnected(className: ComponentName, service: IBinder) { + // We've bound to the Service, cast the IBinder and get the Player instance + val binder = service as MediaSessionPlaybackService.ServiceBinder + _serviceBinder.value = binder + +// binder.player = getPlayer("", context.playerModule) +// if(binder.player == null && playerId != null) { + if(playerId != null) { + binder.player = getPlayer(playerId!!, context.playerModule) + } + _player.value = binder.player + +// val player = binder.player!! +// _player.value = player + } + + override fun onServiceDisconnected(name: ComponentName) { + _player.value = null + } + } + + // [!!!] -- on new sources, the media session does not get overridden + // ISSUE: whenever a player instance is created the service gets updated/overridden + // with that instance. + // Because in player.ts we call `setupMediaSession` on `initialize()`. + @ReactMethod + fun setupMediaSession(playerId: NativeId, promise: Promise) { + promise.unit.resolveOnUiThread { + this@MediaSessionModule.playerId = playerId + val intent = Intent(context, MediaSessionPlaybackService::class.java) + intent.putExtra("playerId", playerId) + intent.action = Intent.ACTION_MEDIA_BUTTON + context.bindService(intent, connection, Context.BIND_AUTO_CREATE) + // TODO: is this the same as applicationContext.start/bindService ?? + context.startService(intent) + } + } + // ISSUE2 -- if I start playback, minimize, and restart playback. I should + // re-fetch the state of the player from the service and update + // the player in the view + + // ISSUE3 -- if I start playback, minimize the app, the playback gets paused. + // It should not (it is background playback) + + private fun getPlayer( + nativeId: NativeId, + playerModule: PlayerModule?, + ): Player = playerModule?.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") +} diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt index 807f6f54..f74b1f3f 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt @@ -30,6 +30,7 @@ class RNPlayerViewPackage : ReactPackage { BitmovinCastManagerModule(reactContext), BufferModule(reactContext), NetworkModule(reactContext), + MediaSessionModule(reactContext), ) } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt new file mode 100644 index 00000000..cd9395b5 --- /dev/null +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -0,0 +1,79 @@ +package com.bitmovin.player.reactnative.services + +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import com.bitmovin.player.api.Player +import com.bitmovin.player.api.media.session.MediaSession +import com.bitmovin.player.api.media.session.MediaSessionService + +class MediaSessionPlaybackService : MediaSessionService() { + inner class ServiceBinder : Binder() { + // When the service starts, it creates a player + // When playback activity is created, it gets a binder and + // goes to the service and gets the player from it -- the same player instance. + var player: Player? + get() = this@MediaSessionPlaybackService.player + set(value) { + this@MediaSessionPlaybackService.player = value + value?.let { + createMediaSession(it) + } + } + + fun connectSession() = mediaSession?.let { addSession(it) } + fun disconnectSession() = mediaSession?.let{ + removeSession(it) + it.release() + } + } + + private val binder = ServiceBinder() + private var player: Player? = null + private var mediaSession: MediaSession? = null + + override fun onGetSession(): MediaSession? = mediaSession + + // Player has 2 pointers: one from application, one from service + // but is the same instance. + // So when it loses the application one, it still does not get + // garbage-collected because it still has a strong reference. + override fun onCreate() { + super.onCreate() + // [!!] I need to get the same react-native instance of the player here +// player = Player(this) + + // cannot create mediaSession without a player + mediaSession = MediaSession( + this, + mainLooper, + Player(this), + ) + } + + override fun onDestroy() { + mediaSession?.release() + player?.destroy() + player = null + + super.onDestroy() + } + + override fun onBind(intent: Intent?): IBinder { + super.onBind(intent) + return binder + } + + private fun createMediaSession(player: Player) { + binder.disconnectSession() + + val newMediaSession = MediaSession(// the real media session + this, + mainLooper, + player, + ) + + mediaSession = newMediaSession + binder.connectSession() + } +} \ No newline at end of file diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 7559418f..95a05983 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + + + + + + + diff --git a/example/src/screens/BasicPlayback.tsx b/example/src/screens/BasicPlayback.tsx index 027bad63..8327b6f0 100644 --- a/example/src/screens/BasicPlayback.tsx +++ b/example/src/screens/BasicPlayback.tsx @@ -38,7 +38,8 @@ export default function BasicPlayback() { metadata: { platform: Platform.OS }, }); return () => { - player.destroy(); + // player.destroy();// TODO: this is customers'concerns: they should not destroy in case of background playback + // TODO: even on iOS actually it should not play after player.destroy() }; }, [player]) ); diff --git a/src/index.ts b/src/index.ts index f4219176..c5bd1466 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,3 +25,4 @@ export * from './liveConfig'; export * from './bufferApi'; export * from './network'; export * from './lockScreenControlConfig'; +export * from './mediaSession/mediaSessionApi'; diff --git a/src/mediaSession/mediaSessionApi.ts b/src/mediaSession/mediaSessionApi.ts new file mode 100644 index 00000000..1a1dd643 --- /dev/null +++ b/src/mediaSession/mediaSessionApi.ts @@ -0,0 +1,19 @@ +import { NativeModules } from 'react-native'; + +const MediaSessionModule = NativeModules.MediaSessionModule; + +export class MediaSessionApi { + /** + * The native player id that this buffer api is attached to. + */ + readonly nativeId: string; + + constructor(playerId: string) { + this.nativeId = playerId; + // console.log(NativeModules); + } + + setupMediaSession() { + MediaSessionModule.setupMediaSession(this.nativeId); + } +} diff --git a/src/player.ts b/src/player.ts index e936a87e..926c0126 100644 --- a/src/player.ts +++ b/src/player.ts @@ -11,6 +11,7 @@ import { AdItem } from './advertising'; import { BufferApi } from './bufferApi'; import { VideoQuality } from './media'; import { Network } from './network'; +import { MediaSessionApi } from './mediaSession/mediaSessionApi'; const PlayerModule = NativeModules.PlayerModule; @@ -47,6 +48,8 @@ export class Player extends NativeInstance { * The {@link BufferApi} for interactions regarding the buffer. */ buffer: BufferApi = new BufferApi(this.nativeId); + // TODO: docs + mediaSession: MediaSessionApi = new MediaSessionApi(this.nativeId); /** * Allocates the native `Player` instance and its resources natively. @@ -73,6 +76,7 @@ export class Player extends NativeInstance { this.network?.nativeId ); } + this.mediaSession.setupMediaSession(); this.isInitialized = true; } }; From 1579ed5c93e44adc3ea00eae36d145fe0df70554 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 16 Oct 2024 11:08:56 +0200 Subject: [PATCH 02/56] Move the player instance creation to the service --- .../player/reactnative/BitmovinBaseModule.kt | 9 +++ .../player/reactnative/MediaSessionModule.kt | 68 +++++++++++++++---- .../player/reactnative/PlayerModule.kt | 40 +++++++++++ .../services/MediaSessionPlaybackService.kt | 19 +++++- .../android/app/src/main/AndroidManifest.xml | 2 +- example/src/screens/BasicPlayback.tsx | 11 +++ src/mediaSession/mediaSessionApi.ts | 21 ++++-- src/player.ts | 6 +- 8 files changed, 155 insertions(+), 21 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt index 145f455b..8054dd4b 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt @@ -40,6 +40,15 @@ abstract class BitmovinBaseModule( } } + protected inline fun TPromise.runOnUiThread( + crossinline block: () -> Unit, + ) { + val uiManager = runAndRejectOnException { uiManager } ?: return + uiManager.addUIBlock { + block() + } + } + protected val RejectPromiseOnExceptionBlock.playerModule: PlayerModule get() = context.playerModule ?: throw IllegalArgumentException("PlayerModule not found") diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt index f71e39b8..bc9b72af 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt @@ -18,7 +18,6 @@ private const val MODULE_NAME = "MediaSessionModule" @ReactModule(name = MODULE_NAME) class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { override fun getName() = MODULE_NAME - private var playerId: NativeId? = null private val _player = MutableStateFlow(null) val player = _player.asStateFlow() @@ -26,21 +25,24 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( private val _serviceBinder = MutableStateFlow(null) val serviceBinder = _serviceBinder.asStateFlow() - private val connection = object : ServiceConnection { + inner class MediaSessionServiceConnection( + val playerPromise: Promise + ): ServiceConnection { + // we need a promise here, so that we can resolve it here instead of in setupMediaSession + // subclass ServiceConnection and pass promise override fun onServiceConnected(className: ComponentName, service: IBinder) { // We've bound to the Service, cast the IBinder and get the Player instance val binder = service as MediaSessionPlaybackService.ServiceBinder _serviceBinder.value = binder - -// binder.player = getPlayer("", context.playerModule) -// if(binder.player == null && playerId != null) { - if(playerId != null) { - binder.player = getPlayer(playerId!!, context.playerModule) - } _player.value = binder.player -// val player = binder.player!! -// _player.value = player + // after all of this, resolve the promise with the playerId + // so we can do in react-native: + // this.nativeId = this.mediaSession.setupMediaSession(); + + playerPromise.unit.resolveOnUiThread { + binder.playerNativeId + } } override fun onServiceDisconnected(name: ComponentName) { @@ -48,18 +50,49 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( } } +// private val connection = object : ServiceConnection { +// +// // we need a promise here, so that we can resolve it here instead of in setupMediaSession +// // subclass ServiceConnection and pass promise +// override fun onServiceConnected(className: ComponentName, service: IBinder) { +// // We've bound to the Service, cast the IBinder and get the Player instance +// val binder = service as MediaSessionPlaybackService.ServiceBinder +// _serviceBinder.value = binder +// +//// binder.player = getPlayer("", context.playerModule) +//// if(binder.player == null && playerId != null) { +// if(playerId != null) { +// binder.player = getPlayer(playerId!!, context.playerModule) +// } +// _player.value = binder.player +// +//// val player = binder.player!! +//// _player.value = player +// } +// +// override fun onServiceDisconnected(name: ComponentName) { +// _player.value = null +// } +// } + + // [!!!] -- on new sources, the media session does not get overridden - // ISSUE: whenever a player instance is created the service gets updated/overridden - // with that instance. + // ISSUE: whenever a player instance is created the service does not get + // updated/overridden with that instance. // Because in player.ts we call `setupMediaSession` on `initialize()`. @ReactMethod fun setupMediaSession(playerId: NativeId, promise: Promise) { - promise.unit.resolveOnUiThread { + // if there is an existing media session session, change its content + // the change should be on play actually, not on player creation + + promise.unit.runOnUiThread {// too: this runOnUiThread should be not under the promise scope (promise.) this@MediaSessionModule.playerId = playerId val intent = Intent(context, MediaSessionPlaybackService::class.java) - intent.putExtra("playerId", playerId) intent.action = Intent.ACTION_MEDIA_BUTTON + + val connection = MediaSessionServiceConnection(promise) context.bindService(intent, connection, Context.BIND_AUTO_CREATE) + // TODO: is this the same as applicationContext.start/bindService ?? context.startService(intent) } @@ -71,8 +104,15 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( // ISSUE3 -- if I start playback, minimize the app, the playback gets paused. // It should not (it is background playback) + // ISSUE4 -- if `loadSource()` is a different source than the one playing in the + // service, load that instead. + private fun getPlayer( nativeId: NativeId, playerModule: PlayerModule?, ): Player = playerModule?.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") } + +// TODO: check how the background playback feature on Android +// TODO: check lock-screen control on iOS: +// what happens if I start art-of-motion, and go back to menu (is the played destroyed?) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 7922fb4c..982ca743 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -1,5 +1,8 @@ package com.bitmovin.player.reactnative +import android.content.ComponentName +import android.content.ServiceConnection +import android.os.IBinder import android.util.Log import com.bitmovin.analytics.api.DefaultMetadata import com.bitmovin.player.api.Player @@ -12,8 +15,11 @@ import com.bitmovin.player.reactnative.converter.toAnalyticsDefaultMetadata import com.bitmovin.player.reactnative.converter.toJson import com.bitmovin.player.reactnative.converter.toPlayerConfig import com.bitmovin.player.reactnative.extensions.mapToReactArray +import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService import com.facebook.react.bridge.* import com.facebook.react.module.annotations.ReactModule +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import java.security.InvalidParameterException private const val MODULE_NAME = "PlayerModule" @@ -90,6 +96,40 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex defaultMetadata = defaultMetadata ?: DefaultMetadata(), ) } + + // FINAL: (ideas) + // If config is enabled, create the Intent here to start/bind the service -- setupMediaSession() + // Put the nativeId in the thread + + } + + // where is the player created + // how the player instance is given to the service (binder setting player vs playerId passing) + + private val _player = MutableStateFlow(null) + val player = _player.asStateFlow() + + private val _serviceBinder = MutableStateFlow(null) + val serviceBinder = _serviceBinder.asStateFlow() + + inner class MediaSessionServiceConnection( + val playerPromise: Promise + ): ServiceConnection { + override fun onServiceConnected(className: ComponentName, service: IBinder) { + // We've bound to the Service, cast the IBinder and get the Player instance + val binder = service as MediaSessionPlaybackService.ServiceBinder + _serviceBinder.value = binder + binder.player = + + + playerPromise.unit.resolveOnUiThread { + binder.playerNativeId + } + } + + override fun onServiceDisconnected(name: ComponentName) { + _player.value = null + } } /** diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index cd9395b5..6c8f4efb 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -6,9 +6,24 @@ import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.api.media.session.MediaSession import com.bitmovin.player.api.media.session.MediaSessionService +import com.bitmovin.player.reactnative.BitmovinBaseModule +import com.bitmovin.player.reactnative.MODULE_NAME +import com.bitmovin.player.reactnative.NativeId +import com.facebook.react.bridge.* +import com.facebook.react.module.annotations.ReactModule class MediaSessionPlaybackService : MediaSessionService() { + @ReactModule(name = "test") + inner class TestModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { + override fun getName(): String { + // TODO: read background investigation on Confluence + return "test" + } + } + inner class ServiceBinder : Binder() { + var playerNativeId: NativeId + // When the service starts, it creates a player // When playback activity is created, it gets a binder and // goes to the service and gets the player from it -- the same player instance. @@ -44,10 +59,12 @@ class MediaSessionPlaybackService : MediaSessionService() { // player = Player(this) // cannot create mediaSession without a player + // TODO: call playerModule.createPlayer to actually create a player + // so then we'll go to `mediaSessionModule.. onServiceConnected` mediaSession = MediaSession( this, mainLooper, - Player(this), + Player(this),// use the playermodule's player here ) } diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 95a05983..edd84fd4 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -54,7 +54,7 @@ android:value="com.bitmovin.player.casting.BitmovinCastOptionsProvider" /> diff --git a/example/src/screens/BasicPlayback.tsx b/example/src/screens/BasicPlayback.tsx index 8327b6f0..ebb62921 100644 --- a/example/src/screens/BasicPlayback.tsx +++ b/example/src/screens/BasicPlayback.tsx @@ -16,10 +16,21 @@ function prettyPrint(header: string, obj: any) { export default function BasicPlayback() { useTVGestures(); + // when we want background playback, + // from native start the service and actually init the player + // from there (Android) -- or give the player to the service. + // + // so with background playblack enabled, there must be only a single + // player instance existing, in the service + // + // But the first time (app start), when the service is not existing yet, + // but we'll start it + const player = usePlayer({ remoteControlConfig: { isCastEnabled: false, }, + // e.g. background playback / lock-screen-controls : true }); useFocusEffect( diff --git a/src/mediaSession/mediaSessionApi.ts b/src/mediaSession/mediaSessionApi.ts index 1a1dd643..b9963187 100644 --- a/src/mediaSession/mediaSessionApi.ts +++ b/src/mediaSession/mediaSessionApi.ts @@ -9,11 +9,24 @@ export class MediaSessionApi { readonly nativeId: string; constructor(playerId: string) { + // if service exists, take native id from it, and dont use playerId at all + this.nativeId = playerId; - // console.log(NativeModules); + // FINAL: + // when player is created on RN, it always creates a player on native side + // and sets itself in-charge of the service. So the last player will be the + // one in charge. + // So I'll never go and retrieve the instance from the service, but just put it. } - setupMediaSession() { - MediaSessionModule.setupMediaSession(this.nativeId); - } + /** + * Sets up the Media Session for the Player. + * In case there is already an existing Media Session, the player will be put + * in charge of it. + * + * @returns the native player ID which is in charge of the Media Session. + */ + setupMediaSession = async (): Promise => { + return MediaSessionModule.setupMediaSession(this.nativeId); + }; } diff --git a/src/player.ts b/src/player.ts index 926c0126..a1e4aa7d 100644 --- a/src/player.ts +++ b/src/player.ts @@ -56,6 +56,11 @@ export class Player extends NativeInstance { */ initialize = () => { if (!this.isInitialized) { + // check if media session service exists, + // if so grab the player instance from there + this.mediaSession.setupMediaSession(); + // this.nativeId = this.mediaSession.setupMediaSession(); + if (this.config?.networkConfig) { this.network = new Network(this.config.networkConfig); this.network.initialize(); @@ -76,7 +81,6 @@ export class Player extends NativeInstance { this.network?.nativeId ); } - this.mediaSession.setupMediaSession(); this.isInitialized = true; } }; From dc1f4bfd6a0a629e7ccc1e74bd494d01535537a3 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 16 Oct 2024 13:46:10 +0200 Subject: [PATCH 03/56] Bind the player correctly to the service on player creation Via native by using player module Note: The actual media session module can just be a native one (like offline module) as it is just called via native (player module) --- .../player/reactnative/BitmovinBaseModule.kt | 4 ++ .../player/reactnative/MediaSessionModule.kt | 59 ++++++++----------- .../player/reactnative/PlayerModule.kt | 36 +++-------- .../extensions/ReactContextExtension.kt | 2 + .../services/MediaSessionPlaybackService.kt | 16 +++-- src/mediaSession/mediaSessionApi.ts | 10 ++-- src/player.ts | 2 +- 7 files changed, 52 insertions(+), 77 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt index 8054dd4b..ef9c1573 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt @@ -4,6 +4,7 @@ import android.util.Log import com.bitmovin.player.api.Player import com.bitmovin.player.api.source.Source import com.bitmovin.player.reactnative.extensions.drmModule +import com.bitmovin.player.reactnative.extensions.mediaSessionModule import com.bitmovin.player.reactnative.extensions.networkModule import com.bitmovin.player.reactnative.extensions.offlineModule import com.bitmovin.player.reactnative.extensions.playerModule @@ -67,6 +68,9 @@ abstract class BitmovinBaseModule( protected val RejectPromiseOnExceptionBlock.networkModule: NetworkModule get() = context.networkModule ?: throw IllegalStateException("NetworkModule not found") + protected val RejectPromiseOnExceptionBlock.mediaSessionModule: MediaSessionModule + get() = context.mediaSessionModule ?: throw IllegalStateException("MediaSessionModule not found") + fun RejectPromiseOnExceptionBlock.getPlayer( nativeId: NativeId, playerModule: PlayerModule = this.playerModule, diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt index bc9b72af..7877faeb 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt @@ -19,30 +19,23 @@ private const val MODULE_NAME = "MediaSessionModule" class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { override fun getName() = MODULE_NAME + private lateinit var playerId: NativeId + private val _player = MutableStateFlow(null) val player = _player.asStateFlow() private val _serviceBinder = MutableStateFlow(null) val serviceBinder = _serviceBinder.asStateFlow() - inner class MediaSessionServiceConnection( - val playerPromise: Promise - ): ServiceConnection { + inner class MediaSessionServiceConnection: ServiceConnection { // we need a promise here, so that we can resolve it here instead of in setupMediaSession // subclass ServiceConnection and pass promise override fun onServiceConnected(className: ComponentName, service: IBinder) { // We've bound to the Service, cast the IBinder and get the Player instance val binder = service as MediaSessionPlaybackService.ServiceBinder _serviceBinder.value = binder - _player.value = binder.player - - // after all of this, resolve the promise with the playerId - // so we can do in react-native: - // this.nativeId = this.mediaSession.setupMediaSession(); - - playerPromise.unit.resolveOnUiThread { - binder.playerNativeId - } +// binder.playerNativeId = playerId + binder.player = getPlayer() } override fun onServiceDisconnected(name: ComponentName) { @@ -81,38 +74,38 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( // updated/overridden with that instance. // Because in player.ts we call `setupMediaSession` on `initialize()`. @ReactMethod - fun setupMediaSession(playerId: NativeId, promise: Promise) { + fun setupMediaSession(playerId: NativeId) { // if there is an existing media session session, change its content // the change should be on play actually, not on player creation - promise.unit.runOnUiThread {// too: this runOnUiThread should be not under the promise scope (promise.) - this@MediaSessionModule.playerId = playerId - val intent = Intent(context, MediaSessionPlaybackService::class.java) - intent.action = Intent.ACTION_MEDIA_BUTTON - val connection = MediaSessionServiceConnection(promise) - context.bindService(intent, connection, Context.BIND_AUTO_CREATE) - - // TODO: is this the same as applicationContext.start/bindService ?? - context.startService(intent) - } - } - // ISSUE2 -- if I start playback, minimize, and restart playback. I should - // re-fetch the state of the player from the service and update - // the player in the view + this@MediaSessionModule.playerId = playerId + val intent = Intent(context, MediaSessionPlaybackService::class.java) + intent.action = Intent.ACTION_MEDIA_BUTTON - // ISSUE3 -- if I start playback, minimize the app, the playback gets paused. - // It should not (it is background playback) + val connection = MediaSessionServiceConnection() + context.bindService(intent, connection, Context.BIND_AUTO_CREATE) - // ISSUE4 -- if `loadSource()` is a different source than the one playing in the - // service, load that instead. + // TODO: is this the same as applicationContext.start/bindService ?? + context.startService(intent) + } private fun getPlayer( - nativeId: NativeId, - playerModule: PlayerModule?, + nativeId: NativeId = playerId, + playerModule: PlayerModule? = context.playerModule, ): Player = playerModule?.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") } // TODO: check how the background playback feature on Android // TODO: check lock-screen control on iOS: // what happens if I start art-of-motion, and go back to menu (is the played destroyed?) + +// ISSUE2 -- if I start playback, minimize, and restart playback. I should +// re-fetch the state of the player from the service and update +// the player in the view + +// ISSUE3 -- if I start playback, minimize the app, the playback gets paused. +// It should not (it is background playback) + +// ISSUE4 -- if `loadSource()` is a different source than the one playing in the +// service, load that instead. diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 982ca743..a5e6a4c8 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -101,37 +101,17 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex // If config is enabled, create the Intent here to start/bind the service -- setupMediaSession() // Put the nativeId in the thread - } - - // where is the player created - // how the player instance is given to the service (binder setting player vs playerId passing) - - private val _player = MutableStateFlow(null) - val player = _player.asStateFlow() - - private val _serviceBinder = MutableStateFlow(null) - val serviceBinder = _serviceBinder.asStateFlow() - - inner class MediaSessionServiceConnection( - val playerPromise: Promise - ): ServiceConnection { - override fun onServiceConnected(className: ComponentName, service: IBinder) { - // We've bound to the Service, cast the IBinder and get the Player instance - val binder = service as MediaSessionPlaybackService.ServiceBinder - _serviceBinder.value = binder - binder.player = - - - playerPromise.unit.resolveOnUiThread { - binder.playerNativeId - } - } - - override fun onServiceDisconnected(name: ComponentName) { - _player.value = null +// if (playerConfig.lockScreenConfig.isEnabled) { + promise.unit.resolveOnUiThread { + mediaSessionModule + .setupMediaSession(nativeId) } } + // FINAL: 2 issues to solve: + // - where is the player created + // - how the player instance is given to the service (binder setting player vs playerId passing) + /** * Load the source of the given [nativeId] with `config` options from JS. * @param nativeId Target player. diff --git a/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt b/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt index 64090363..31be9a22 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt @@ -1,6 +1,7 @@ package com.bitmovin.player.reactnative.extensions import com.bitmovin.player.reactnative.DrmModule +import com.bitmovin.player.reactnative.MediaSessionModule import com.bitmovin.player.reactnative.NetworkModule import com.bitmovin.player.reactnative.OfflineModule import com.bitmovin.player.reactnative.PlayerModule @@ -20,3 +21,4 @@ val ReactApplicationContext.uiManagerModule get() = getModule() val ReactApplicationContext.drmModule get() = getModule() val ReactApplicationContext.customMessageHandlerModule get() = getModule() val ReactApplicationContext.networkModule get() = getModule() +val ReactApplicationContext.mediaSessionModule get() = getModule() diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index 6c8f4efb..5d2b96f2 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -7,22 +7,20 @@ import com.bitmovin.player.api.Player import com.bitmovin.player.api.media.session.MediaSession import com.bitmovin.player.api.media.session.MediaSessionService import com.bitmovin.player.reactnative.BitmovinBaseModule -import com.bitmovin.player.reactnative.MODULE_NAME import com.bitmovin.player.reactnative.NativeId import com.facebook.react.bridge.* import com.facebook.react.module.annotations.ReactModule class MediaSessionPlaybackService : MediaSessionService() { - @ReactModule(name = "test") - inner class TestModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { - override fun getName(): String { - // TODO: read background investigation on Confluence - return "test" - } - } +// @ReactModule(name = "test") +// inner class TestModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { +// override fun getName(): String { +// // TODO: read background investigation on Confluence +// } +// } inner class ServiceBinder : Binder() { - var playerNativeId: NativeId +// lateinit var playerNativeId: NativeId // When the service starts, it creates a player // When playback activity is created, it gets a binder and diff --git a/src/mediaSession/mediaSessionApi.ts b/src/mediaSession/mediaSessionApi.ts index b9963187..cb7fce12 100644 --- a/src/mediaSession/mediaSessionApi.ts +++ b/src/mediaSession/mediaSessionApi.ts @@ -16,17 +16,15 @@ export class MediaSessionApi { // when player is created on RN, it always creates a player on native side // and sets itself in-charge of the service. So the last player will be the // one in charge. - // So I'll never go and retrieve the instance from the service, but just put it. + // So I'll never go and retrieve the instance from the service, but just put it. } /** * Sets up the Media Session for the Player. * In case there is already an existing Media Session, the player will be put * in charge of it. - * - * @returns the native player ID which is in charge of the Media Session. */ - setupMediaSession = async (): Promise => { - return MediaSessionModule.setupMediaSession(this.nativeId); - }; + // setupMediaSession = async (): Promise => { + // return MediaSessionModule.setupMediaSession(this.nativeId); + // }; } diff --git a/src/player.ts b/src/player.ts index a1e4aa7d..b9ebaf9c 100644 --- a/src/player.ts +++ b/src/player.ts @@ -58,7 +58,7 @@ export class Player extends NativeInstance { if (!this.isInitialized) { // check if media session service exists, // if so grab the player instance from there - this.mediaSession.setupMediaSession(); + // this.mediaSession.setupMediaSession(); // this.nativeId = this.mediaSession.setupMediaSession(); if (this.config?.networkConfig) { From f08ca298cbb5f0449d09bbc549c288fd1b91e211 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 16 Oct 2024 13:47:29 +0200 Subject: [PATCH 04/56] Remove media session module from typescript This module will be Android-only. --- src/index.ts | 1 - src/mediaSession/mediaSessionApi.ts | 30 ----------------------------- src/player.ts | 8 -------- 3 files changed, 39 deletions(-) delete mode 100644 src/mediaSession/mediaSessionApi.ts diff --git a/src/index.ts b/src/index.ts index c5bd1466..f4219176 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,4 +25,3 @@ export * from './liveConfig'; export * from './bufferApi'; export * from './network'; export * from './lockScreenControlConfig'; -export * from './mediaSession/mediaSessionApi'; diff --git a/src/mediaSession/mediaSessionApi.ts b/src/mediaSession/mediaSessionApi.ts deleted file mode 100644 index cb7fce12..00000000 --- a/src/mediaSession/mediaSessionApi.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NativeModules } from 'react-native'; - -const MediaSessionModule = NativeModules.MediaSessionModule; - -export class MediaSessionApi { - /** - * The native player id that this buffer api is attached to. - */ - readonly nativeId: string; - - constructor(playerId: string) { - // if service exists, take native id from it, and dont use playerId at all - - this.nativeId = playerId; - // FINAL: - // when player is created on RN, it always creates a player on native side - // and sets itself in-charge of the service. So the last player will be the - // one in charge. - // So I'll never go and retrieve the instance from the service, but just put it. - } - - /** - * Sets up the Media Session for the Player. - * In case there is already an existing Media Session, the player will be put - * in charge of it. - */ - // setupMediaSession = async (): Promise => { - // return MediaSessionModule.setupMediaSession(this.nativeId); - // }; -} diff --git a/src/player.ts b/src/player.ts index b9ebaf9c..e936a87e 100644 --- a/src/player.ts +++ b/src/player.ts @@ -11,7 +11,6 @@ import { AdItem } from './advertising'; import { BufferApi } from './bufferApi'; import { VideoQuality } from './media'; import { Network } from './network'; -import { MediaSessionApi } from './mediaSession/mediaSessionApi'; const PlayerModule = NativeModules.PlayerModule; @@ -48,19 +47,12 @@ export class Player extends NativeInstance { * The {@link BufferApi} for interactions regarding the buffer. */ buffer: BufferApi = new BufferApi(this.nativeId); - // TODO: docs - mediaSession: MediaSessionApi = new MediaSessionApi(this.nativeId); /** * Allocates the native `Player` instance and its resources natively. */ initialize = () => { if (!this.isInitialized) { - // check if media session service exists, - // if so grab the player instance from there - // this.mediaSession.setupMediaSession(); - // this.nativeId = this.mediaSession.setupMediaSession(); - if (this.config?.networkConfig) { this.network = new Network(this.config.networkConfig); this.network.initialize(); From 07a70c61ab5b55da0a6004e3dcbb79af5006be33 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 16 Oct 2024 15:01:54 +0200 Subject: [PATCH 05/56] Fix media session player not getting replaced properly The old player was not getting destroyed when a new one is being put in charge of the media session --- .../player/reactnative/MediaSessionModule.kt | 45 +++++-------------- .../services/MediaSessionPlaybackService.kt | 21 +++------ 2 files changed, 16 insertions(+), 50 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt index 7877faeb..4bb64fb5 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt @@ -19,10 +19,11 @@ private const val MODULE_NAME = "MediaSessionModule" class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { override fun getName() = MODULE_NAME + private var isServiceStarted = false private lateinit var playerId: NativeId private val _player = MutableStateFlow(null) - val player = _player.asStateFlow() +// val player = _player.asStateFlow() private val _serviceBinder = MutableStateFlow(null) val serviceBinder = _serviceBinder.asStateFlow() @@ -34,7 +35,6 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( // We've bound to the Service, cast the IBinder and get the Player instance val binder = service as MediaSessionPlaybackService.ServiceBinder _serviceBinder.value = binder -// binder.playerNativeId = playerId binder.player = getPlayer() } @@ -43,31 +43,6 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( } } -// private val connection = object : ServiceConnection { -// -// // we need a promise here, so that we can resolve it here instead of in setupMediaSession -// // subclass ServiceConnection and pass promise -// override fun onServiceConnected(className: ComponentName, service: IBinder) { -// // We've bound to the Service, cast the IBinder and get the Player instance -// val binder = service as MediaSessionPlaybackService.ServiceBinder -// _serviceBinder.value = binder -// -//// binder.player = getPlayer("", context.playerModule) -//// if(binder.player == null && playerId != null) { -// if(playerId != null) { -// binder.player = getPlayer(playerId!!, context.playerModule) -// } -// _player.value = binder.player -// -//// val player = binder.player!! -//// _player.value = player -// } -// -// override fun onServiceDisconnected(name: ComponentName) { -// _player.value = null -// } -// } - // [!!!] -- on new sources, the media session does not get overridden // ISSUE: whenever a player instance is created the service does not get @@ -78,7 +53,6 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( // if there is an existing media session session, change its content // the change should be on play actually, not on player creation - this@MediaSessionModule.playerId = playerId val intent = Intent(context, MediaSessionPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON @@ -86,19 +60,22 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( val connection = MediaSessionServiceConnection() context.bindService(intent, connection, Context.BIND_AUTO_CREATE) - // TODO: is this the same as applicationContext.start/bindService ?? - context.startService(intent) + if (!isServiceStarted) { + context.startService(intent) + isServiceStarted = true + } } private fun getPlayer( nativeId: NativeId = playerId, playerModule: PlayerModule? = context.playerModule, ): Player = playerModule?.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") -} -// TODO: check how the background playback feature on Android -// TODO: check lock-screen control on iOS: -// what happens if I start art-of-motion, and go back to menu (is the played destroyed?) + /// FINE + // - add LockScreenConfig serializers + // - add a lock-screen control sample: only there the config is set to true + // - fix player is paused when app is minimized: see android codebase: implement Activity lfiecycle onStart and onStop and detach player +} // ISSUE2 -- if I start playback, minimize, and restart playback. I should // re-fetch the state of the player from the service and update diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index 5d2b96f2..9f8712d9 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -6,28 +6,16 @@ import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.api.media.session.MediaSession import com.bitmovin.player.api.media.session.MediaSessionService -import com.bitmovin.player.reactnative.BitmovinBaseModule -import com.bitmovin.player.reactnative.NativeId -import com.facebook.react.bridge.* -import com.facebook.react.module.annotations.ReactModule class MediaSessionPlaybackService : MediaSessionService() { -// @ReactModule(name = "test") -// inner class TestModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { -// override fun getName(): String { -// // TODO: read background investigation on Confluence -// } -// } - inner class ServiceBinder : Binder() { -// lateinit var playerNativeId: NativeId - // When the service starts, it creates a player // When playback activity is created, it gets a binder and // goes to the service and gets the player from it -- the same player instance. var player: Player? get() = this@MediaSessionPlaybackService.player set(value) { + this@MediaSessionPlaybackService.player?.destroy() this@MediaSessionPlaybackService.player = value value?.let { createMediaSession(it) @@ -54,7 +42,7 @@ class MediaSessionPlaybackService : MediaSessionService() { override fun onCreate() { super.onCreate() // [!!] I need to get the same react-native instance of the player here -// player = Player(this) + val player = this.player ?: Player(this) // cannot create mediaSession without a player // TODO: call playerModule.createPlayer to actually create a player @@ -62,12 +50,13 @@ class MediaSessionPlaybackService : MediaSessionService() { mediaSession = MediaSession( this, mainLooper, - Player(this),// use the playermodule's player here + player, ) } override fun onDestroy() { - mediaSession?.release() + binder.disconnectSession() + player?.destroy() player = null From 257ca00a56636ad77d55034122cb67eaa7f52302 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 17 Oct 2024 14:46:39 +0200 Subject: [PATCH 06/56] Introduce the lock-screen controls sample --- .../player/reactnative/PlayerModule.kt | 11 ++- .../reactnative/converter/JsonConverter.kt | 16 ++++ example/src/App.tsx | 11 +++ example/src/screens/LockScreenControls.tsx | 87 +++++++++++++++++++ 4 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 example/src/screens/LockScreenControls.tsx diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index a5e6a4c8..94044a78 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -9,6 +9,7 @@ import com.bitmovin.player.api.Player import com.bitmovin.player.api.PlayerConfig import com.bitmovin.player.api.analytics.create import com.bitmovin.player.api.event.PlayerEvent +import com.bitmovin.player.reactnative.converter.lockScreenControlConfig import com.bitmovin.player.reactnative.converter.toAdItem import com.bitmovin.player.reactnative.converter.toAnalyticsConfig import com.bitmovin.player.reactnative.converter.toAnalyticsDefaultMetadata @@ -101,10 +102,12 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex // If config is enabled, create the Intent here to start/bind the service -- setupMediaSession() // Put the nativeId in the thread -// if (playerConfig.lockScreenConfig.isEnabled) { - promise.unit.resolveOnUiThread { - mediaSessionModule - .setupMediaSession(nativeId) + if (playerConfig.lockScreenControlConfig.isEnabled) { +// playerConfig.playbackConfig + promise.unit.resolveOnUiThread { + mediaSessionModule + .setupMediaSession(nativeId) + } } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index bb83e8ca..474ec0b6 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -92,6 +92,7 @@ fun ReadableMap.toPlayerConfig(): PlayerConfig = PlayerConfig(key = getString("l withMap("bufferConfig") { bufferConfig = it.toBufferConfig() } withMap("liveConfig") { liveConfig = it.toLiveConfig() } withMap("networkConfig") { networkConfig = it.toNetworkConfig() } + withMap("lockScreenControlConfig") { lockScreenControlConfig = it.toLockScreenControlConfig() } } /** @@ -883,6 +884,21 @@ fun String.toMediaType(): MediaType? = when (this) { else -> null } +data class LockScreenControlConfig( + var isEnabled: Boolean = false +) + +fun ReadableMap.toLockScreenControlConfig(): LockScreenControlConfig = LockScreenControlConfig().apply { + withBoolean("isEnabled") { isEnabled = it } +} + +private val lockScreenConfigMap = mutableMapOf() +var PlayerConfig.lockScreenControlConfig: LockScreenControlConfig + get() = lockScreenConfigMap[this] ?: LockScreenControlConfig() + set(value) { + lockScreenConfigMap[this] = value + } + /** * Converts a [CastPayload] object into its JS representation. */ diff --git a/example/src/App.tsx b/example/src/App.tsx index 54799276..a09e95e2 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -20,6 +20,7 @@ import LandscapeFullscreenHandling from './screens/LandscapeFullscreenHandling'; import SystemUI from './screens/SystemUi'; import OfflinePlayback from './screens/OfflinePlayback'; import Casting from './screens/Casting'; +import LockScreenControls from './screens/LockScreenControls'; export type RootStackParamsList = { ExamplesList: { @@ -58,6 +59,7 @@ export type RootStackParamsList = { }; Casting: undefined; SystemUI: undefined; + LockScreenControls: undefined; }; const RootStack = createNativeStackNavigator(); @@ -109,6 +111,10 @@ export default function App() { title: 'Programmatic Track Selection', routeName: 'ProgrammaticTrackSelection' as keyof RootStackParamsList, }, + { + title: 'Lock-Screen Controls', + routeName: 'LockScreenControls' as keyof RootStackParamsList, + }, ], }; @@ -262,6 +268,11 @@ export default function App() { options={{ title: 'Casting' }} /> )} + ); diff --git a/example/src/screens/LockScreenControls.tsx b/example/src/screens/LockScreenControls.tsx new file mode 100644 index 00000000..2d4a0dda --- /dev/null +++ b/example/src/screens/LockScreenControls.tsx @@ -0,0 +1,87 @@ +import React, { useCallback } from 'react'; +import { View, Platform, StyleSheet } from 'react-native'; +import { useFocusEffect } from '@react-navigation/native'; +import { + Event, + usePlayer, + PlayerView, + SourceType, +} from 'bitmovin-player-react-native'; +import { useTVGestures } from '../hooks'; + +function prettyPrint(header: string, obj: any) { + console.log(header, JSON.stringify(obj, null, 2)); +} + +export default function LockScreenControls() { + useTVGestures(); + + const player = usePlayer({ + lockScreenControlConfig: { + isEnabled: true, + }, + remoteControlConfig: { + isCastEnabled: false, + }, + }); + + useFocusEffect( + useCallback(() => { + player.load({ + url: + Platform.OS === 'ios' + ? 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8' + : 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd', + type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH, + title: 'Art of Motion', + poster: + 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/poster.jpg', + thumbnailTrack: + 'https://cdn.bitmovin.com/content/assets/art-of-motion-dash-hls-progressive/thumbnails/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.vtt', + metadata: { platform: Platform.OS }, + }); + return () => { + player.destroy(); + }; + }, [player]) + ); + + const onReady = useCallback((event: Event) => { + prettyPrint(`EVENT [${event.name}]`, event); + }, []); + + const onEvent = useCallback((event: Event) => { + prettyPrint(`EVENT [${event.name}]`, event); + }, []); + + return ( + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'black', + }, + player: { + flex: 1, + }, +}); From ae8818678fa53e74236438d306c69c7a06831c8f Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 17 Oct 2024 14:47:39 +0200 Subject: [PATCH 07/56] Revert changes to basic playback --- example/src/screens/BasicPlayback.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/example/src/screens/BasicPlayback.tsx b/example/src/screens/BasicPlayback.tsx index ebb62921..027bad63 100644 --- a/example/src/screens/BasicPlayback.tsx +++ b/example/src/screens/BasicPlayback.tsx @@ -16,21 +16,10 @@ function prettyPrint(header: string, obj: any) { export default function BasicPlayback() { useTVGestures(); - // when we want background playback, - // from native start the service and actually init the player - // from there (Android) -- or give the player to the service. - // - // so with background playblack enabled, there must be only a single - // player instance existing, in the service - // - // But the first time (app start), when the service is not existing yet, - // but we'll start it - const player = usePlayer({ remoteControlConfig: { isCastEnabled: false, }, - // e.g. background playback / lock-screen-controls : true }); useFocusEffect( @@ -49,8 +38,7 @@ export default function BasicPlayback() { metadata: { platform: Platform.OS }, }); return () => { - // player.destroy();// TODO: this is customers'concerns: they should not destroy in case of background playback - // TODO: even on iOS actually it should not play after player.destroy() + player.destroy(); }; }, [player]) ); From 140317a127fad81f31b1563b3325e173b3ec1a48 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 17 Oct 2024 15:29:58 +0200 Subject: [PATCH 08/56] Fix auto-pausing when minimizing the app This has to be handled via the activity lifecycle. Took `BackgroundPlaybackScreen` from Android SDK samples as the example Also fix `playerEventRelay` being null in some conditions --- .../player/reactnative/MediaSessionModule.kt | 2 +- .../player/reactnative/RNPlayerView.kt | 51 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt index 4bb64fb5..8ef00a5a 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt @@ -23,7 +23,7 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( private lateinit var playerId: NativeId private val _player = MutableStateFlow(null) -// val player = _player.asStateFlow() + val player = _player.asStateFlow() private val _serviceBinder = MutableStateFlow(null) val serviceBinder = _serviceBinder.asStateFlow() diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 647cf14c..bea3949e 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -16,6 +16,7 @@ import com.bitmovin.player.api.event.SourceEvent import com.bitmovin.player.api.ui.PlayerViewConfig import com.bitmovin.player.api.ui.StyleConfig import com.bitmovin.player.reactnative.converter.toJson +import com.bitmovin.player.reactnative.extensions.mediaSessionModule import com.facebook.react.ReactActivity import com.facebook.react.bridge.* import com.facebook.react.uimanager.events.RCTEventEmitter @@ -102,8 +103,38 @@ class RNPlayerView( private val activityLifecycle = (context.currentActivity as? ReactActivity)?.lifecycle ?: error("Trying to create an instance of ${this::class.simpleName} while not attached to a ReactActivity") + /** + * Relays the provided set of events, emitted by the player, together with the associated name + * to the `eventOutput` callback. + */ + private var playerEventRelay: EventRelay + +// val viewModelPlayer = context.mediaSessionModule?.player?.value + + val viewModelPlayer: Player? + get() = context.mediaSessionModule?.player?.value + +// // Don't stop the player when going to background +// override fun onStart(playerView: PlayerView) { +// playerView.player = player +// super.onStart(playerView) +// } +// +// override fun onStop(playerView: PlayerView) { +// playerView.player = null +// super.onStop(playerView) +// } + private val activityLifecycleObserver = object : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { +// if (context.mediaSessionModule?.player?.value != null) { +// player = context.mediaSessionModule?.player?.value +// } +// player = viewModelPlayer +// playerView?.player = viewModelPlayer + playerView?.player = context.mediaSessionModule?.serviceBinder?.value?.player + + playerView?.onStart() } @@ -116,6 +147,12 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { +// player = null + playerView?.player = null +// context.mediaSessionModule?.serviceBinder?.value?.player = null +// if (context.mediaSessionModule?.player?.value != null) { +// player = context.mediaSessionModule?.player?.value +// } playerView?.onStop() } @@ -123,6 +160,11 @@ class RNPlayerView( } init { + playerEventRelay = EventRelay( + EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING, + ::emitEventFromPlayer, + ) + // React Native has a bug that dynamically added views sometimes aren't laid out again properly. // Since we dynamically add and remove SurfaceView under the hood this caused the player // to suddenly not show the video anymore because SurfaceView was not laid out properly. @@ -133,15 +175,6 @@ class RNPlayerView( activityLifecycle.addObserver(activityLifecycleObserver) } - /** - * Relays the provided set of events, emitted by the player, together with the associated name - * to the `eventOutput` callback. - */ - private val playerEventRelay = EventRelay( - EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING, - ::emitEventFromPlayer, - ) - /** * Relays the provided set of events, emitted by the player view, together with the associated name * to the `eventOutput` callback. From 904d8c06bcf62b8de5f757bed1a23a14c3c66b4b Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 18 Oct 2024 10:19:32 +0200 Subject: [PATCH 09/56] Fix every sample having background playback --- .../player/reactnative/RNPlayerView.kt | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index bea3949e..2481fca8 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -109,32 +109,13 @@ class RNPlayerView( */ private var playerEventRelay: EventRelay -// val viewModelPlayer = context.mediaSessionModule?.player?.value - - val viewModelPlayer: Player? - get() = context.mediaSessionModule?.player?.value - -// // Don't stop the player when going to background -// override fun onStart(playerView: PlayerView) { -// playerView.player = player -// super.onStart(playerView) -// } -// -// override fun onStop(playerView: PlayerView) { -// playerView.player = null -// super.onStop(playerView) -// } - private val activityLifecycleObserver = object : DefaultLifecycleObserver { + // Don't stop the player when going to background override fun onStart(owner: LifecycleOwner) { -// if (context.mediaSessionModule?.player?.value != null) { -// player = context.mediaSessionModule?.player?.value -// } -// player = viewModelPlayer -// playerView?.player = viewModelPlayer - playerView?.player = context.mediaSessionModule?.serviceBinder?.value?.player - - +// playerView?.player = context.mediaSessionModule?.serviceBinder?.value?.player + if (context.mediaSessionModule?.serviceBinder?.value?.player != null) { + player = context.mediaSessionModule?.serviceBinder?.value?.player + } playerView?.onStart() } @@ -147,12 +128,9 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { -// player = null - playerView?.player = null -// context.mediaSessionModule?.serviceBinder?.value?.player = null -// if (context.mediaSessionModule?.player?.value != null) { -// player = context.mediaSessionModule?.player?.value -// } + if (context.mediaSessionModule?.serviceBinder?.value?.player != null) { + player = null + } playerView?.onStop() } From 34f63e17994e2615b577d86499ec9c02e12bbc9f Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 18 Oct 2024 10:28:15 +0200 Subject: [PATCH 10/56] Fix every sample having background playback after service is on --- .../player/reactnative/MediaSessionModule.kt | 5 +--- .../player/reactnative/RNPlayerView.kt | 29 ++++++++++++++++--- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt index 8ef00a5a..a3628093 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt @@ -22,9 +22,6 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( private var isServiceStarted = false private lateinit var playerId: NativeId - private val _player = MutableStateFlow(null) - val player = _player.asStateFlow() - private val _serviceBinder = MutableStateFlow(null) val serviceBinder = _serviceBinder.asStateFlow() @@ -39,7 +36,7 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( } override fun onServiceDisconnected(name: ComponentName) { - _player.value = null + _serviceBinder.value?.player = null } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 2481fca8..c216aef2 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -15,8 +15,10 @@ import com.bitmovin.player.api.event.PlayerEvent import com.bitmovin.player.api.event.SourceEvent import com.bitmovin.player.api.ui.PlayerViewConfig import com.bitmovin.player.api.ui.StyleConfig +import com.bitmovin.player.reactnative.converter.lockScreenControlConfig import com.bitmovin.player.reactnative.converter.toJson import com.bitmovin.player.reactnative.extensions.mediaSessionModule +import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.ReactActivity import com.facebook.react.bridge.* import com.facebook.react.uimanager.events.RCTEventEmitter @@ -109,12 +111,20 @@ class RNPlayerView( */ private var playerEventRelay: EventRelay + private var mediaSessionServicePlayer: Player? + get() = context.mediaSessionModule?.serviceBinder?.value?.player + set(value) { + context.mediaSessionModule?.serviceBinder?.value?.player = value + } + + private var oldPlayer: Player? = null + private val activityLifecycleObserver = object : DefaultLifecycleObserver { // Don't stop the player when going to background override fun onStart(owner: LifecycleOwner) { -// playerView?.player = context.mediaSessionModule?.serviceBinder?.value?.player - if (context.mediaSessionModule?.serviceBinder?.value?.player != null) { - player = context.mediaSessionModule?.serviceBinder?.value?.player + if (mediaSessionServicePlayer != null) { + oldPlayer = player + player = mediaSessionServicePlayer } playerView?.onStart() } @@ -128,9 +138,20 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { - if (context.mediaSessionModule?.serviceBinder?.value?.player != null) { + if (player?.config?.lockScreenControlConfig?.isEnabled == false) { + mediaSessionServicePlayer = null + } + else { player = null } +// if (mediaSessionServicePlayer != null) { +//// context.mediaSessionModule?.serviceBinder?.value?.player = null +//// if (mediaSessionServicePlayer != player) { +//// mediaSessionServicePlayer = null +//// } +// +// player = oldPlayer +// } playerView?.onStop() } From 7fde45fe0434c1f5735cc457d434b1b263e71b9d Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 18 Oct 2024 10:35:34 +0200 Subject: [PATCH 11/56] Cleanup code --- .../player/reactnative/MediaSessionModule.kt | 25 ------------------- .../player/reactnative/PlayerModule.kt | 15 ----------- .../player/reactnative/RNPlayerView.kt | 16 ++---------- 3 files changed, 2 insertions(+), 54 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt index a3628093..301f25b1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt @@ -26,8 +26,6 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( val serviceBinder = _serviceBinder.asStateFlow() inner class MediaSessionServiceConnection: ServiceConnection { - // we need a promise here, so that we can resolve it here instead of in setupMediaSession - // subclass ServiceConnection and pass promise override fun onServiceConnected(className: ComponentName, service: IBinder) { // We've bound to the Service, cast the IBinder and get the Player instance val binder = service as MediaSessionPlaybackService.ServiceBinder @@ -40,16 +38,8 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( } } - - // [!!!] -- on new sources, the media session does not get overridden - // ISSUE: whenever a player instance is created the service does not get - // updated/overridden with that instance. - // Because in player.ts we call `setupMediaSession` on `initialize()`. @ReactMethod fun setupMediaSession(playerId: NativeId) { - // if there is an existing media session session, change its content - // the change should be on play actually, not on player creation - this@MediaSessionModule.playerId = playerId val intent = Intent(context, MediaSessionPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON @@ -67,19 +57,4 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( nativeId: NativeId = playerId, playerModule: PlayerModule? = context.playerModule, ): Player = playerModule?.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") - - /// FINE - // - add LockScreenConfig serializers - // - add a lock-screen control sample: only there the config is set to true - // - fix player is paused when app is minimized: see android codebase: implement Activity lfiecycle onStart and onStop and detach player } - -// ISSUE2 -- if I start playback, minimize, and restart playback. I should -// re-fetch the state of the player from the service and update -// the player in the view - -// ISSUE3 -- if I start playback, minimize the app, the playback gets paused. -// It should not (it is background playback) - -// ISSUE4 -- if `loadSource()` is a different source than the one playing in the -// service, load that instead. diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 94044a78..f1733a53 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -1,8 +1,5 @@ package com.bitmovin.player.reactnative -import android.content.ComponentName -import android.content.ServiceConnection -import android.os.IBinder import android.util.Log import com.bitmovin.analytics.api.DefaultMetadata import com.bitmovin.player.api.Player @@ -16,11 +13,8 @@ import com.bitmovin.player.reactnative.converter.toAnalyticsDefaultMetadata import com.bitmovin.player.reactnative.converter.toJson import com.bitmovin.player.reactnative.converter.toPlayerConfig import com.bitmovin.player.reactnative.extensions.mapToReactArray -import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService import com.facebook.react.bridge.* import com.facebook.react.module.annotations.ReactModule -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import java.security.InvalidParameterException private const val MODULE_NAME = "PlayerModule" @@ -98,12 +92,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex ) } - // FINAL: (ideas) - // If config is enabled, create the Intent here to start/bind the service -- setupMediaSession() - // Put the nativeId in the thread - if (playerConfig.lockScreenControlConfig.isEnabled) { -// playerConfig.playbackConfig promise.unit.resolveOnUiThread { mediaSessionModule .setupMediaSession(nativeId) @@ -111,10 +100,6 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex } } - // FINAL: 2 issues to solve: - // - where is the player created - // - how the player instance is given to the service (binder setting player vs playerId passing) - /** * Load the source of the given [nativeId] with `config` options from JS. * @param nativeId Target player. diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index c216aef2..d2e2c14d 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -18,7 +18,6 @@ import com.bitmovin.player.api.ui.StyleConfig import com.bitmovin.player.reactnative.converter.lockScreenControlConfig import com.bitmovin.player.reactnative.converter.toJson import com.bitmovin.player.reactnative.extensions.mediaSessionModule -import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.ReactActivity import com.facebook.react.bridge.* import com.facebook.react.uimanager.events.RCTEventEmitter @@ -117,13 +116,10 @@ class RNPlayerView( context.mediaSessionModule?.serviceBinder?.value?.player = value } - private var oldPlayer: Player? = null - private val activityLifecycleObserver = object : DefaultLifecycleObserver { // Don't stop the player when going to background override fun onStart(owner: LifecycleOwner) { if (mediaSessionServicePlayer != null) { - oldPlayer = player player = mediaSessionServicePlayer } playerView?.onStart() @@ -140,18 +136,10 @@ class RNPlayerView( override fun onStop(owner: LifecycleOwner) { if (player?.config?.lockScreenControlConfig?.isEnabled == false) { mediaSessionServicePlayer = null - } - else { + } else { player = null } -// if (mediaSessionServicePlayer != null) { -//// context.mediaSessionModule?.serviceBinder?.value?.player = null -//// if (mediaSessionServicePlayer != player) { -//// mediaSessionServicePlayer = null -//// } -// -// player = oldPlayer -// } + playerView?.onStop() } From 5c04e05635191744ecd20b4a2ba2755a722871a4 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 18 Oct 2024 11:14:14 +0200 Subject: [PATCH 12/56] Cleanup unnecessary code and comments --- .../player/reactnative/BitmovinBaseModule.kt | 9 --------- .../services/MediaSessionPlaybackService.kt | 13 +------------ 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt index ef9c1573..f298b42a 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt @@ -41,15 +41,6 @@ abstract class BitmovinBaseModule( } } - protected inline fun TPromise.runOnUiThread( - crossinline block: () -> Unit, - ) { - val uiManager = runAndRejectOnException { uiManager } ?: return - uiManager.addUIBlock { - block() - } - } - protected val RejectPromiseOnExceptionBlock.playerModule: PlayerModule get() = context.playerModule ?: throw IllegalArgumentException("PlayerModule not found") diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index 9f8712d9..acffbfc1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -9,9 +9,6 @@ import com.bitmovin.player.api.media.session.MediaSessionService class MediaSessionPlaybackService : MediaSessionService() { inner class ServiceBinder : Binder() { - // When the service starts, it creates a player - // When playback activity is created, it gets a binder and - // goes to the service and gets the player from it -- the same player instance. var player: Player? get() = this@MediaSessionPlaybackService.player set(value) { @@ -35,18 +32,10 @@ class MediaSessionPlaybackService : MediaSessionService() { override fun onGetSession(): MediaSession? = mediaSession - // Player has 2 pointers: one from application, one from service - // but is the same instance. - // So when it loses the application one, it still does not get - // garbage-collected because it still has a strong reference. override fun onCreate() { super.onCreate() - // [!!] I need to get the same react-native instance of the player here val player = this.player ?: Player(this) - // cannot create mediaSession without a player - // TODO: call playerModule.createPlayer to actually create a player - // so then we'll go to `mediaSessionModule.. onServiceConnected` mediaSession = MediaSession( this, mainLooper, @@ -71,7 +60,7 @@ class MediaSessionPlaybackService : MediaSessionService() { private fun createMediaSession(player: Player) { binder.disconnectSession() - val newMediaSession = MediaSession(// the real media session + val newMediaSession = MediaSession( this, mainLooper, player, From ec76873988bfe460b3fb70b9b2102f5a7457de9f Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 18 Oct 2024 11:56:38 +0200 Subject: [PATCH 13/56] Remove unnecessary decorator --- .../java/com/bitmovin/player/reactnative/MediaSessionModule.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt index 301f25b1..f2cd9670 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt @@ -38,7 +38,6 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( } } - @ReactMethod fun setupMediaSession(playerId: NativeId) { this@MediaSessionModule.playerId = playerId val intent = Intent(context, MediaSessionPlaybackService::class.java) From 9a39c499a9546be5f9399aad29332bee68dfe7bc Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 18 Oct 2024 12:22:23 +0200 Subject: [PATCH 14/56] Make the connection manager a normal class instead of a module As modules represent something native that can be re-used from the JS side, but this is not the case here --- .../bitmovin/player/reactnative/BitmovinBaseModule.kt | 4 ---- ...SessionModule.kt => MediaSessionConnectionManager.kt} | 9 ++------- .../java/com/bitmovin/player/reactnative/PlayerModule.kt | 7 +++++-- .../java/com/bitmovin/player/reactnative/RNPlayerView.kt | 6 +++--- .../bitmovin/player/reactnative/RNPlayerViewPackage.kt | 1 - .../reactnative/extensions/ReactContextExtension.kt | 2 -- 6 files changed, 10 insertions(+), 19 deletions(-) rename android/src/main/java/com/bitmovin/player/reactnative/{MediaSessionModule.kt => MediaSessionConnectionManager.kt} (85%) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt index f298b42a..145f455b 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt @@ -4,7 +4,6 @@ import android.util.Log import com.bitmovin.player.api.Player import com.bitmovin.player.api.source.Source import com.bitmovin.player.reactnative.extensions.drmModule -import com.bitmovin.player.reactnative.extensions.mediaSessionModule import com.bitmovin.player.reactnative.extensions.networkModule import com.bitmovin.player.reactnative.extensions.offlineModule import com.bitmovin.player.reactnative.extensions.playerModule @@ -59,9 +58,6 @@ abstract class BitmovinBaseModule( protected val RejectPromiseOnExceptionBlock.networkModule: NetworkModule get() = context.networkModule ?: throw IllegalStateException("NetworkModule not found") - protected val RejectPromiseOnExceptionBlock.mediaSessionModule: MediaSessionModule - get() = context.mediaSessionModule ?: throw IllegalStateException("MediaSessionModule not found") - fun RejectPromiseOnExceptionBlock.getPlayer( nativeId: NativeId, playerModule: PlayerModule = this.playerModule, diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt similarity index 85% rename from android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt rename to android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt index f2cd9670..673183b0 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt @@ -10,15 +10,10 @@ import com.bitmovin.player.api.Player import com.bitmovin.player.reactnative.extensions.playerModule import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService import com.facebook.react.bridge.* -import com.facebook.react.module.annotations.ReactModule import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -private const val MODULE_NAME = "MediaSessionModule" -@ReactModule(name = MODULE_NAME) -class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule(context) { - override fun getName() = MODULE_NAME - +class MediaSessionConnectionManager(val context: ReactApplicationContext) { private var isServiceStarted = false private lateinit var playerId: NativeId @@ -39,7 +34,7 @@ class MediaSessionModule(context: ReactApplicationContext) : BitmovinBaseModule( } fun setupMediaSession(playerId: NativeId) { - this@MediaSessionModule.playerId = playerId + this@MediaSessionConnectionManager.playerId = playerId val intent = Intent(context, MediaSessionPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index f1733a53..cbabe57b 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -26,6 +26,8 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() + var mediaSessionConnectionManager: MediaSessionConnectionManager? = null + /** * JS exported module name. */ @@ -93,9 +95,9 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex } if (playerConfig.lockScreenControlConfig.isEnabled) { + mediaSessionConnectionManager = MediaSessionConnectionManager(context) promise.unit.resolveOnUiThread { - mediaSessionModule - .setupMediaSession(nativeId) + mediaSessionConnectionManager?.setupMediaSession(nativeId) } } } @@ -222,6 +224,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex promise.unit.resolveOnUiThreadWithPlayer(nativeId) { destroy() players.remove(nativeId) + mediaSessionConnectionManager = null } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index d2e2c14d..62da2df4 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -17,7 +17,7 @@ import com.bitmovin.player.api.ui.PlayerViewConfig import com.bitmovin.player.api.ui.StyleConfig import com.bitmovin.player.reactnative.converter.lockScreenControlConfig import com.bitmovin.player.reactnative.converter.toJson -import com.bitmovin.player.reactnative.extensions.mediaSessionModule +import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.ReactActivity import com.facebook.react.bridge.* import com.facebook.react.uimanager.events.RCTEventEmitter @@ -111,9 +111,9 @@ class RNPlayerView( private var playerEventRelay: EventRelay private var mediaSessionServicePlayer: Player? - get() = context.mediaSessionModule?.serviceBinder?.value?.player + get() = context.playerModule?.mediaSessionConnectionManager?.serviceBinder?.value?.player set(value) { - context.mediaSessionModule?.serviceBinder?.value?.player = value + context.playerModule?.mediaSessionConnectionManager?.serviceBinder?.value?.player = value } private val activityLifecycleObserver = object : DefaultLifecycleObserver { diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt index f74b1f3f..807f6f54 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt @@ -30,7 +30,6 @@ class RNPlayerViewPackage : ReactPackage { BitmovinCastManagerModule(reactContext), BufferModule(reactContext), NetworkModule(reactContext), - MediaSessionModule(reactContext), ) } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt b/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt index 31be9a22..64090363 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt @@ -1,7 +1,6 @@ package com.bitmovin.player.reactnative.extensions import com.bitmovin.player.reactnative.DrmModule -import com.bitmovin.player.reactnative.MediaSessionModule import com.bitmovin.player.reactnative.NetworkModule import com.bitmovin.player.reactnative.OfflineModule import com.bitmovin.player.reactnative.PlayerModule @@ -21,4 +20,3 @@ val ReactApplicationContext.uiManagerModule get() = getModule() val ReactApplicationContext.drmModule get() = getModule() val ReactApplicationContext.customMessageHandlerModule get() = getModule() val ReactApplicationContext.networkModule get() = getModule() -val ReactApplicationContext.mediaSessionModule get() = getModule() From 2d14fb7549103407e91e7a00c088c92a38032b13 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 18 Oct 2024 12:28:39 +0200 Subject: [PATCH 15/56] Add newline at end of the file --- .../player/reactnative/services/MediaSessionPlaybackService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index acffbfc1..be27a9c5 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -69,4 +69,4 @@ class MediaSessionPlaybackService : MediaSessionService() { mediaSession = newMediaSession binder.connectSession() } -} \ No newline at end of file +} From 40be6a75dc7c18fb09f8ac9ca5e438fcbd6079db Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 18 Oct 2024 14:30:38 +0200 Subject: [PATCH 16/56] Update docs with android details Limitation section will need an update once the lock-screen implementation into Android SDK is finished --- src/lockScreenControlConfig.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lockScreenControlConfig.ts b/src/lockScreenControlConfig.ts index 89a9831b..66303898 100644 --- a/src/lockScreenControlConfig.ts +++ b/src/lockScreenControlConfig.ts @@ -7,6 +7,7 @@ export interface LockScreenControlConfig { /** * Enable the default behavior of displaying media information * on the lock screen and within the control center. + * * Default is `false`. * * For a detailed list of the supported features in the **default behavior**, @@ -31,18 +32,25 @@ export interface LockScreenControlConfig { * Here is the list of features supported by the default behavior. * * ### Populated Metadata - * - asset URL (to visualize the correct kind of data — _e.g. a waveform for audio files_) + * - media type (to visualize the correct kind of data — _e.g. a waveform for audio files_) * - title * - artwork + * - elapsed time + * - duration + * + * **Android-only** + * - source description + * + * **iOS-only** * - live or VOD status * - playback rate * - default playback rate - * - elapsed time - * - duration * * ### Registered Commands * - toggle play/pause * - change playback position + * + * **iOS-only** * - skip forward * - skip backward */ From e239fce34fd60cedf966367c484e40db2d688af4 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 23 Oct 2024 13:49:28 +0200 Subject: [PATCH 17/56] Simplify `LockScreenControlConfig` serialization --- .../java/com/bitmovin/player/reactnative/PlayerModule.kt | 7 +++++-- .../java/com/bitmovin/player/reactnative/RNPlayerView.kt | 2 +- .../player/reactnative/converter/JsonConverter.kt | 8 -------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index cbabe57b..c32cc401 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -6,11 +6,11 @@ import com.bitmovin.player.api.Player import com.bitmovin.player.api.PlayerConfig import com.bitmovin.player.api.analytics.create import com.bitmovin.player.api.event.PlayerEvent -import com.bitmovin.player.reactnative.converter.lockScreenControlConfig import com.bitmovin.player.reactnative.converter.toAdItem import com.bitmovin.player.reactnative.converter.toAnalyticsConfig import com.bitmovin.player.reactnative.converter.toAnalyticsDefaultMetadata import com.bitmovin.player.reactnative.converter.toJson +import com.bitmovin.player.reactnative.converter.toLockScreenControlConfig import com.bitmovin.player.reactnative.converter.toPlayerConfig import com.bitmovin.player.reactnative.extensions.mapToReactArray import com.facebook.react.bridge.* @@ -27,6 +27,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex private val players: Registry = mutableMapOf() var mediaSessionConnectionManager: MediaSessionConnectionManager? = null + var isMediaSessionPlaybackEnabled: Boolean = false /** * JS exported module name. @@ -77,6 +78,8 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val playerConfig = playerConfigJson?.toPlayerConfig() ?: PlayerConfig() val analyticsConfig = analyticsConfigJson?.toAnalyticsConfig() val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() + isMediaSessionPlaybackEnabled = playerConfigJson?.getMap("lockScreenControlConfig") + ?.toLockScreenControlConfig()?.isEnabled ?: false val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { @@ -94,7 +97,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex ) } - if (playerConfig.lockScreenControlConfig.isEnabled) { + if (isMediaSessionPlaybackEnabled) { mediaSessionConnectionManager = MediaSessionConnectionManager(context) promise.unit.resolveOnUiThread { mediaSessionConnectionManager?.setupMediaSession(nativeId) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 62da2df4..2d42085c 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -134,7 +134,7 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { - if (player?.config?.lockScreenControlConfig?.isEnabled == false) { + if (context.playerModule?.isMediaSessionPlaybackEnabled == false) { mediaSessionServicePlayer = null } else { player = null diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index 474ec0b6..5370e7f2 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -92,7 +92,6 @@ fun ReadableMap.toPlayerConfig(): PlayerConfig = PlayerConfig(key = getString("l withMap("bufferConfig") { bufferConfig = it.toBufferConfig() } withMap("liveConfig") { liveConfig = it.toLiveConfig() } withMap("networkConfig") { networkConfig = it.toNetworkConfig() } - withMap("lockScreenControlConfig") { lockScreenControlConfig = it.toLockScreenControlConfig() } } /** @@ -892,13 +891,6 @@ fun ReadableMap.toLockScreenControlConfig(): LockScreenControlConfig = LockScree withBoolean("isEnabled") { isEnabled = it } } -private val lockScreenConfigMap = mutableMapOf() -var PlayerConfig.lockScreenControlConfig: LockScreenControlConfig - get() = lockScreenConfigMap[this] ?: LockScreenControlConfig() - set(value) { - lockScreenConfigMap[this] = value - } - /** * Converts a [CastPayload] object into its JS representation. */ From a3698fdaf2210d9b37b27104937b3cad530f5258 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 23 Oct 2024 15:05:47 +0200 Subject: [PATCH 18/56] Update docs for bg-playback support on Android --- CHANGELOG.md | 1 + src/playbackConfig.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae271c3e..8f48f467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - `LockScreenControlConfig` to configure the lock screen information for the application. When `isEnabled` is `true`, the current media information will be shown on the lock-screen and within the control center +- Android: `playerConfig.playbackConfig.isBackgroundPlaybackEnabled` to support background playback ### Changed diff --git a/src/playbackConfig.ts b/src/playbackConfig.ts index 70a5748e..f2ec9014 100644 --- a/src/playbackConfig.ts +++ b/src/playbackConfig.ts @@ -47,19 +47,19 @@ export interface PlaybackConfig { * When set to `true`, also make sure to properly configure your app to allow * background playback. * - * On tvOS, background playback is only supported for audio-only content. - * * Default is `false`. * + * @note + * On tvOS, background playback is only supported for audio-only content. + * * @example * ``` * const player = new Player({ - * { + * playbackConfig: { * isBackgroundPlaybackEnabled: true, - * } - * }) + * }, + * }); * ``` - * @platform iOS, tvOS */ isBackgroundPlaybackEnabled?: boolean; /** From 44008f99aeeeb0c490bfe48f4e307dc7e6553292 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 23 Oct 2024 15:06:37 +0200 Subject: [PATCH 19/56] Add bg playback sample --- example/src/App.tsx | 11 +++ example/src/screens/BackgroundPlayback.tsx | 87 ++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 example/src/screens/BackgroundPlayback.tsx diff --git a/example/src/App.tsx b/example/src/App.tsx index a09e95e2..7eeda420 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -21,6 +21,7 @@ import SystemUI from './screens/SystemUi'; import OfflinePlayback from './screens/OfflinePlayback'; import Casting from './screens/Casting'; import LockScreenControls from './screens/LockScreenControls'; +import BackgroundPlayback from './screens/BackgroundPlayback'; export type RootStackParamsList = { ExamplesList: { @@ -60,6 +61,7 @@ export type RootStackParamsList = { Casting: undefined; SystemUI: undefined; LockScreenControls: undefined; + BackgroundPlayback: undefined; }; const RootStack = createNativeStackNavigator(); @@ -115,6 +117,10 @@ export default function App() { title: 'Lock-Screen Controls', routeName: 'LockScreenControls' as keyof RootStackParamsList, }, + { + title: 'Background Playback', + routeName: 'BackgroundPlayback' as keyof RootStackParamsList, + }, ], }; @@ -273,6 +279,11 @@ export default function App() { component={LockScreenControls} options={{ title: 'Lock-Screen Controls' }} /> + ); diff --git a/example/src/screens/BackgroundPlayback.tsx b/example/src/screens/BackgroundPlayback.tsx new file mode 100644 index 00000000..e1a43cd7 --- /dev/null +++ b/example/src/screens/BackgroundPlayback.tsx @@ -0,0 +1,87 @@ +import React, { useCallback } from 'react'; +import { View, Platform, StyleSheet } from 'react-native'; +import { useFocusEffect } from '@react-navigation/native'; +import { + Event, + usePlayer, + PlayerView, + SourceType, +} from 'bitmovin-player-react-native'; +import { useTVGestures } from '../hooks'; + +function prettyPrint(header: string, obj: any) { + console.log(header, JSON.stringify(obj, null, 2)); +} + +export default function BackgroundPlayback() { + useTVGestures(); + + const player = usePlayer({ + playbackConfig: { + isBackgroundPlaybackEnabled: true, + }, + remoteControlConfig: { + isCastEnabled: false, + }, + }); + + useFocusEffect( + useCallback(() => { + player.load({ + url: + Platform.OS === 'ios' + ? 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8' + : 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd', + type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH, + title: 'Art of Motion', + poster: + 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/poster.jpg', + thumbnailTrack: + 'https://cdn.bitmovin.com/content/assets/art-of-motion-dash-hls-progressive/thumbnails/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.vtt', + metadata: { platform: Platform.OS }, + }); + return () => { + player.destroy(); + }; + }, [player]) + ); + + const onReady = useCallback((event: Event) => { + prettyPrint(`EVENT [${event.name}]`, event); + }, []); + + const onEvent = useCallback((event: Event) => { + prettyPrint(`EVENT [${event.name}]`, event); + }, []); + + return ( + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'black', + }, + player: { + flex: 1, + }, +}); From 64a572cf76be143ce58918beecb57e947156002a Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 23 Oct 2024 16:10:10 +0200 Subject: [PATCH 20/56] temp: tentative changes --- .../player/reactnative/MediaSessionConnectionManager.kt | 9 ++++++++- .../java/com/bitmovin/player/reactnative/PlayerModule.kt | 8 ++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt index 673183b0..b2e8607a 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt @@ -33,10 +33,12 @@ class MediaSessionConnectionManager(val context: ReactApplicationContext) { } } - fun setupMediaSession(playerId: NativeId) { + fun setupMediaSession(playerId: NativeId, playerModule: PlayerModule) { this@MediaSessionConnectionManager.playerId = playerId val intent = Intent(context, MediaSessionPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON +// intent.putExtra(isBackgroundPlaybackEnabledKey, playerModule.isBackgroundPlaybackEnabled) + intent.putExtra(isMediaSessionPlaybackEnabledKey, playerModule.isMediaSessionPlaybackEnabled) val connection = MediaSessionServiceConnection() context.bindService(intent, connection, Context.BIND_AUTO_CREATE) @@ -51,4 +53,9 @@ class MediaSessionConnectionManager(val context: ReactApplicationContext) { nativeId: NativeId = playerId, playerModule: PlayerModule? = context.playerModule, ): Player = playerModule?.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") + + companion object { +// const val isBackgroundPlaybackEnabledKey: String = "isBackgroundPlaybackEnabled" + const val isMediaSessionPlaybackEnabledKey: String = "isMediaSessionPlaybackEnabled" + } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index c32cc401..5bb844a5 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -13,6 +13,7 @@ import com.bitmovin.player.reactnative.converter.toJson import com.bitmovin.player.reactnative.converter.toLockScreenControlConfig import com.bitmovin.player.reactnative.converter.toPlayerConfig import com.bitmovin.player.reactnative.extensions.mapToReactArray +import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.bridge.* import com.facebook.react.module.annotations.ReactModule import java.security.InvalidParameterException @@ -28,6 +29,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex var mediaSessionConnectionManager: MediaSessionConnectionManager? = null var isMediaSessionPlaybackEnabled: Boolean = false + var isBackgroundPlaybackEnabled: Boolean = false /** * JS exported module name. @@ -80,6 +82,8 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() isMediaSessionPlaybackEnabled = playerConfigJson?.getMap("lockScreenControlConfig") ?.toLockScreenControlConfig()?.isEnabled ?: false + isBackgroundPlaybackEnabled = playerConfigJson?.getMap("playbackConfig") + ?.getBoolean("isBackgroundPlaybackEnabled") ?: false val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { @@ -97,10 +101,10 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex ) } - if (isMediaSessionPlaybackEnabled) { + if (isBackgroundPlaybackEnabled || isMediaSessionPlaybackEnabled) { mediaSessionConnectionManager = MediaSessionConnectionManager(context) promise.unit.resolveOnUiThread { - mediaSessionConnectionManager?.setupMediaSession(nativeId) + mediaSessionConnectionManager?.setupMediaSession(nativeId, this@PlayerModule) } } } From 96eafe9f7dc8a25e8d9a35e937467b4841795a43 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 23 Oct 2024 17:15:34 +0200 Subject: [PATCH 21/56] Unify most of the logic for bg and mediasession --- .../MediaSessionConnectionManager.kt | 30 ++++++++--------- .../player/reactnative/PlayerModule.kt | 15 +++++---- .../player/reactnative/RNPlayerView.kt | 15 ++++----- .../services/BackgroundPlaybackService.kt | 32 +++++++++++++++++++ .../services/MediaSessionPlaybackService.kt | 8 ++--- .../android/app/src/main/AndroidManifest.xml | 6 ++++ 6 files changed, 72 insertions(+), 34 deletions(-) create mode 100644 android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt index b2e8607a..948e3183 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt @@ -5,25 +5,28 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection +import android.os.Binder import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.reactnative.extensions.playerModule +import com.bitmovin.player.reactnative.services.BackgroundPlaybackService import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService import com.facebook.react.bridge.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +// TODO: rename BackgroundPlaybackConnectionManager as both are form of bg playback +// TODO: mediaSessionPlayback to mediaSession class MediaSessionConnectionManager(val context: ReactApplicationContext) { private var isServiceStarted = false private lateinit var playerId: NativeId - private val _serviceBinder = MutableStateFlow(null) + private val _serviceBinder = MutableStateFlow(null) val serviceBinder = _serviceBinder.asStateFlow() - inner class MediaSessionServiceConnection: ServiceConnection { + inner class BackgroundPlaybackServiceConnection: ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { - // We've bound to the Service, cast the IBinder and get the Player instance - val binder = service as MediaSessionPlaybackService.ServiceBinder + val binder = service as PlayerServiceBinder _serviceBinder.value = binder binder.player = getPlayer() } @@ -33,14 +36,14 @@ class MediaSessionConnectionManager(val context: ReactApplicationContext) { } } - fun setupMediaSession(playerId: NativeId, playerModule: PlayerModule) { + fun setupBackgroundPlayback(playerId: NativeId, playerModule: PlayerModule) { this@MediaSessionConnectionManager.playerId = playerId - val intent = Intent(context, MediaSessionPlaybackService::class.java) + val serviceClass = if (playerModule.isMediaSessionPlaybackEnabled) + MediaSessionPlaybackService::class.java else + BackgroundPlaybackService::class.java + val intent = Intent(context, serviceClass) intent.action = Intent.ACTION_MEDIA_BUTTON -// intent.putExtra(isBackgroundPlaybackEnabledKey, playerModule.isBackgroundPlaybackEnabled) - intent.putExtra(isMediaSessionPlaybackEnabledKey, playerModule.isMediaSessionPlaybackEnabled) - - val connection = MediaSessionServiceConnection() + val connection: ServiceConnection = BackgroundPlaybackServiceConnection() context.bindService(intent, connection, Context.BIND_AUTO_CREATE) if (!isServiceStarted) { @@ -53,9 +56,6 @@ class MediaSessionConnectionManager(val context: ReactApplicationContext) { nativeId: NativeId = playerId, playerModule: PlayerModule? = context.playerModule, ): Player = playerModule?.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") - - companion object { -// const val isBackgroundPlaybackEnabledKey: String = "isBackgroundPlaybackEnabled" - const val isMediaSessionPlaybackEnabledKey: String = "isMediaSessionPlaybackEnabled" - } } + +open class PlayerServiceBinder(open var player: Player?) : Binder() diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 5bb844a5..622fd5c1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -27,7 +27,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() - var mediaSessionConnectionManager: MediaSessionConnectionManager? = null + var backgroundPlaybackConnectionManager: MediaSessionConnectionManager? = null var isMediaSessionPlaybackEnabled: Boolean = false var isBackgroundPlaybackEnabled: Boolean = false @@ -82,8 +82,9 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() isMediaSessionPlaybackEnabled = playerConfigJson?.getMap("lockScreenControlConfig") ?.toLockScreenControlConfig()?.isEnabled ?: false - isBackgroundPlaybackEnabled = playerConfigJson?.getMap("playbackConfig") - ?.getBoolean("isBackgroundPlaybackEnabled") ?: false + isBackgroundPlaybackEnabled = if (isMediaSessionPlaybackEnabled) + isMediaSessionPlaybackEnabled else + playerConfigJson?.getMap("playbackConfig")?.getBoolean("isBackgroundPlaybackEnabled") ?: false val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { @@ -101,10 +102,10 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex ) } - if (isBackgroundPlaybackEnabled || isMediaSessionPlaybackEnabled) { - mediaSessionConnectionManager = MediaSessionConnectionManager(context) + if (isBackgroundPlaybackEnabled) { + backgroundPlaybackConnectionManager = MediaSessionConnectionManager(context) promise.unit.resolveOnUiThread { - mediaSessionConnectionManager?.setupMediaSession(nativeId, this@PlayerModule) + backgroundPlaybackConnectionManager?.setupBackgroundPlayback(nativeId, this@PlayerModule) } } } @@ -231,7 +232,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex promise.unit.resolveOnUiThreadWithPlayer(nativeId) { destroy() players.remove(nativeId) - mediaSessionConnectionManager = null + backgroundPlaybackConnectionManager = null } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 2d42085c..31352ec9 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -15,7 +15,6 @@ import com.bitmovin.player.api.event.PlayerEvent import com.bitmovin.player.api.event.SourceEvent import com.bitmovin.player.api.ui.PlayerViewConfig import com.bitmovin.player.api.ui.StyleConfig -import com.bitmovin.player.reactnative.converter.lockScreenControlConfig import com.bitmovin.player.reactnative.converter.toJson import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.ReactActivity @@ -110,17 +109,17 @@ class RNPlayerView( */ private var playerEventRelay: EventRelay - private var mediaSessionServicePlayer: Player? - get() = context.playerModule?.mediaSessionConnectionManager?.serviceBinder?.value?.player + private var backgroundPlaybackServicePlayer: Player? + get() = context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.value?.player set(value) { - context.playerModule?.mediaSessionConnectionManager?.serviceBinder?.value?.player = value + context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.value?.player = value } private val activityLifecycleObserver = object : DefaultLifecycleObserver { // Don't stop the player when going to background override fun onStart(owner: LifecycleOwner) { - if (mediaSessionServicePlayer != null) { - player = mediaSessionServicePlayer + if (backgroundPlaybackServicePlayer != null) { + player = backgroundPlaybackServicePlayer // TODO: background playback does not go here on app reopening } playerView?.onStart() } @@ -134,8 +133,8 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { - if (context.playerModule?.isMediaSessionPlaybackEnabled == false) { - mediaSessionServicePlayer = null + if (context.playerModule?.isBackgroundPlaybackEnabled == false) { + backgroundPlaybackServicePlayer = null } else { player = null } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt new file mode 100644 index 00000000..8d44d89a --- /dev/null +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt @@ -0,0 +1,32 @@ +package com.bitmovin.player.reactnative.services + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import com.bitmovin.player.api.Player +import com.bitmovin.player.reactnative.PlayerServiceBinder + +class BackgroundPlaybackService : Service() { + inner class ServiceBinder(player: Player?) : PlayerServiceBinder(player) { + override var player: Player? + get() = this@BackgroundPlaybackService.player + set(value) { + this@BackgroundPlaybackService.player?.destroy() + this@BackgroundPlaybackService.player = value + } + } + + private var player: Player? = null + private val binder = ServiceBinder(player) + + override fun onDestroy() { + player?.destroy() + player = null + + super.onDestroy() + } + + override fun onBind(intent: Intent?): IBinder { + return binder + } +} diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index be27a9c5..3feac8c1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -1,15 +1,15 @@ package com.bitmovin.player.reactnative.services import android.content.Intent -import android.os.Binder import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.api.media.session.MediaSession import com.bitmovin.player.api.media.session.MediaSessionService +import com.bitmovin.player.reactnative.PlayerServiceBinder class MediaSessionPlaybackService : MediaSessionService() { - inner class ServiceBinder : Binder() { - var player: Player? + inner class ServiceBinder(player: Player?) : PlayerServiceBinder(player) { + override var player: Player? get() = this@MediaSessionPlaybackService.player set(value) { this@MediaSessionPlaybackService.player?.destroy() @@ -26,8 +26,8 @@ class MediaSessionPlaybackService : MediaSessionService() { } } - private val binder = ServiceBinder() private var player: Player? = null + private val binder = ServiceBinder(player) private var mediaSession: MediaSession? = null override fun onGetSession(): MediaSession? = mediaSession diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index edd84fd4..d3ac01b5 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -62,5 +62,11 @@ + + + From fca2dda8ddb99c7c8107d0ad8bea00a8a5bfa115 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 23 Oct 2024 17:22:01 +0200 Subject: [PATCH 22/56] Rename bg manager more generic and run ktlint --- ...onManager.kt => BackgroundPlaybackManager.kt} | 16 ++++++++-------- .../bitmovin/player/reactnative/PlayerModule.kt | 11 ++++++----- .../reactnative/converter/JsonConverter.kt | 2 +- .../services/MediaSessionPlaybackService.kt | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) rename android/src/main/java/com/bitmovin/player/reactnative/{MediaSessionConnectionManager.kt => BackgroundPlaybackManager.kt} (82%) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt similarity index 82% rename from android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt rename to android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt index 948e3183..9df121b8 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt @@ -1,6 +1,5 @@ package com.bitmovin.player.reactnative - import android.content.ComponentName import android.content.Context import android.content.Intent @@ -15,16 +14,14 @@ import com.facebook.react.bridge.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -// TODO: rename BackgroundPlaybackConnectionManager as both are form of bg playback -// TODO: mediaSessionPlayback to mediaSession -class MediaSessionConnectionManager(val context: ReactApplicationContext) { +class BackgroundPlaybackManager(val context: ReactApplicationContext) { private var isServiceStarted = false private lateinit var playerId: NativeId private val _serviceBinder = MutableStateFlow(null) val serviceBinder = _serviceBinder.asStateFlow() - inner class BackgroundPlaybackServiceConnection: ServiceConnection { + inner class BackgroundPlaybackServiceConnection : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { val binder = service as PlayerServiceBinder _serviceBinder.value = binder @@ -37,10 +34,13 @@ class MediaSessionConnectionManager(val context: ReactApplicationContext) { } fun setupBackgroundPlayback(playerId: NativeId, playerModule: PlayerModule) { - this@MediaSessionConnectionManager.playerId = playerId - val serviceClass = if (playerModule.isMediaSessionPlaybackEnabled) - MediaSessionPlaybackService::class.java else + this@BackgroundPlaybackManager.playerId = playerId + val serviceClass = if (playerModule.isMediaSessionPlaybackEnabled) { + MediaSessionPlaybackService::class.java + } else { BackgroundPlaybackService::class.java + } +// val serviceClass = MediaSessionPlaybackService::class.java val intent = Intent(context, serviceClass) intent.action = Intent.ACTION_MEDIA_BUTTON val connection: ServiceConnection = BackgroundPlaybackServiceConnection() diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 622fd5c1..95b9cb10 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -13,7 +13,6 @@ import com.bitmovin.player.reactnative.converter.toJson import com.bitmovin.player.reactnative.converter.toLockScreenControlConfig import com.bitmovin.player.reactnative.converter.toPlayerConfig import com.bitmovin.player.reactnative.extensions.mapToReactArray -import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.bridge.* import com.facebook.react.module.annotations.ReactModule import java.security.InvalidParameterException @@ -27,7 +26,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() - var backgroundPlaybackConnectionManager: MediaSessionConnectionManager? = null + var backgroundPlaybackConnectionManager: BackgroundPlaybackManager? = null var isMediaSessionPlaybackEnabled: Boolean = false var isBackgroundPlaybackEnabled: Boolean = false @@ -82,9 +81,11 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() isMediaSessionPlaybackEnabled = playerConfigJson?.getMap("lockScreenControlConfig") ?.toLockScreenControlConfig()?.isEnabled ?: false - isBackgroundPlaybackEnabled = if (isMediaSessionPlaybackEnabled) - isMediaSessionPlaybackEnabled else + isBackgroundPlaybackEnabled = if (isMediaSessionPlaybackEnabled) { + isMediaSessionPlaybackEnabled + } else { playerConfigJson?.getMap("playbackConfig")?.getBoolean("isBackgroundPlaybackEnabled") ?: false + } val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { @@ -103,7 +104,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex } if (isBackgroundPlaybackEnabled) { - backgroundPlaybackConnectionManager = MediaSessionConnectionManager(context) + backgroundPlaybackConnectionManager = BackgroundPlaybackManager(context) promise.unit.resolveOnUiThread { backgroundPlaybackConnectionManager?.setupBackgroundPlayback(nativeId, this@PlayerModule) } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index 5370e7f2..92843081 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -884,7 +884,7 @@ fun String.toMediaType(): MediaType? = when (this) { } data class LockScreenControlConfig( - var isEnabled: Boolean = false + var isEnabled: Boolean = false, ) fun ReadableMap.toLockScreenControlConfig(): LockScreenControlConfig = LockScreenControlConfig().apply { diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index 3feac8c1..e19378c7 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -20,7 +20,7 @@ class MediaSessionPlaybackService : MediaSessionService() { } fun connectSession() = mediaSession?.let { addSession(it) } - fun disconnectSession() = mediaSession?.let{ + fun disconnectSession() = mediaSession?.let { removeSession(it) it.release() } From 08d0dd53238df78cec1795967bdec2fc7b311c4c Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 24 Oct 2024 10:22:26 +0200 Subject: [PATCH 23/56] Remove unnecessary `onCreate` A 'fake' media session was being initialized here because the Android SDK by a design mistake had mediaSession as not nullable. --- .../services/MediaSessionPlaybackService.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index be27a9c5..5ed3198c 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -32,17 +32,6 @@ class MediaSessionPlaybackService : MediaSessionService() { override fun onGetSession(): MediaSession? = mediaSession - override fun onCreate() { - super.onCreate() - val player = this.player ?: Player(this) - - mediaSession = MediaSession( - this, - mainLooper, - player, - ) - } - override fun onDestroy() { binder.disconnectSession() From c78df51b7016f37c4c16c90033236ba2f98457db Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 24 Oct 2024 10:23:25 +0200 Subject: [PATCH 24/56] Format --- .../player/reactnative/MediaSessionConnectionManager.kt | 3 +-- .../main/java/com/bitmovin/player/reactnative/RNPlayerView.kt | 1 - .../com/bitmovin/player/reactnative/converter/JsonConverter.kt | 2 +- .../player/reactnative/services/MediaSessionPlaybackService.kt | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt index 673183b0..f93dfeb1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionConnectionManager.kt @@ -1,6 +1,5 @@ package com.bitmovin.player.reactnative - import android.content.ComponentName import android.content.Context import android.content.Intent @@ -20,7 +19,7 @@ class MediaSessionConnectionManager(val context: ReactApplicationContext) { private val _serviceBinder = MutableStateFlow(null) val serviceBinder = _serviceBinder.asStateFlow() - inner class MediaSessionServiceConnection: ServiceConnection { + inner class MediaSessionServiceConnection : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // We've bound to the Service, cast the IBinder and get the Player instance val binder = service as MediaSessionPlaybackService.ServiceBinder diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 2d42085c..aea71de2 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -15,7 +15,6 @@ import com.bitmovin.player.api.event.PlayerEvent import com.bitmovin.player.api.event.SourceEvent import com.bitmovin.player.api.ui.PlayerViewConfig import com.bitmovin.player.api.ui.StyleConfig -import com.bitmovin.player.reactnative.converter.lockScreenControlConfig import com.bitmovin.player.reactnative.converter.toJson import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.ReactActivity diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index 5370e7f2..92843081 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -884,7 +884,7 @@ fun String.toMediaType(): MediaType? = when (this) { } data class LockScreenControlConfig( - var isEnabled: Boolean = false + var isEnabled: Boolean = false, ) fun ReadableMap.toLockScreenControlConfig(): LockScreenControlConfig = LockScreenControlConfig().apply { diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index 5ed3198c..0091d873 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -20,7 +20,7 @@ class MediaSessionPlaybackService : MediaSessionService() { } fun connectSession() = mediaSession?.let { addSession(it) } - fun disconnectSession() = mediaSession?.let{ + fun disconnectSession() = mediaSession?.let { removeSession(it) it.release() } From 30e5466aed3bc92c907788cc2e36ea0f1f9f3e6c Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 24 Oct 2024 12:05:22 +0200 Subject: [PATCH 25/56] pair-programming : just use one service, destroy media session --- .../player/reactnative/BackgroundPlaybackManager.kt | 9 ++------- .../com/bitmovin/player/reactnative/RNPlayerView.kt | 2 +- .../services/MediaSessionPlaybackService.kt | 11 ++++++++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt index 9df121b8..b8f15525 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt @@ -35,14 +35,9 @@ class BackgroundPlaybackManager(val context: ReactApplicationContext) { fun setupBackgroundPlayback(playerId: NativeId, playerModule: PlayerModule) { this@BackgroundPlaybackManager.playerId = playerId - val serviceClass = if (playerModule.isMediaSessionPlaybackEnabled) { - MediaSessionPlaybackService::class.java - } else { - BackgroundPlaybackService::class.java - } -// val serviceClass = MediaSessionPlaybackService::class.java - val intent = Intent(context, serviceClass) + val intent = Intent(context, MediaSessionPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON + intent.putExtra("isMediaSessionEnabled", playerModule.isMediaSessionPlaybackEnabled) val connection: ServiceConnection = BackgroundPlaybackServiceConnection() context.bindService(intent, connection, Context.BIND_AUTO_CREATE) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 31352ec9..e8b71f44 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -119,7 +119,7 @@ class RNPlayerView( // Don't stop the player when going to background override fun onStart(owner: LifecycleOwner) { if (backgroundPlaybackServicePlayer != null) { - player = backgroundPlaybackServicePlayer // TODO: background playback does not go here on app reopening + player = backgroundPlaybackServicePlayer } playerView?.onStart() } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index ed083b3b..16323ef0 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -15,7 +15,14 @@ class MediaSessionPlaybackService : MediaSessionService() { this@MediaSessionPlaybackService.player?.destroy() this@MediaSessionPlaybackService.player = value value?.let { - createMediaSession(it) + mediaSession?.let { mediaSession -> + removeSession(mediaSession) + mediaSession.release() + } + + if (this@MediaSessionPlaybackService.isMediaSessionEnabled) { + createMediaSession(it) + } } } @@ -29,6 +36,7 @@ class MediaSessionPlaybackService : MediaSessionService() { private var player: Player? = null private val binder = ServiceBinder(player) private var mediaSession: MediaSession? = null + private var isMediaSessionEnabled: Boolean = false override fun onGetSession(): MediaSession? = mediaSession @@ -43,6 +51,7 @@ class MediaSessionPlaybackService : MediaSessionService() { override fun onBind(intent: Intent?): IBinder { super.onBind(intent) + isMediaSessionEnabled = intent?.getBooleanExtra("isMediaSessionEnabled", isMediaSessionEnabled) ?: false return binder } From a0c9e86aede8a662f433a0bf1f16559c2ac9326a Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 24 Oct 2024 12:06:15 +0200 Subject: [PATCH 26/56] Remove simple bg service --- .../services/BackgroundPlaybackService.kt | 32 ------------------- .../android/app/src/main/AndroidManifest.xml | 6 ---- 2 files changed, 38 deletions(-) delete mode 100644 android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt deleted file mode 100644 index 8d44d89a..00000000 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.bitmovin.player.reactnative.services - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import com.bitmovin.player.api.Player -import com.bitmovin.player.reactnative.PlayerServiceBinder - -class BackgroundPlaybackService : Service() { - inner class ServiceBinder(player: Player?) : PlayerServiceBinder(player) { - override var player: Player? - get() = this@BackgroundPlaybackService.player - set(value) { - this@BackgroundPlaybackService.player?.destroy() - this@BackgroundPlaybackService.player = value - } - } - - private var player: Player? = null - private val binder = ServiceBinder(player) - - override fun onDestroy() { - player?.destroy() - player = null - - super.onDestroy() - } - - override fun onBind(intent: Intent?): IBinder { - return binder - } -} diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index d3ac01b5..edd84fd4 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -62,11 +62,5 @@ - - - From f2659ccf27e76fb5987ff7649bb8df116934dee6 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 24 Oct 2024 15:56:04 +0200 Subject: [PATCH 27/56] pair-programming session to unify services With Mario --- .../reactnative/BackgroundPlaybackManager.kt | 33 +++++------ .../player/reactnative/RNPlayerView.kt | 6 +- .../services/MediaSessionPlaybackService.kt | 57 ++++++++----------- 3 files changed, 40 insertions(+), 56 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt index b8f15525..5620ef05 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt @@ -4,47 +4,42 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection -import android.os.Binder import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.reactnative.extensions.playerModule -import com.bitmovin.player.reactnative.services.BackgroundPlaybackService +import com.bitmovin.player.reactnative.services.BackgroundPlaybackContext import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService import com.facebook.react.bridge.* -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow class BackgroundPlaybackManager(val context: ReactApplicationContext) { - private var isServiceStarted = false private lateinit var playerId: NativeId + private lateinit var playerModule: PlayerModule - private val _serviceBinder = MutableStateFlow(null) - val serviceBinder = _serviceBinder.asStateFlow() + internal var serviceBinder: MediaSessionPlaybackService.ServiceBinder? = null inner class BackgroundPlaybackServiceConnection : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { - val binder = service as PlayerServiceBinder - _serviceBinder.value = binder - binder.player = getPlayer() + val binder = service as MediaSessionPlaybackService.ServiceBinder + serviceBinder = binder + binder.context = BackgroundPlaybackContext( + getPlayer(), + playerModule.isMediaSessionPlaybackEnabled + ) } override fun onServiceDisconnected(name: ComponentName) { - _serviceBinder.value?.player = null + serviceBinder?.context = null } } fun setupBackgroundPlayback(playerId: NativeId, playerModule: PlayerModule) { - this@BackgroundPlaybackManager.playerId = playerId + this.playerId = playerId + this.playerModule = playerModule + val intent = Intent(context, MediaSessionPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON - intent.putExtra("isMediaSessionEnabled", playerModule.isMediaSessionPlaybackEnabled) val connection: ServiceConnection = BackgroundPlaybackServiceConnection() context.bindService(intent, connection, Context.BIND_AUTO_CREATE) - - if (!isServiceStarted) { - context.startService(intent) - isServiceStarted = true - } } private fun getPlayer( @@ -52,5 +47,3 @@ class BackgroundPlaybackManager(val context: ReactApplicationContext) { playerModule: PlayerModule? = context.playerModule, ): Player = playerModule?.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") } - -open class PlayerServiceBinder(open var player: Player?) : Binder() diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index e8b71f44..3e1e7f96 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -110,9 +110,11 @@ class RNPlayerView( private var playerEventRelay: EventRelay private var backgroundPlaybackServicePlayer: Player? - get() = context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.value?.player + get() = context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.context?.player set(value) { - context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.value?.player = value + value?.let { + context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.context?.player = it + } } private val activityLifecycleObserver = object : DefaultLifecycleObserver { diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index 16323ef0..c4cc2052 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -1,70 +1,59 @@ package com.bitmovin.player.reactnative.services import android.content.Intent +import android.os.Binder import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.api.media.session.MediaSession import com.bitmovin.player.api.media.session.MediaSessionService -import com.bitmovin.player.reactnative.PlayerServiceBinder + +class BackgroundPlaybackContext(var player: Player, var provideLockScreenControls: Boolean) class MediaSessionPlaybackService : MediaSessionService() { - inner class ServiceBinder(player: Player?) : PlayerServiceBinder(player) { - override var player: Player? - get() = this@MediaSessionPlaybackService.player + inner class ServiceBinder : Binder() { + var context: BackgroundPlaybackContext? + get() = this@MediaSessionPlaybackService.playbackContext set(value) { - this@MediaSessionPlaybackService.player?.destroy() - this@MediaSessionPlaybackService.player = value + disconnectSession() + this@MediaSessionPlaybackService.playbackContext = value value?.let { - mediaSession?.let { mediaSession -> - removeSession(mediaSession) - mediaSession.release() - } - - if (this@MediaSessionPlaybackService.isMediaSessionEnabled) { - createMediaSession(it) + if (it.provideLockScreenControls) { + createSession(it.player) + connectSession() } } } - - fun connectSession() = mediaSession?.let { addSession(it) } - fun disconnectSession() = mediaSession?.let { - removeSession(it) - it.release() - } } - private var player: Player? = null - private val binder = ServiceBinder(player) + private var playbackContext: BackgroundPlaybackContext? = null + private val binder = ServiceBinder() private var mediaSession: MediaSession? = null - private var isMediaSessionEnabled: Boolean = false - override fun onGetSession(): MediaSession? = mediaSession + override fun onGetSession(): MediaSession? = null override fun onDestroy() { - binder.disconnectSession() - - player?.destroy() - player = null + disconnectSession() + playbackContext = null super.onDestroy() } override fun onBind(intent: Intent?): IBinder { super.onBind(intent) - isMediaSessionEnabled = intent?.getBooleanExtra("isMediaSessionEnabled", isMediaSessionEnabled) ?: false return binder } - private fun createMediaSession(player: Player) { - binder.disconnectSession() - - val newMediaSession = MediaSession( + private fun createSession(player: Player) { + mediaSession = MediaSession( this, mainLooper, player, ) + } - mediaSession = newMediaSession - binder.connectSession() + private fun connectSession() = mediaSession?.let { addSession(it) } + private fun disconnectSession() = mediaSession?.let { + removeSession(it) + it.release() } } From 8a80b44ac0f3b0e511f401ff6904a94cb10273cc Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 25 Oct 2024 14:03:35 +0200 Subject: [PATCH 28/56] Rename service AS media session is only the widget API --- .../player/reactnative/BackgroundPlaybackManager.kt | 8 ++++---- ...ionPlaybackService.kt => BackgroundPlaybackService.kt} | 6 +++--- example/android/app/src/main/AndroidManifest.xml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename android/src/main/java/com/bitmovin/player/reactnative/services/{MediaSessionPlaybackService.kt => BackgroundPlaybackService.kt} (88%) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt index 5620ef05..b8624e8d 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt @@ -8,18 +8,18 @@ import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.reactnative.extensions.playerModule import com.bitmovin.player.reactnative.services.BackgroundPlaybackContext -import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService +import com.bitmovin.player.reactnative.services.BackgroundPlaybackService import com.facebook.react.bridge.* class BackgroundPlaybackManager(val context: ReactApplicationContext) { private lateinit var playerId: NativeId private lateinit var playerModule: PlayerModule - internal var serviceBinder: MediaSessionPlaybackService.ServiceBinder? = null + internal var serviceBinder: BackgroundPlaybackService.ServiceBinder? = null inner class BackgroundPlaybackServiceConnection : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { - val binder = service as MediaSessionPlaybackService.ServiceBinder + val binder = service as BackgroundPlaybackService.ServiceBinder serviceBinder = binder binder.context = BackgroundPlaybackContext( getPlayer(), @@ -36,7 +36,7 @@ class BackgroundPlaybackManager(val context: ReactApplicationContext) { this.playerId = playerId this.playerModule = playerModule - val intent = Intent(context, MediaSessionPlaybackService::class.java) + val intent = Intent(context, BackgroundPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON val connection: ServiceConnection = BackgroundPlaybackServiceConnection() context.bindService(intent, connection, Context.BIND_AUTO_CREATE) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt similarity index 88% rename from android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt rename to android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt index c4cc2052..6eb5a691 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt @@ -9,13 +9,13 @@ import com.bitmovin.player.api.media.session.MediaSessionService class BackgroundPlaybackContext(var player: Player, var provideLockScreenControls: Boolean) -class MediaSessionPlaybackService : MediaSessionService() { +class BackgroundPlaybackService : MediaSessionService() { inner class ServiceBinder : Binder() { var context: BackgroundPlaybackContext? - get() = this@MediaSessionPlaybackService.playbackContext + get() = this@BackgroundPlaybackService.playbackContext set(value) { disconnectSession() - this@MediaSessionPlaybackService.playbackContext = value + this@BackgroundPlaybackService.playbackContext = value value?.let { if (it.provideLockScreenControls) { createSession(it.player) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index edd84fd4..a43f93ff 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -54,7 +54,7 @@ android:value="com.bitmovin.player.casting.BitmovinCastOptionsProvider" /> From 6181e748ef82af7d1a7e643ff62df7615ba520d9 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 25 Oct 2024 14:13:55 +0200 Subject: [PATCH 29/56] Make media session independent of background playback As it is not related to background playback --- .../java/com/bitmovin/player/reactnative/PlayerModule.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 95b9cb10..57831cea 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -81,11 +81,8 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() isMediaSessionPlaybackEnabled = playerConfigJson?.getMap("lockScreenControlConfig") ?.toLockScreenControlConfig()?.isEnabled ?: false - isBackgroundPlaybackEnabled = if (isMediaSessionPlaybackEnabled) { - isMediaSessionPlaybackEnabled - } else { - playerConfigJson?.getMap("playbackConfig")?.getBoolean("isBackgroundPlaybackEnabled") ?: false - } + isBackgroundPlaybackEnabled = playerConfigJson?.getMap("playbackConfig") + ?.getBoolean("isBackgroundPlaybackEnabled") ?: false val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { @@ -103,7 +100,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex ) } - if (isBackgroundPlaybackEnabled) { + if (isMediaSessionPlaybackEnabled) { backgroundPlaybackConnectionManager = BackgroundPlaybackManager(context) promise.unit.resolveOnUiThread { backgroundPlaybackConnectionManager?.setupBackgroundPlayback(nativeId, this@PlayerModule) From 89f856ff86fdc3c540133a91a69daee258f6cef8 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 25 Oct 2024 14:17:54 +0200 Subject: [PATCH 30/56] Simplify since media session can be on without bg playback --- .../reactnative/BackgroundPlaybackManager.kt | 13 +++---------- .../player/reactnative/PlayerModule.kt | 2 +- .../player/reactnative/RNPlayerView.kt | 4 ++-- .../services/BackgroundPlaybackService.kt | 18 +++++++----------- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt index b8624e8d..59106040 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt @@ -7,34 +7,27 @@ import android.content.ServiceConnection import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.reactnative.extensions.playerModule -import com.bitmovin.player.reactnative.services.BackgroundPlaybackContext import com.bitmovin.player.reactnative.services.BackgroundPlaybackService import com.facebook.react.bridge.* class BackgroundPlaybackManager(val context: ReactApplicationContext) { private lateinit var playerId: NativeId - private lateinit var playerModule: PlayerModule - internal var serviceBinder: BackgroundPlaybackService.ServiceBinder? = null inner class BackgroundPlaybackServiceConnection : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { val binder = service as BackgroundPlaybackService.ServiceBinder serviceBinder = binder - binder.context = BackgroundPlaybackContext( - getPlayer(), - playerModule.isMediaSessionPlaybackEnabled - ) + binder.player = getPlayer() } override fun onServiceDisconnected(name: ComponentName) { - serviceBinder?.context = null + serviceBinder?.player = null } } - fun setupBackgroundPlayback(playerId: NativeId, playerModule: PlayerModule) { + fun setupBackgroundPlayback(playerId: NativeId) { this.playerId = playerId - this.playerModule = playerModule val intent = Intent(context, BackgroundPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 57831cea..b3972064 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -103,7 +103,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex if (isMediaSessionPlaybackEnabled) { backgroundPlaybackConnectionManager = BackgroundPlaybackManager(context) promise.unit.resolveOnUiThread { - backgroundPlaybackConnectionManager?.setupBackgroundPlayback(nativeId, this@PlayerModule) + backgroundPlaybackConnectionManager?.setupBackgroundPlayback(nativeId) } } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 3e1e7f96..f44d0cd1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -110,10 +110,10 @@ class RNPlayerView( private var playerEventRelay: EventRelay private var backgroundPlaybackServicePlayer: Player? - get() = context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.context?.player + get() = context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.player set(value) { value?.let { - context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.context?.player = it + context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.player = it } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt index 6eb5a691..5f4004d2 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt @@ -7,25 +7,21 @@ import com.bitmovin.player.api.Player import com.bitmovin.player.api.media.session.MediaSession import com.bitmovin.player.api.media.session.MediaSessionService -class BackgroundPlaybackContext(var player: Player, var provideLockScreenControls: Boolean) - class BackgroundPlaybackService : MediaSessionService() { inner class ServiceBinder : Binder() { - var context: BackgroundPlaybackContext? - get() = this@BackgroundPlaybackService.playbackContext + var player: Player? + get() = this@BackgroundPlaybackService.player set(value) { disconnectSession() - this@BackgroundPlaybackService.playbackContext = value + this@BackgroundPlaybackService.player = value value?.let { - if (it.provideLockScreenControls) { - createSession(it.player) - connectSession() - } + createSession(it) + connectSession() } } } - private var playbackContext: BackgroundPlaybackContext? = null + private var player: Player? = null private val binder = ServiceBinder() private var mediaSession: MediaSession? = null @@ -33,7 +29,7 @@ class BackgroundPlaybackService : MediaSessionService() { override fun onDestroy() { disconnectSession() - playbackContext = null + player = null super.onDestroy() } From 62c7c42e8f321893accda22e67104e7b52759abd Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 25 Oct 2024 14:19:12 +0200 Subject: [PATCH 31/56] Enable lock-screen to provide a notification As it is - good user experience on iOS - necessary on Android after certain OS version --- example/src/screens/BackgroundPlayback.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/src/screens/BackgroundPlayback.tsx b/example/src/screens/BackgroundPlayback.tsx index e1a43cd7..16f95725 100644 --- a/example/src/screens/BackgroundPlayback.tsx +++ b/example/src/screens/BackgroundPlayback.tsx @@ -20,6 +20,9 @@ export default function BackgroundPlayback() { playbackConfig: { isBackgroundPlaybackEnabled: true, }, + lockScreenControlConfig: { + isEnabled: true, + }, remoteControlConfig: { isCastEnabled: false, }, From b7e672f6d86d0b77b5679bc153e78c1b8b884777 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 25 Oct 2024 14:25:38 +0200 Subject: [PATCH 32/56] Use simpler names for variables And reduce scope where possible --- .../bitmovin/player/reactnative/PlayerModule.kt | 17 ++++++++--------- .../bitmovin/player/reactnative/RNPlayerView.kt | 14 +++++++------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index b3972064..e29f3333 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -26,9 +26,8 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() - var backgroundPlaybackConnectionManager: BackgroundPlaybackManager? = null - var isMediaSessionPlaybackEnabled: Boolean = false - var isBackgroundPlaybackEnabled: Boolean = false + var backgroundPlaybackManager: BackgroundPlaybackManager? = null + var enableBackgroundPlayback: Boolean = false /** * JS exported module name. @@ -79,9 +78,9 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val playerConfig = playerConfigJson?.toPlayerConfig() ?: PlayerConfig() val analyticsConfig = analyticsConfigJson?.toAnalyticsConfig() val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() - isMediaSessionPlaybackEnabled = playerConfigJson?.getMap("lockScreenControlConfig") + val enableMediaSession = playerConfigJson?.getMap("lockScreenControlConfig") ?.toLockScreenControlConfig()?.isEnabled ?: false - isBackgroundPlaybackEnabled = playerConfigJson?.getMap("playbackConfig") + enableBackgroundPlayback = playerConfigJson?.getMap("playbackConfig") ?.getBoolean("isBackgroundPlaybackEnabled") ?: false val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } @@ -100,10 +99,10 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex ) } - if (isMediaSessionPlaybackEnabled) { - backgroundPlaybackConnectionManager = BackgroundPlaybackManager(context) + if (enableMediaSession) { + backgroundPlaybackManager = BackgroundPlaybackManager(context) promise.unit.resolveOnUiThread { - backgroundPlaybackConnectionManager?.setupBackgroundPlayback(nativeId) + backgroundPlaybackManager?.setupBackgroundPlayback(nativeId) } } } @@ -230,7 +229,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex promise.unit.resolveOnUiThreadWithPlayer(nativeId) { destroy() players.remove(nativeId) - backgroundPlaybackConnectionManager = null + backgroundPlaybackManager = null } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index f44d0cd1..2603b95d 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -109,19 +109,19 @@ class RNPlayerView( */ private var playerEventRelay: EventRelay - private var backgroundPlaybackServicePlayer: Player? - get() = context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.player + private var playerInBackgroundService: Player? + get() = context.playerModule?.backgroundPlaybackManager?.serviceBinder?.player set(value) { value?.let { - context.playerModule?.backgroundPlaybackConnectionManager?.serviceBinder?.player = it + context.playerModule?.backgroundPlaybackManager?.serviceBinder?.player = it } } private val activityLifecycleObserver = object : DefaultLifecycleObserver { // Don't stop the player when going to background override fun onStart(owner: LifecycleOwner) { - if (backgroundPlaybackServicePlayer != null) { - player = backgroundPlaybackServicePlayer + if (playerInBackgroundService != null) { + player = playerInBackgroundService } playerView?.onStart() } @@ -135,8 +135,8 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { - if (context.playerModule?.isBackgroundPlaybackEnabled == false) { - backgroundPlaybackServicePlayer = null + if (context.playerModule?.enableBackgroundPlayback == false) { + playerInBackgroundService = null } else { player = null } From 496edd9fbd943973fa04e7cea8952e57fe545b6b Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 25 Oct 2024 14:45:14 +0200 Subject: [PATCH 33/56] Add note about Android needing a notification for bg playback --- src/playbackConfig.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/playbackConfig.ts b/src/playbackConfig.ts index f2ec9014..da60662d 100644 --- a/src/playbackConfig.ts +++ b/src/playbackConfig.ts @@ -50,6 +50,9 @@ export interface PlaybackConfig { * Default is `false`. * * @note + * On Android, {@link LockScreenControlConfig.isEnabled} has to be `true` for + * background playback to work. + * @note * On tvOS, background playback is only supported for audio-only content. * * @example From 5c3bb1399b8aa5355b4fb2a04cbedb2215e56376 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Fri, 25 Oct 2024 15:19:17 +0200 Subject: [PATCH 34/56] Rename BackgroundPlaybackService-related classes and vars As they're actually directly related to media session instead now --- ...Manager.kt => MediaSessionPlaybackManager.kt} | 16 ++++++++-------- .../bitmovin/player/reactnative/PlayerModule.kt | 8 ++++---- .../bitmovin/player/reactnative/RNPlayerView.kt | 12 ++++++------ ...Service.kt => MediaSessionPlaybackService.kt} | 6 +++--- example/android/app/src/main/AndroidManifest.xml | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) rename android/src/main/java/com/bitmovin/player/reactnative/{BackgroundPlaybackManager.kt => MediaSessionPlaybackManager.kt} (64%) rename android/src/main/java/com/bitmovin/player/reactnative/services/{BackgroundPlaybackService.kt => MediaSessionPlaybackService.kt} (87%) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionPlaybackManager.kt similarity index 64% rename from android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt rename to android/src/main/java/com/bitmovin/player/reactnative/MediaSessionPlaybackManager.kt index 59106040..40010966 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BackgroundPlaybackManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionPlaybackManager.kt @@ -7,16 +7,16 @@ import android.content.ServiceConnection import android.os.IBinder import com.bitmovin.player.api.Player import com.bitmovin.player.reactnative.extensions.playerModule -import com.bitmovin.player.reactnative.services.BackgroundPlaybackService +import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService import com.facebook.react.bridge.* -class BackgroundPlaybackManager(val context: ReactApplicationContext) { +class MediaSessionPlaybackManager(val context: ReactApplicationContext) { private lateinit var playerId: NativeId - internal var serviceBinder: BackgroundPlaybackService.ServiceBinder? = null + internal var serviceBinder: MediaSessionPlaybackService.ServiceBinder? = null - inner class BackgroundPlaybackServiceConnection : ServiceConnection { + inner class MediaSessionPlaybackServiceConnection : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { - val binder = service as BackgroundPlaybackService.ServiceBinder + val binder = service as MediaSessionPlaybackService.ServiceBinder serviceBinder = binder binder.player = getPlayer() } @@ -26,12 +26,12 @@ class BackgroundPlaybackManager(val context: ReactApplicationContext) { } } - fun setupBackgroundPlayback(playerId: NativeId) { + fun setupMediaSessionPlayback(playerId: NativeId) { this.playerId = playerId - val intent = Intent(context, BackgroundPlaybackService::class.java) + val intent = Intent(context, MediaSessionPlaybackService::class.java) intent.action = Intent.ACTION_MEDIA_BUTTON - val connection: ServiceConnection = BackgroundPlaybackServiceConnection() + val connection: ServiceConnection = MediaSessionPlaybackServiceConnection() context.bindService(intent, connection, Context.BIND_AUTO_CREATE) } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index e29f3333..c2c06555 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -26,7 +26,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() - var backgroundPlaybackManager: BackgroundPlaybackManager? = null + var mediaSessionPlaybackManager: MediaSessionPlaybackManager? = null var enableBackgroundPlayback: Boolean = false /** @@ -100,9 +100,9 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex } if (enableMediaSession) { - backgroundPlaybackManager = BackgroundPlaybackManager(context) + mediaSessionPlaybackManager = MediaSessionPlaybackManager(context) promise.unit.resolveOnUiThread { - backgroundPlaybackManager?.setupBackgroundPlayback(nativeId) + mediaSessionPlaybackManager?.setupMediaSessionPlayback(nativeId) } } } @@ -229,7 +229,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex promise.unit.resolveOnUiThreadWithPlayer(nativeId) { destroy() players.remove(nativeId) - backgroundPlaybackManager = null + mediaSessionPlaybackManager = null } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 2603b95d..24ed8bf1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -109,19 +109,19 @@ class RNPlayerView( */ private var playerEventRelay: EventRelay - private var playerInBackgroundService: Player? - get() = context.playerModule?.backgroundPlaybackManager?.serviceBinder?.player + private var playerInMediaSessionService: Player? + get() = context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player set(value) { value?.let { - context.playerModule?.backgroundPlaybackManager?.serviceBinder?.player = it + context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player = it } } private val activityLifecycleObserver = object : DefaultLifecycleObserver { // Don't stop the player when going to background override fun onStart(owner: LifecycleOwner) { - if (playerInBackgroundService != null) { - player = playerInBackgroundService + if (playerInMediaSessionService != null) { + player = playerInMediaSessionService } playerView?.onStart() } @@ -136,7 +136,7 @@ class RNPlayerView( override fun onStop(owner: LifecycleOwner) { if (context.playerModule?.enableBackgroundPlayback == false) { - playerInBackgroundService = null + playerInMediaSessionService = null } else { player = null } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt similarity index 87% rename from android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt rename to android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index 5f4004d2..b408e0d1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/BackgroundPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -7,13 +7,13 @@ import com.bitmovin.player.api.Player import com.bitmovin.player.api.media.session.MediaSession import com.bitmovin.player.api.media.session.MediaSessionService -class BackgroundPlaybackService : MediaSessionService() { +class MediaSessionPlaybackService : MediaSessionService() { inner class ServiceBinder : Binder() { var player: Player? - get() = this@BackgroundPlaybackService.player + get() = this@MediaSessionPlaybackService.player set(value) { disconnectSession() - this@BackgroundPlaybackService.player = value + this@MediaSessionPlaybackService.player = value value?.let { createSession(it) connectSession() diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index a43f93ff..edd84fd4 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -54,7 +54,7 @@ android:value="com.bitmovin.player.casting.BitmovinCastOptionsProvider" /> From 503a4fc2cad5854751751937e115b988c9f53646 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Mon, 4 Nov 2024 16:14:27 +0100 Subject: [PATCH 35/56] Move `playerEventRelay` initialization outside the `init` block --- .../com/bitmovin/player/reactnative/RNPlayerView.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 24ed8bf1..07e2e4b0 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -107,7 +107,10 @@ class RNPlayerView( * Relays the provided set of events, emitted by the player, together with the associated name * to the `eventOutput` callback. */ - private var playerEventRelay: EventRelay + private var playerEventRelay: EventRelay = EventRelay( + EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING, + ::emitEventFromPlayer, + ) private var playerInMediaSessionService: Player? get() = context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player @@ -148,11 +151,6 @@ class RNPlayerView( } init { - playerEventRelay = EventRelay( - EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING, - ::emitEventFromPlayer, - ) - // React Native has a bug that dynamically added views sometimes aren't laid out again properly. // Since we dynamically add and remove SurfaceView under the hood this caused the player // to suddenly not show the video anymore because SurfaceView was not laid out properly. From 5a4ff3c379dcaef1dab6fbaa5d6ed9349b976770 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Mon, 4 Nov 2024 16:14:53 +0100 Subject: [PATCH 36/56] Remove confusing comment --- .../main/java/com/bitmovin/player/reactnative/RNPlayerView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 07e2e4b0..483195d9 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -121,7 +121,6 @@ class RNPlayerView( } private val activityLifecycleObserver = object : DefaultLifecycleObserver { - // Don't stop the player when going to background override fun onStart(owner: LifecycleOwner) { if (playerInMediaSessionService != null) { player = playerInMediaSessionService From abaedf2fe5aedfd66baa95d12785e257ea35c0b4 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Mon, 4 Nov 2024 16:32:07 +0100 Subject: [PATCH 37/56] Allow proper setting to `null` as well, and remove useless code --- .../java/com/bitmovin/player/reactnative/RNPlayerView.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 483195d9..e178ce88 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -115,9 +115,7 @@ class RNPlayerView( private var playerInMediaSessionService: Player? get() = context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player set(value) { - value?.let { - context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player = it - } + context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player = value } private val activityLifecycleObserver = object : DefaultLifecycleObserver { @@ -137,9 +135,7 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { - if (context.playerModule?.enableBackgroundPlayback == false) { - playerInMediaSessionService = null - } else { + if (context.playerModule?.enableBackgroundPlayback == true) { player = null } From 735612276a11c7bc5af0e053e0c4e6fd8c4946a9 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Tue, 5 Nov 2024 16:02:49 +0100 Subject: [PATCH 38/56] Fix player recovering from service --- .../java/com/bitmovin/player/reactnative/RNPlayerView.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index e178ce88..bec9da18 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -117,11 +117,12 @@ class RNPlayerView( set(value) { context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player = value } + var player3:Player? = null private val activityLifecycleObserver = object : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { - if (playerInMediaSessionService != null) { - player = playerInMediaSessionService + if (player3 != null) { + player = player3 } playerView?.onStart() } @@ -136,6 +137,7 @@ class RNPlayerView( override fun onStop(owner: LifecycleOwner) { if (context.playerModule?.enableBackgroundPlayback == true) { + player3 = player player = null } From 95b632edee0dc10779307e438131c561791a2b24 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Tue, 5 Nov 2024 16:05:44 +0100 Subject: [PATCH 39/56] Refactor: move bg playback flag to view and cleanup --- .../bitmovin/player/reactnative/PlayerModule.kt | 5 +---- .../bitmovin/player/reactnative/RNPlayerView.kt | 17 ++++++----------- .../player/reactnative/RNPlayerViewManager.kt | 1 + 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index c2c06555..7a3cbcc1 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -26,8 +26,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() - var mediaSessionPlaybackManager: MediaSessionPlaybackManager? = null - var enableBackgroundPlayback: Boolean = false + private var mediaSessionPlaybackManager: MediaSessionPlaybackManager? = null /** * JS exported module name. @@ -80,8 +79,6 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() val enableMediaSession = playerConfigJson?.getMap("lockScreenControlConfig") ?.toLockScreenControlConfig()?.isEnabled ?: false - enableBackgroundPlayback = playerConfigJson?.getMap("playbackConfig") - ?.getBoolean("isBackgroundPlaybackEnabled") ?: false val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index bec9da18..26c98ef6 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -16,7 +16,6 @@ import com.bitmovin.player.api.event.SourceEvent import com.bitmovin.player.api.ui.PlayerViewConfig import com.bitmovin.player.api.ui.StyleConfig import com.bitmovin.player.reactnative.converter.toJson -import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.ReactActivity import com.facebook.react.bridge.* import com.facebook.react.uimanager.events.RCTEventEmitter @@ -112,17 +111,13 @@ class RNPlayerView( ::emitEventFromPlayer, ) - private var playerInMediaSessionService: Player? - get() = context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player - set(value) { - context.playerModule?.mediaSessionPlaybackManager?.serviceBinder?.player = value - } - var player3:Player? = null + internal var enableBackgroundPlayback: Boolean = false + var playerInMediaSessionService: Player? = null private val activityLifecycleObserver = object : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { - if (player3 != null) { - player = player3 + if (playerInMediaSessionService != null) { + player = playerInMediaSessionService } playerView?.onStart() } @@ -136,8 +131,8 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { - if (context.playerModule?.enableBackgroundPlayback == true) { - player3 = player + if (enableBackgroundPlayback) { + playerInMediaSessionService = player player = null } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt index 1cb0ffe2..842c945f 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt @@ -248,6 +248,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple val playbackConfig = playerConfig?.getMap("playbackConfig") val isPictureInPictureEnabled = view.config?.pictureInPictureConfig?.isEnabled == true || playbackConfig?.getBooleanOrNull("isPictureInPictureEnabled") == true + view.enableBackgroundPlayback = playbackConfig?.getBoolean("isBackgroundPlaybackEnabled") ?: false val rnStyleConfigWrapper = playerConfig?.toRNStyleConfigWrapperFromPlayerConfig() val configuredPlayerViewConfig = view.config?.playerViewConfig ?: PlayerViewConfig() From fe1e6517f1fe2b63efff420572a4f4f1d439b537 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Tue, 5 Nov 2024 16:16:15 +0100 Subject: [PATCH 40/56] Rename lockScreenControlConfig to mediaControlConfig --- CHANGELOG.md | 4 ++-- .../com/bitmovin/player/reactnative/PlayerModule.kt | 6 +++--- .../player/reactnative/converter/JsonConverter.kt | 4 ++-- example/src/screens/BackgroundPlayback.tsx | 2 +- example/src/screens/LockScreenControls.tsx | 2 +- ios/RCTConvert+BitmovinPlayer.swift | 4 ++-- src/index.ts | 2 +- ...ockScreenControlConfig.ts => mediaControlConfig.ts} | 10 +++++----- src/playbackConfig.ts | 2 +- src/playerConfig.ts | 8 ++++---- src/tweaksConfig.ts | 2 +- 11 files changed, 23 insertions(+), 23 deletions(-) rename src/{lockScreenControlConfig.ts => mediaControlConfig.ts} (82%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f48f467..e9023c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- `LockScreenControlConfig` to configure the lock screen information for the application. When `isEnabled` is `true`, the current media information will be shown on the lock-screen and within the control center +- `MediaControlConfig` to configure the media control information for the application. When `isEnabled` is `true`, the current media information will be shown on the lock-screen, in notifications, and within the control center - Android: `playerConfig.playbackConfig.isBackgroundPlaybackEnabled` to support background playback ### Changed @@ -13,7 +13,7 @@ ### Deprecated -- `TweaksConfig.updatesNowPlayingInfoCenter` in favor of `LockScreenControlConfig.isEnabled` +- `TweaksConfig.updatesNowPlayingInfoCenter` in favor of `MediaControlConfig.isEnabled` ## [0.29.0] - 2024-09-09 diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 7a3cbcc1..cf3ae046 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -10,7 +10,7 @@ import com.bitmovin.player.reactnative.converter.toAdItem import com.bitmovin.player.reactnative.converter.toAnalyticsConfig import com.bitmovin.player.reactnative.converter.toAnalyticsDefaultMetadata import com.bitmovin.player.reactnative.converter.toJson -import com.bitmovin.player.reactnative.converter.toLockScreenControlConfig +import com.bitmovin.player.reactnative.converter.toMediaControlConfig import com.bitmovin.player.reactnative.converter.toPlayerConfig import com.bitmovin.player.reactnative.extensions.mapToReactArray import com.facebook.react.bridge.* @@ -77,8 +77,8 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val playerConfig = playerConfigJson?.toPlayerConfig() ?: PlayerConfig() val analyticsConfig = analyticsConfigJson?.toAnalyticsConfig() val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() - val enableMediaSession = playerConfigJson?.getMap("lockScreenControlConfig") - ?.toLockScreenControlConfig()?.isEnabled ?: false + val enableMediaSession = playerConfigJson?.getMap("mediaControlConfig") + ?.toMediaControlConfig()?.isEnabled ?: false val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index 92843081..1df466c8 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -883,11 +883,11 @@ fun String.toMediaType(): MediaType? = when (this) { else -> null } -data class LockScreenControlConfig( +data class MediaControlConfig( var isEnabled: Boolean = false, ) -fun ReadableMap.toLockScreenControlConfig(): LockScreenControlConfig = LockScreenControlConfig().apply { +fun ReadableMap.toMediaControlConfig(): MediaControlConfig = MediaControlConfig().apply { withBoolean("isEnabled") { isEnabled = it } } diff --git a/example/src/screens/BackgroundPlayback.tsx b/example/src/screens/BackgroundPlayback.tsx index 16f95725..ba5a30ba 100644 --- a/example/src/screens/BackgroundPlayback.tsx +++ b/example/src/screens/BackgroundPlayback.tsx @@ -20,7 +20,7 @@ export default function BackgroundPlayback() { playbackConfig: { isBackgroundPlaybackEnabled: true, }, - lockScreenControlConfig: { + mediaControlConfig: { isEnabled: true, }, remoteControlConfig: { diff --git a/example/src/screens/LockScreenControls.tsx b/example/src/screens/LockScreenControls.tsx index 2d4a0dda..1022ca62 100644 --- a/example/src/screens/LockScreenControls.tsx +++ b/example/src/screens/LockScreenControls.tsx @@ -17,7 +17,7 @@ export default function LockScreenControls() { useTVGestures(); const player = usePlayer({ - lockScreenControlConfig: { + mediaControlConfig: { isEnabled: true, }, remoteControlConfig: { diff --git a/ios/RCTConvert+BitmovinPlayer.swift b/ios/RCTConvert+BitmovinPlayer.swift index c9f73fa2..3128c8dc 100644 --- a/ios/RCTConvert+BitmovinPlayer.swift +++ b/ios/RCTConvert+BitmovinPlayer.swift @@ -42,7 +42,7 @@ extension RCTConvert { if let networkConfig = RCTConvert.networkConfig(json["networkConfig"]) { playerConfig.networkConfig = networkConfig } - if let nowPlayingConfig = RCTConvert.lockScreenControlConfig(json["lockScreenControlConfig"]) { + if let nowPlayingConfig = RCTConvert.mediaControlConfig(json["mediaControlConfig"]) { playerConfig.nowPlayingConfig = nowPlayingConfig } #if os(iOS) @@ -1332,7 +1332,7 @@ extension RCTConvert { ] } - static func lockScreenControlConfig(_ json: Any?) -> NowPlayingConfig? { + static func mediaControlConfig(_ json: Any?) -> NowPlayingConfig? { guard let json = json as? [String: Any?] else { return nil } diff --git a/src/index.ts b/src/index.ts index f4219176..20d34fc7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,4 +24,4 @@ export * from './playerConfig'; export * from './liveConfig'; export * from './bufferApi'; export * from './network'; -export * from './lockScreenControlConfig'; +export * from './mediaControlConfig'; diff --git a/src/lockScreenControlConfig.ts b/src/mediaControlConfig.ts similarity index 82% rename from src/lockScreenControlConfig.ts rename to src/mediaControlConfig.ts index 66303898..b1db24bb 100644 --- a/src/lockScreenControlConfig.ts +++ b/src/mediaControlConfig.ts @@ -1,12 +1,12 @@ /** - * Configures the lock screen information for the application. This information will be displayed - * wherever current media information typically appears, such as the lock screen + * Configures the media control information for the application. This information will be displayed + * wherever current media information typically appears, such as the lock screen, in notifications, and * and inside the control center. */ -export interface LockScreenControlConfig { +export interface MediaControlConfig { /** * Enable the default behavior of displaying media information - * on the lock screen and within the control center. + * on the lock screen, in notifications, and within the control center. * * Default is `false`. * @@ -25,7 +25,7 @@ export interface LockScreenControlConfig { * - There is unexpected behavior when using the IMA SDK. The Google IMA SDK adds its own commands * for play/pause as soon as the ad starts loading (not when it starts playing). Within this window * (approximately around 10 seconds), it is possible that both the ad and the main content are playing - * at the same time when a user interacts with the lock-screen control feature. + * at the same time when a user interacts with the media control feature. * * ## Default Supported Features * --- diff --git a/src/playbackConfig.ts b/src/playbackConfig.ts index da60662d..acf019ee 100644 --- a/src/playbackConfig.ts +++ b/src/playbackConfig.ts @@ -50,7 +50,7 @@ export interface PlaybackConfig { * Default is `false`. * * @note - * On Android, {@link LockScreenControlConfig.isEnabled} has to be `true` for + * On Android, {@link MediaControlConfig.isEnabled} has to be `true` for * background playback to work. * @note * On tvOS, background playback is only supported for audio-only content. diff --git a/src/playerConfig.ts b/src/playerConfig.ts index 11e0e5b0..e6a68690 100644 --- a/src/playerConfig.ts +++ b/src/playerConfig.ts @@ -9,7 +9,7 @@ import { NativeInstanceConfig } from './nativeInstance'; import { PlaybackConfig } from './playbackConfig'; import { LiveConfig } from './liveConfig'; import { NetworkConfig } from './network/networkConfig'; -import { LockScreenControlConfig } from './lockScreenControlConfig'; +import { MediaControlConfig } from './mediaControlConfig'; /** * Object used to configure a new `Player` instance. @@ -75,9 +75,9 @@ export interface PlayerConfig extends NativeInstanceConfig { */ networkConfig?: NetworkConfig; /** - * Configures the lock screen information for the application. This information will be displayed - * wherever current media information typically appears, such as the lock screen + * Configures the media control information for the application. This information will be displayed + * wherever current media information typically appears, such as the lock screen, in notifications, * and inside the control center. */ - lockScreenControlConfig?: LockScreenControlConfig; + mediaControlConfig?: MediaControlConfig; } diff --git a/src/tweaksConfig.ts b/src/tweaksConfig.ts index d55b7020..2269baf7 100644 --- a/src/tweaksConfig.ts +++ b/src/tweaksConfig.ts @@ -166,7 +166,7 @@ export interface TweaksConfig { * * Default is `true`. * - * @deprecated To enable the Now Playing information use {@link LockScreenControlConfig.isEnabled} + * @deprecated To enable the Now Playing information use {@link MediaControlConfig.isEnabled} * @platform iOS */ updatesNowPlayingInfoCenter?: boolean; From 72f68f13e77adbdbc61b8245b2abf500834e1808 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Tue, 5 Nov 2024 16:24:57 +0100 Subject: [PATCH 41/56] Enable media controls by default - NowPlaying info for iOS - MediaSessions for Android --- .../java/com/bitmovin/player/reactnative/PlayerModule.kt | 2 +- ios/RCTConvert+BitmovinPlayer.swift | 5 +++-- src/mediaControlConfig.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index cf3ae046..d54a38ba 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -78,7 +78,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val analyticsConfig = analyticsConfigJson?.toAnalyticsConfig() val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() val enableMediaSession = playerConfigJson?.getMap("mediaControlConfig") - ?.toMediaControlConfig()?.isEnabled ?: false + ?.toMediaControlConfig()?.isEnabled ?: true val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { diff --git a/ios/RCTConvert+BitmovinPlayer.swift b/ios/RCTConvert+BitmovinPlayer.swift index 3128c8dc..4034411f 100644 --- a/ios/RCTConvert+BitmovinPlayer.swift +++ b/ios/RCTConvert+BitmovinPlayer.swift @@ -1333,10 +1333,11 @@ extension RCTConvert { } static func mediaControlConfig(_ json: Any?) -> NowPlayingConfig? { + let nowPlayingConfig = NowPlayingConfig() guard let json = json as? [String: Any?] else { - return nil + nowPlayingConfig.isNowPlayingInfoEnabled = true + return nowPlayingConfig } - let nowPlayingConfig = NowPlayingConfig() if let isEnabled = json["isEnabled"] as? Bool { nowPlayingConfig.isNowPlayingInfoEnabled = isEnabled } diff --git a/src/mediaControlConfig.ts b/src/mediaControlConfig.ts index b1db24bb..3b7fec19 100644 --- a/src/mediaControlConfig.ts +++ b/src/mediaControlConfig.ts @@ -8,7 +8,7 @@ export interface MediaControlConfig { * Enable the default behavior of displaying media information * on the lock screen, in notifications, and within the control center. * - * Default is `false`. + * Default is `true`. * * For a detailed list of the supported features in the **default behavior**, * check the **Default Supported Features** section. From b97c8c1eb5b660354be1b087db575c04ff51321c Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 09:27:09 +0100 Subject: [PATCH 42/56] Rename sample as well --- example/src/App.tsx | 14 +++++++------- .../{LockScreenControls.tsx => MediaControls.tsx} | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) rename example/src/screens/{LockScreenControls.tsx => MediaControls.tsx} (97%) diff --git a/example/src/App.tsx b/example/src/App.tsx index 7eeda420..29826188 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -20,7 +20,7 @@ import LandscapeFullscreenHandling from './screens/LandscapeFullscreenHandling'; import SystemUI from './screens/SystemUi'; import OfflinePlayback from './screens/OfflinePlayback'; import Casting from './screens/Casting'; -import LockScreenControls from './screens/LockScreenControls'; +import MediaControls from './screens/MediaControls'; import BackgroundPlayback from './screens/BackgroundPlayback'; export type RootStackParamsList = { @@ -60,7 +60,7 @@ export type RootStackParamsList = { }; Casting: undefined; SystemUI: undefined; - LockScreenControls: undefined; + MediaControls: undefined; BackgroundPlayback: undefined; }; @@ -114,8 +114,8 @@ export default function App() { routeName: 'ProgrammaticTrackSelection' as keyof RootStackParamsList, }, { - title: 'Lock-Screen Controls', - routeName: 'LockScreenControls' as keyof RootStackParamsList, + title: 'Media Controls', + routeName: 'MediaControls' as keyof RootStackParamsList, }, { title: 'Background Playback', @@ -275,9 +275,9 @@ export default function App() { /> )} Date: Wed, 6 Nov 2024 09:29:35 +0100 Subject: [PATCH 43/56] Fix wrong default value --- .../com/bitmovin/player/reactnative/converter/JsonConverter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index 1df466c8..1a0f6a89 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -884,7 +884,7 @@ fun String.toMediaType(): MediaType? = when (this) { } data class MediaControlConfig( - var isEnabled: Boolean = false, + var isEnabled: Boolean = true, ) fun ReadableMap.toMediaControlConfig(): MediaControlConfig = MediaControlConfig().apply { From d244a2c5ad6e97933122c3f0dcd23a1b64d30c76 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 13:09:07 +0100 Subject: [PATCH 44/56] Remove default value in case of nil --- .../main/java/com/bitmovin/player/reactnative/PlayerModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index d54a38ba..99441a32 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -78,7 +78,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val analyticsConfig = analyticsConfigJson?.toAnalyticsConfig() val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() val enableMediaSession = playerConfigJson?.getMap("mediaControlConfig") - ?.toMediaControlConfig()?.isEnabled ?: true + ?.toMediaControlConfig()?.isEnabled val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { From 36e88e6d28a8066797fef76bb963fb2dc930c50b Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 13:18:04 +0100 Subject: [PATCH 45/56] Fix state management Pair-programming session w Mario Improve general player view and player management Fix: - Restore the correct player from media session - Media Session not getting destroyed as the `@ReactMethod onDestroy()` in `PlayerModule.kt` was nullifying the media session manager inside the promise block. However, the main thread was already killed from JS, so that code was unreached Spot and comment a bug in `PlayerView` creation in Android SDK side --- .../MediaSessionPlaybackManager.kt | 12 +++++++++-- .../player/reactnative/PlayerModule.kt | 4 ++-- .../player/reactnative/RNPlayerView.kt | 21 +++++++++++++++---- .../player/reactnative/RNPlayerViewManager.kt | 1 + .../services/MediaSessionPlaybackService.kt | 4 ++++ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionPlaybackManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionPlaybackManager.kt index 40010966..15de7745 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionPlaybackManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/MediaSessionPlaybackManager.kt @@ -11,8 +11,10 @@ import com.bitmovin.player.reactnative.services.MediaSessionPlaybackService import com.facebook.react.bridge.* class MediaSessionPlaybackManager(val context: ReactApplicationContext) { + private var serviceBinder: MediaSessionPlaybackService.ServiceBinder? = null private lateinit var playerId: NativeId - internal var serviceBinder: MediaSessionPlaybackService.ServiceBinder? = null + val player: Player? + get() = serviceBinder?.player inner class MediaSessionPlaybackServiceConnection : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { @@ -22,7 +24,7 @@ class MediaSessionPlaybackManager(val context: ReactApplicationContext) { } override fun onServiceDisconnected(name: ComponentName) { - serviceBinder?.player = null + destroy(playerId) } } @@ -35,6 +37,12 @@ class MediaSessionPlaybackManager(val context: ReactApplicationContext) { context.bindService(intent, connection, Context.BIND_AUTO_CREATE) } + fun destroy(nativeId: NativeId) { + if (nativeId != playerId) { return } + serviceBinder?.player = null + serviceBinder = null + } + private fun getPlayer( nativeId: NativeId = playerId, playerModule: PlayerModule? = context.playerModule, diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 99441a32..6358ef55 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -26,7 +26,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() - private var mediaSessionPlaybackManager: MediaSessionPlaybackManager? = null + var mediaSessionPlaybackManager: MediaSessionPlaybackManager? = null /** * JS exported module name. @@ -223,10 +223,10 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ @ReactMethod fun destroy(nativeId: NativeId, promise: Promise) { + mediaSessionPlaybackManager?.destroy(nativeId) promise.unit.resolveOnUiThreadWithPlayer(nativeId) { destroy() players.remove(nativeId) - mediaSessionPlaybackManager = null } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 26c98ef6..4576d046 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -16,6 +16,7 @@ import com.bitmovin.player.api.event.SourceEvent import com.bitmovin.player.api.ui.PlayerViewConfig import com.bitmovin.player.api.ui.StyleConfig import com.bitmovin.player.reactnative.converter.toJson +import com.bitmovin.player.reactnative.extensions.playerModule import com.facebook.react.ReactActivity import com.facebook.react.bridge.* import com.facebook.react.uimanager.events.RCTEventEmitter @@ -117,7 +118,7 @@ class RNPlayerView( private val activityLifecycleObserver = object : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { if (playerInMediaSessionService != null) { - player = playerInMediaSessionService + playerView?.player = playerInMediaSessionService } playerView?.onStart() } @@ -131,9 +132,20 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { - if (enableBackgroundPlayback) { - playerInMediaSessionService = player - player = null + playerInMediaSessionService = null + + // Remove player from view so it does not get paused when entering background + // when background playback is enabled + playerView?.player?.let { + if (!enableBackgroundPlayback) { + return + } + if (context.playerModule?.mediaSessionPlaybackManager?.player != it) { + return + } + + playerInMediaSessionService = it + playerView?.player = null } playerView?.onStop() @@ -201,6 +213,7 @@ class RNPlayerView( // is responsible for destroying the player in the end. playerView.player = null playerView.onDestroy() + (playerView.parent as? ViewGroup)?.removeView(playerView) } /** diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt index 842c945f..71dd565d 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt @@ -297,6 +297,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple block() } catch (e: Exception) { Log.e(MODULE_NAME, "Error while executing command", e) + // TODO: re throw } } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt index b408e0d1..ff7afa28 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/services/MediaSessionPlaybackService.kt @@ -12,6 +12,10 @@ class MediaSessionPlaybackService : MediaSessionService() { var player: Player? get() = this@MediaSessionPlaybackService.player set(value) { + if (player == value) { + return + } + disconnectSession() this@MediaSessionPlaybackService.player = value value?.let { From 8d2fdb3fa146d6aaf34acd87f2bc10fbe2fc0b43 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 14:11:54 +0100 Subject: [PATCH 46/56] Export bg playback handling in a func for `onStop` --- .../bitmovin/player/reactnative/RNPlayerView.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 4576d046..cfde71f5 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -133,9 +133,15 @@ class RNPlayerView( override fun onStop(owner: LifecycleOwner) { playerInMediaSessionService = null + removePlayerForBackgroundPlayback() + playerView?.onStop() + } + + override fun onDestroy(owner: LifecycleOwner) = dispose() - // Remove player from view so it does not get paused when entering background - // when background playback is enabled + // When background playback is enabled, + // remove player from view so it does not get paused when entering background + private fun removePlayerForBackgroundPlayback() { playerView?.player?.let { if (!enableBackgroundPlayback) { return @@ -147,11 +153,7 @@ class RNPlayerView( playerInMediaSessionService = it playerView?.player = null } - - playerView?.onStop() } - - override fun onDestroy(owner: LifecycleOwner) = dispose() } init { From 2f09d63315996c23b058993e7a2d9840444774c0 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 14:12:44 +0100 Subject: [PATCH 47/56] Fix default value As this config is not existing on Native, we have to set a default value directly here Because it may not be there in the Json --- .../main/java/com/bitmovin/player/reactnative/PlayerModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 6358ef55..c8044ffb 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -78,7 +78,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex val analyticsConfig = analyticsConfigJson?.toAnalyticsConfig() val defaultMetadata = analyticsConfigJson?.getMap("defaultMetadata")?.toAnalyticsDefaultMetadata() val enableMediaSession = playerConfigJson?.getMap("mediaControlConfig") - ?.toMediaControlConfig()?.isEnabled + ?.toMediaControlConfig()?.isEnabled ?: true val networkConfig = networkNativeId?.let { networkModule.getConfig(it) } if (networkConfig != null) { From e22981ee3486a6a5546eeadde68eaa97ff8be076 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 15:11:13 +0100 Subject: [PATCH 48/56] Move statement inside block --- .../main/java/com/bitmovin/player/reactnative/RNPlayerView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index cfde71f5..1593b78f 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -132,7 +132,6 @@ class RNPlayerView( } override fun onStop(owner: LifecycleOwner) { - playerInMediaSessionService = null removePlayerForBackgroundPlayback() playerView?.onStop() } @@ -142,6 +141,7 @@ class RNPlayerView( // When background playback is enabled, // remove player from view so it does not get paused when entering background private fun removePlayerForBackgroundPlayback() { + playerInMediaSessionService = null playerView?.player?.let { if (!enableBackgroundPlayback) { return From a70276777d5a8dfca953c95f0bc8d81a7741addf Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 15:22:45 +0100 Subject: [PATCH 49/56] Remove media control sample since it is enabled by default --- example/src/App.tsx | 11 ---- example/src/screens/MediaControls.tsx | 88 --------------------------- 2 files changed, 99 deletions(-) delete mode 100644 example/src/screens/MediaControls.tsx diff --git a/example/src/App.tsx b/example/src/App.tsx index 29826188..a8b3ea33 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -20,7 +20,6 @@ import LandscapeFullscreenHandling from './screens/LandscapeFullscreenHandling'; import SystemUI from './screens/SystemUi'; import OfflinePlayback from './screens/OfflinePlayback'; import Casting from './screens/Casting'; -import MediaControls from './screens/MediaControls'; import BackgroundPlayback from './screens/BackgroundPlayback'; export type RootStackParamsList = { @@ -60,7 +59,6 @@ export type RootStackParamsList = { }; Casting: undefined; SystemUI: undefined; - MediaControls: undefined; BackgroundPlayback: undefined; }; @@ -113,10 +111,6 @@ export default function App() { title: 'Programmatic Track Selection', routeName: 'ProgrammaticTrackSelection' as keyof RootStackParamsList, }, - { - title: 'Media Controls', - routeName: 'MediaControls' as keyof RootStackParamsList, - }, { title: 'Background Playback', routeName: 'BackgroundPlayback' as keyof RootStackParamsList, @@ -274,11 +268,6 @@ export default function App() { options={{ title: 'Casting' }} /> )} - { - player.load({ - url: - Platform.OS === 'ios' - ? 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8' - : 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd', - type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH, - title: 'Art of Motion', - poster: - 'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/poster.jpg', - thumbnailTrack: - 'https://cdn.bitmovin.com/content/assets/art-of-motion-dash-hls-progressive/thumbnails/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.vtt', - metadata: { platform: Platform.OS }, - }); - return () => { - player.destroy(); - }; - }, [player]) - ); - - const onReady = useCallback((event: Event) => { - prettyPrint(`EVENT [${event.name}]`, event); - }, []); - - const onEvent = useCallback((event: Event) => { - prettyPrint(`EVENT [${event.name}]`, event); - }, []); - - return ( - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: 'black', - }, - player: { - flex: 1, - }, -}); From cf7c34f41d4b752a1d2209df1029babd30b823e9 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 15:33:06 +0100 Subject: [PATCH 50/56] Move initialization of media session manager outside init block --- .../main/java/com/bitmovin/player/reactnative/PlayerModule.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index c8044ffb..470e2c27 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -26,7 +26,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() - var mediaSessionPlaybackManager: MediaSessionPlaybackManager? = null + var mediaSessionPlaybackManager: MediaSessionPlaybackManager? = MediaSessionPlaybackManager(context) /** * JS exported module name. @@ -97,7 +97,6 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex } if (enableMediaSession) { - mediaSessionPlaybackManager = MediaSessionPlaybackManager(context) promise.unit.resolveOnUiThread { mediaSessionPlaybackManager?.setupMediaSessionPlayback(nativeId) } From f0b06d7f338a79ed86b7567b248400a773bad994 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 15:39:54 +0100 Subject: [PATCH 51/56] Optimize var --- .../java/com/bitmovin/player/reactnative/PlayerModule.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 470e2c27..b4a45185 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -26,7 +26,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ private val players: Registry = mutableMapOf() - var mediaSessionPlaybackManager: MediaSessionPlaybackManager? = MediaSessionPlaybackManager(context) + val mediaSessionPlaybackManager = MediaSessionPlaybackManager(context) /** * JS exported module name. @@ -98,7 +98,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex if (enableMediaSession) { promise.unit.resolveOnUiThread { - mediaSessionPlaybackManager?.setupMediaSessionPlayback(nativeId) + mediaSessionPlaybackManager.setupMediaSessionPlayback(nativeId) } } } @@ -222,7 +222,7 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ @ReactMethod fun destroy(nativeId: NativeId, promise: Promise) { - mediaSessionPlaybackManager?.destroy(nativeId) + mediaSessionPlaybackManager.destroy(nativeId) promise.unit.resolveOnUiThreadWithPlayer(nativeId) { destroy() players.remove(nativeId) From ad495fb21d41b9ea3dfebc30c8d40f3438717da4 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 15:40:10 +0100 Subject: [PATCH 52/56] Document android limitation --- src/mediaControlConfig.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mediaControlConfig.ts b/src/mediaControlConfig.ts index 3b7fec19..145a0c38 100644 --- a/src/mediaControlConfig.ts +++ b/src/mediaControlConfig.ts @@ -17,6 +17,7 @@ export interface MediaControlConfig { * * ## Limitations * --- + * - Android: If an app creates multiple player instances, the player shown in media controls is the latest one created having media controls enabled. * - At the moment, the current media information is disabled during casting. * * ## Known Issues From 554967836fc4e69ceaed9cca7bd9b446cee85ad8 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 15:40:33 +0100 Subject: [PATCH 53/56] Remove unnecessary code --- .../main/java/com/bitmovin/player/reactnative/RNPlayerView.kt | 1 - .../java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 1593b78f..1218c346 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -228,7 +228,6 @@ class RNPlayerView( } this._playerView = playerView if (playerView.parent != this) { - (playerView.parent as ViewGroup?)?.removeView(playerView) addView(playerView, 0) } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt index 71dd565d..842c945f 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt @@ -297,7 +297,6 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple block() } catch (e: Exception) { Log.e(MODULE_NAME, "Error while executing command", e) - // TODO: re throw } } } From adb0ea11a857bfa01f91eac97e70dbff5dbc0d6d Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 15:50:37 +0100 Subject: [PATCH 54/56] Remove correct line --- .../main/java/com/bitmovin/player/reactnative/RNPlayerView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 1218c346..84b86310 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -215,7 +215,6 @@ class RNPlayerView( // is responsible for destroying the player in the end. playerView.player = null playerView.onDestroy() - (playerView.parent as? ViewGroup)?.removeView(playerView) } /** @@ -228,6 +227,7 @@ class RNPlayerView( } this._playerView = playerView if (playerView.parent != this) { + (playerView.parent as ViewGroup?)?.removeView(playerView) addView(playerView, 0) } } From 6d625727e94186932894d5da40136fe2177db670 Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Wed, 6 Nov 2024 16:57:41 +0100 Subject: [PATCH 55/56] Update to stable version having Media Session API --- android/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 67a97e89..37d586f7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -105,5 +105,6 @@ dependencies { // Bitmovin implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.33.0' implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' - implementation 'com.bitmovin.player:player:3.84.0+jason' + implementation 'com.bitmovin.player:player:3.91.0+jason' + implementation 'com.bitmovin.player:player-media-session:3.91.0+jason' } From b12d6b09405e5f226371433351402a081b5e60cf Mon Sep 17 00:00:00 2001 From: Michele Pozzi <123.mpozzi@gmail.com> Date: Thu, 7 Nov 2024 11:31:47 +0100 Subject: [PATCH 56/56] Do not use `let` for nullability check to save indentation space --- .../player/reactnative/RNPlayerView.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 84b86310..db019681 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -142,17 +142,17 @@ class RNPlayerView( // remove player from view so it does not get paused when entering background private fun removePlayerForBackgroundPlayback() { playerInMediaSessionService = null - playerView?.player?.let { - if (!enableBackgroundPlayback) { - return - } - if (context.playerModule?.mediaSessionPlaybackManager?.player != it) { - return - } - - playerInMediaSessionService = it - playerView?.player = null + val player = playerView?.player ?: return + + if (!enableBackgroundPlayback) { + return + } + if (context.playerModule?.mediaSessionPlaybackManager?.player != player) { + return } + + playerInMediaSessionService = player + playerView?.player = null } }