From f0e709afa64034c1ec548ba314bd4457fdbd313f Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Wed, 26 Jul 2023 12:18:59 +0200 Subject: [PATCH 01/10] feat(android): add command to enter/exit fullscreen --- .../player/reactnative/RNPlayerViewManager.kt | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) 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 fce5952e..f32d147e 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt @@ -24,10 +24,11 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple /** * Native component functions. */ - enum class Commands { - ATTACH_PLAYER, - ATTACH_FULLSCREEN_BRIDGE, - SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID, + enum class Commands(val command: String) { + ATTACH_PLAYER("attachPlayer"), + ATTACH_FULLSCREEN_BRIDGE("attachFullscreenBridge"), + SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID("setCustomMessageHandlerBridgeId"), + SET_FULLSCREEN("setFullscreen"); } /** @@ -132,11 +133,9 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple * to call 'functions' on them. * @return map between names (used in js) and command ids (used in native code). */ - override fun getCommandsMap(): MutableMap = mutableMapOf( - "attachPlayer" to Commands.ATTACH_PLAYER.ordinal, - "attachFullscreenBridge" to Commands.ATTACH_FULLSCREEN_BRIDGE.ordinal, - "setCustomMessageHandlerBridgeId" to Commands.SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID.ordinal, - ) + override fun getCommandsMap(): MutableMap = Commands.values().associate { + it.command to it.ordinal + }.toMutableMap() /** * Callback triggered in response to command dispatches from the js side. @@ -145,19 +144,21 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple * @param args Arguments list sent from the js side. */ override fun receiveCommand(view: RNPlayerView, commandId: String?, args: ReadableArray?) { - super.receiveCommand(view, commandId, args) - commandId?.toInt()?.let { - when (it) { - Commands.ATTACH_PLAYER.ordinal -> attachPlayer(view, args?.getString(1), args?.getMap(2)) - Commands.ATTACH_FULLSCREEN_BRIDGE.ordinal -> args?.getString(1)?.let { fullscreenBridgeId -> - attachFullscreenBridge(view, fullscreenBridgeId) + val command = commandId?.toInt()?.toCommand() ?: return + when (command) { + Commands.ATTACH_PLAYER -> attachPlayer(view, args?.getString(1), args?.getMap(2)) + Commands.ATTACH_FULLSCREEN_BRIDGE -> args?.getString(1)?.let { fullscreenBridgeId -> + attachFullscreenBridge(view, fullscreenBridgeId) + } + Commands.SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID -> { + args?.getString(1)?.let { customMessageHandlerBridgeId -> + setCustomMessageHandlerBridgeId(view, customMessageHandlerBridgeId) } - Commands.SET_CUSTOM_MESSAGE_HANDLER_BRIDGE_ID.ordinal -> { - args?.getString(1)?.let { customMessageHandlerBridgeId -> - setCustomMessageHandlerBridgeId(view, customMessageHandlerBridgeId) - } + } + Commands.SET_FULLSCREEN -> { + args?.getBoolean(1)?.let { isFullscreen -> + setFullscreen(view, isFullscreen) } - else -> {} } } } @@ -170,6 +171,20 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple } } + private fun setFullscreen(view: RNPlayerView, isFullscreen: Boolean) { + if (view.playerView?.isFullscreen == isFullscreen) return + + Handler(Looper.getMainLooper()).post { + with(view.playerView ?: return@post) { + if (isFullscreen) { + enterFullscreen() + } else { + exitFullscreen() + } + } + } + } + private fun setCustomMessageHandlerBridgeId(view: RNPlayerView, customMessageHandlerBridgeId: NativeId) { this.customMessageHandlerBridgeId = customMessageHandlerBridgeId attachCustomMessageHandlerBridge(view) @@ -223,3 +238,5 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple */ private fun getPlayerModule(): PlayerModule? = context.getModule() } + +private fun Int.toCommand(): RNPlayerViewManager.Commands? = RNPlayerViewManager.Commands.values().getOrNull(this) From f5ffb7d94610ad4ba47b56283e16e88abd0c8a7f Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Wed, 26 Jul 2023 14:15:44 +0200 Subject: [PATCH 02/10] feat(ios): add command to enter/exit fullscreen --- ios/RNPlayerViewManager.m | 1 + ios/RNPlayerViewManager.swift | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/ios/RNPlayerViewManager.m b/ios/RNPlayerViewManager.m index 5d4c3b3f..b81f1105 100644 --- a/ios/RNPlayerViewManager.m +++ b/ios/RNPlayerViewManager.m @@ -56,5 +56,6 @@ @interface RCT_EXTERN_REMAP_MODULE(NativePlayerView, RNPlayerViewManager, RCTVie RCT_EXTERN_METHOD(attachPlayer:(nonnull NSNumber *)viewId playerId:(NSString *)playerId playerConfig:(nullable NSDictionary *)playerConfig) RCT_EXTERN_METHOD(attachFullscreenBridge:(nonnull NSNumber *)viewId fullscreenBridgeId:(NSString *)fullscreenBridgeId) RCT_EXTERN_METHOD(setCustomMessageHandlerBridgeId:(nonnull NSNumber *)viewId customMessageHandlerBridgeId:(NSString *)fullscreenBridgeId) + RCT_EXTERN_METHOD(setFullscreen:(nonnull NSNumber *)viewId isFullscreen:(BOOL)isFullscreen) @end diff --git a/ios/RNPlayerViewManager.swift b/ios/RNPlayerViewManager.swift index d66d1a6b..9040fa4b 100644 --- a/ios/RNPlayerViewManager.swift +++ b/ios/RNPlayerViewManager.swift @@ -71,6 +71,28 @@ class RNPlayerViewManager: RCTViewManager { self.customMessageHandlerBridgeId = customMessageHandlerBridgeId } + @objc func setFullscreen(_ viewId: NSNumber, isFullscreen: Bool) { + bridge.uiManager.addUIBlock { [weak self] _, views in + guard + let self, + let view = views?[viewId] as? RNPlayerView + else { + return + } + guard let playerView = view.playerView else { + return + } + if playerView.isFullscreen == isFullscreen { + return + } + if isFullscreen { + playerView.enterFullscreen() + } else { + playerView.exitFullscreen() + } + } + } + /// Fetches the initialized `PlayerModule` instance on RN's bridge object. private func getPlayerModule() -> PlayerModule? { bridge.module(for: PlayerModule.self) as? PlayerModule From 869d3e0be9cf7f70e560af76ae4283116bcc8787 Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Wed, 26 Jul 2023 14:16:39 +0200 Subject: [PATCH 03/10] feat: add isFullscreen prop to the `PlayerView`, to define the fullscreen state programatically --- example/src/screens/BasicFullscreenHandling.tsx | 1 + src/components/PlayerView/index.tsx | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/example/src/screens/BasicFullscreenHandling.tsx b/example/src/screens/BasicFullscreenHandling.tsx index c49c31b0..c33641c3 100644 --- a/example/src/screens/BasicFullscreenHandling.tsx +++ b/example/src/screens/BasicFullscreenHandling.tsx @@ -103,6 +103,7 @@ export default function BasicFullscreenHandling({ { fullscreenBridge.current?.destroy(); fullscreenBridge.current = undefined; @@ -135,6 +139,13 @@ export function PlayerView({ customMessageHandlerBridge.current = undefined; }; }, [player]); + + useEffect(() => { + const node = findNodeHandle(nativeView.current); + if (node) { + dispatch('setFullscreen', node, isFullscreen); + } + }, [isFullscreen, nativeView]); return ( Date: Wed, 26 Jul 2023 15:05:59 +0200 Subject: [PATCH 04/10] chore: add documentation to PlayerView props --- src/components/PlayerView/index.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/PlayerView/index.tsx b/src/components/PlayerView/index.tsx index b3a143b1..45db4387 100644 --- a/src/components/PlayerView/index.tsx +++ b/src/components/PlayerView/index.tsx @@ -35,10 +35,22 @@ export interface PlayerViewProps extends BasePlayerViewProps, PlayerViewEvents { */ player: Player; + /** + * The `FullscreenHandler` that is used by the `PlayerView` to control the fullscreen mode. + */ fullscreenHandler?: FullscreenHandler; + /** + * The `CustomMessageHandler` that can be used to directly communicate with the embedded WebUi. + */ customMessageHandler?: CustomMessageHandler; + /** + * Can be set to `true` to enter fullscreen mode, or `false` to exit fullscreen mode. + * By using this property to change the fullscreen state, we make sure that the embedded Player UI is also aware + * of potential fullscreen state changes. + * To use this property, a `FullscreenHandler` must be set. + */ isFullscreen?: Boolean; } From a1ccb83e483e37cb230a7cb7c3c2811c270982d6 Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Thu, 27 Jul 2023 15:47:25 +0200 Subject: [PATCH 05/10] Update ios/RNPlayerViewManager.m MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roland Kákonyi --- ios/RNPlayerViewManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RNPlayerViewManager.m b/ios/RNPlayerViewManager.m index b81f1105..18f8f109 100644 --- a/ios/RNPlayerViewManager.m +++ b/ios/RNPlayerViewManager.m @@ -56,6 +56,6 @@ @interface RCT_EXTERN_REMAP_MODULE(NativePlayerView, RNPlayerViewManager, RCTVie RCT_EXTERN_METHOD(attachPlayer:(nonnull NSNumber *)viewId playerId:(NSString *)playerId playerConfig:(nullable NSDictionary *)playerConfig) RCT_EXTERN_METHOD(attachFullscreenBridge:(nonnull NSNumber *)viewId fullscreenBridgeId:(NSString *)fullscreenBridgeId) RCT_EXTERN_METHOD(setCustomMessageHandlerBridgeId:(nonnull NSNumber *)viewId customMessageHandlerBridgeId:(NSString *)fullscreenBridgeId) - RCT_EXTERN_METHOD(setFullscreen:(nonnull NSNumber *)viewId isFullscreen:(BOOL)isFullscreen) +RCT_EXTERN_METHOD(setFullscreen:(nonnull NSNumber *)viewId isFullscreen:(BOOL)isFullscreen) @end From 38c973e9147e3dbb9c76cbb71ab7050d1d9ca96a Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Thu, 27 Jul 2023 15:47:35 +0200 Subject: [PATCH 06/10] Update ios/RNPlayerViewManager.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roland Kákonyi --- ios/RNPlayerViewManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RNPlayerViewManager.swift b/ios/RNPlayerViewManager.swift index 9040fa4b..3014a145 100644 --- a/ios/RNPlayerViewManager.swift +++ b/ios/RNPlayerViewManager.swift @@ -82,7 +82,7 @@ class RNPlayerViewManager: RCTViewManager { guard let playerView = view.playerView else { return } - if playerView.isFullscreen == isFullscreen { + guard playerView.isFullscreen != isFullscreen else { return } if isFullscreen { From 4d36f5b8ba135ae820fa4e178717fee0173ef631 Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Thu, 27 Jul 2023 15:52:17 +0200 Subject: [PATCH 07/10] chore(android): remove explicit property with default value --- example/src/screens/BasicFullscreenHandling.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/example/src/screens/BasicFullscreenHandling.tsx b/example/src/screens/BasicFullscreenHandling.tsx index c33641c3..c49c31b0 100644 --- a/example/src/screens/BasicFullscreenHandling.tsx +++ b/example/src/screens/BasicFullscreenHandling.tsx @@ -103,7 +103,6 @@ export default function BasicFullscreenHandling({ Date: Thu, 27 Jul 2023 16:26:21 +0200 Subject: [PATCH 08/10] chore: update doc text --- src/components/PlayerView/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/PlayerView/index.tsx b/src/components/PlayerView/index.tsx index 45db4387..89a7ebde 100644 --- a/src/components/PlayerView/index.tsx +++ b/src/components/PlayerView/index.tsx @@ -47,7 +47,9 @@ export interface PlayerViewProps extends BasePlayerViewProps, PlayerViewEvents { /** * Can be set to `true` to enter fullscreen mode, or `false` to exit fullscreen mode. - * By using this property to change the fullscreen state, we make sure that the embedded Player UI is also aware + * Should not be used to get the current fullscreen state. Use `onFullscreenEnter` and `onFullscreenExit` + * or the `FullscreenHandler.isFullscreenActive` property to get the current state. + * By using this property to change the fullscreen state, it is ensured that the embedded Player UI is also aware * of potential fullscreen state changes. * To use this property, a `FullscreenHandler` must be set. */ From 14271faf65c61ef661030b787a8b35fc7dc31538 Mon Sep 17 00:00:00 2001 From: Matthias Tamegger Date: Fri, 28 Jul 2023 13:09:04 +0200 Subject: [PATCH 09/10] chore: rename isFullscreen prop to isFullscreenRequested --- src/components/PlayerView/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/PlayerView/index.tsx b/src/components/PlayerView/index.tsx index 792fa65b..cd8cf0ee 100644 --- a/src/components/PlayerView/index.tsx +++ b/src/components/PlayerView/index.tsx @@ -46,14 +46,14 @@ export interface PlayerViewProps extends BasePlayerViewProps, PlayerViewEvents { customMessageHandler?: CustomMessageHandler; /** - * Can be set to `true` to enter fullscreen mode, or `false` to exit fullscreen mode. + * Can be set to `true` to request fullscreen mode, or `false` to request exit of fullscreen mode. * Should not be used to get the current fullscreen state. Use `onFullscreenEnter` and `onFullscreenExit` * or the `FullscreenHandler.isFullscreenActive` property to get the current state. - * By using this property to change the fullscreen state, it is ensured that the embedded Player UI is also aware + * Using this property to change the fullscreen state, it is ensured that the embedded Player UI is also aware * of potential fullscreen state changes. * To use this property, a `FullscreenHandler` must be set. */ - isFullscreen?: Boolean; + isFullscreenRequested?: Boolean; } /** @@ -89,7 +89,7 @@ export function PlayerView({ player, fullscreenHandler, customMessageHandler, - isFullscreen = false, + isFullscreenRequested = false, ...props }: PlayerViewProps) { // Native view reference. @@ -157,9 +157,9 @@ export function PlayerView({ useEffect(() => { const node = findNodeHandle(nativeView.current); if (node) { - dispatch('setFullscreen', node, isFullscreen); + dispatch('setFullscreen', node, isFullscreenRequested); } - }, [isFullscreen, nativeView]); + }, [isFullscreenRequested, nativeView]); return ( Date: Fri, 28 Jul 2023 13:09:25 +0200 Subject: [PATCH 10/10] chore(android): explicitly fail on invalid commands --- .../bitmovin/player/reactnative/RNPlayerViewManager.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 f32d147e..a3597eba 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt @@ -133,9 +133,9 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple * to call 'functions' on them. * @return map between names (used in js) and command ids (used in native code). */ - override fun getCommandsMap(): MutableMap = Commands.values().associate { + override fun getCommandsMap(): Map = Commands.values().associate { it.command to it.ordinal - }.toMutableMap() + } /** * Callback triggered in response to command dispatches from the js side. @@ -144,7 +144,9 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple * @param args Arguments list sent from the js side. */ override fun receiveCommand(view: RNPlayerView, commandId: String?, args: ReadableArray?) { - val command = commandId?.toInt()?.toCommand() ?: return + val command = commandId?.toInt()?.toCommand() ?: throw IllegalArgumentException( + "The received command is not supported by the Bitmovin Player View" + ) when (command) { Commands.ATTACH_PLAYER -> attachPlayer(view, args?.getString(1), args?.getMap(2)) Commands.ATTACH_FULLSCREEN_BRIDGE -> args?.getString(1)?.let { fullscreenBridgeId ->