Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fullscreen prop for the PlayerView #169

Merged
merged 11 commits into from
Jul 28, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

/**
Expand Down Expand Up @@ -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<String, Int> = 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<String, Int> = Commands.values().associate {
it.command to it.ordinal
}.toMutableMap()
matamegger marked this conversation as resolved.
Show resolved Hide resolved

/**
* Callback triggered in response to command dispatches from the js side.
Expand All @@ -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
matamegger marked this conversation as resolved.
Show resolved Hide resolved
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 -> {}
}
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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)
1 change: 1 addition & 0 deletions ios/RNPlayerViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 22 additions & 0 deletions ios/RNPlayerViewManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
guard playerView.isFullscreen != isFullscreen else {
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
Expand Down
25 changes: 25 additions & 0 deletions src/components/PlayerView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,25 @@ 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.
* Should not be used to get the current fullscreen state. Use `onFullscreenEnter` and `onFullscreenExit`
* or the `FullscreenHandler.isFullscreenActive` property to get the current state.
matamegger marked this conversation as resolved.
Show resolved Hide resolved
* 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.
rolandkakonyi marked this conversation as resolved.
Show resolved Hide resolved
*/
isFullscreen?: Boolean;
}

/**
Expand Down Expand Up @@ -73,6 +89,7 @@ export function PlayerView({
player,
fullscreenHandler,
customMessageHandler,
isFullscreen = false,
...props
}: PlayerViewProps) {
// Native view reference.
Expand Down Expand Up @@ -128,13 +145,21 @@ export function PlayerView({
);
}
}

return () => {
fullscreenBridge.current?.destroy();
fullscreenBridge.current = undefined;
customMessageHandlerBridge.current?.destroy();
customMessageHandlerBridge.current = undefined;
};
}, [player]);

useEffect(() => {
const node = findNodeHandle(nativeView.current);
if (node) {
dispatch('setFullscreen', node, isFullscreen);
}
}, [isFullscreen, nativeView]);
return (
<NativePlayerView
ref={nativeView}
Expand Down