diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c49b00a..4b224595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,14 @@ ### Added - API Reference now can be found [here](https://cdn.bitmovin.com/player/reactnative/0/docs/index.html) -- `PictureInPictureConfig` to `PlayerView` to allow configuring the Picture in Picture behavior -- `PictureInPictureConfig.shouldEnterOnBackground` to start PiP automatically when the app transitions to background +- `PlayerViewConfig` with a `UiConfig` to the `PlayerView` to enable configuration of the visual presentation and behaviour +- `PictureInPictureConfig` to `PlayerViewConfig` to allow configuring the Picture in Picture behavior +- `PictureInPictureConfig.isEnabled` to enable configing if Picture in Picture is enabled +- `PictureInPictureConfig.shouldEnterOnBackground` to start Picture in Picture automatically when the app transitions to background - `onPictureInPictureAvailabilityChanged` event is now emitted on iOS and tvOS in addition to Android - `BufferConfig` to configure player buffer depth - `PlayerView.isPictureInPictureRequested` to programatically create a Picture in Picture request - `PlayerView.scalingMode` to allow changing the video scaling mode -- `PlayerViewConfig` with a `UiConfig` to the `PlayerView` to enable configuration of the visual presentation and behaviour ### Changed 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 42f2e77e..33e5d1a3 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -145,15 +145,10 @@ class RNPlayerView(val context: ReactApplicationContext) : */ var pictureInPictureHandler: RNPictureInPictureHandler? = null - /** - * Configuration for picture in picture behaviors. - */ - var pictureInPictureConfig: RNPictureInPictureHandler.PictureInPictureConfig? = null - /** * Configures the visual presentation and behaviour of the [playerView]. */ - var config: PlayerViewConfig? = null + var config: RNPlayerViewConfigWrapper? = null /** * Whether this view should pause video playback when activity's onPause is called. @@ -311,3 +306,12 @@ class RNPlayerView(val context: ReactApplicationContext) : .receiveEvent(id, name, payload) } } + +/** + * Representation of the React Native API `PlayerViewConfig` object. + * This is necessary as not all of its values can be directly mapped to the native `PlayerViewConfig`. + */ +data class RNPlayerViewConfigWrapper( + val playerViewConfig: PlayerViewConfig?, + val pictureInPictureConfig: RNPictureInPictureHandler.PictureInPictureConfig?, +) 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 5e8072b4..05f17515 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt @@ -183,14 +183,9 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple } } - @ReactProp(name = "pictureInPictureConfig") - fun setPictureInPictureConfig(view: RNPlayerView, pictureInPictureConfig: ReadableMap?) { - view.pictureInPictureConfig = JsonConverter.toPictureInPictureConfig(pictureInPictureConfig) - } - @ReactProp(name = "config") fun setConfig(view: RNPlayerView, config: ReadableMap?) { - view.config = if (config != null) JsonConverter.toPlayerViewConfig(config) else null + view.config = if (config != null) JsonConverter.toRNPlayerViewConfigWrapper(config) else null } private fun attachFullscreenBridge(view: RNPlayerView, fullscreenBridgeId: NativeId) { @@ -253,7 +248,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple Handler(Looper.getMainLooper()).post { val player = getPlayerModule()?.getPlayer(playerId) val playbackConfig = playerConfig?.getMap("playbackConfig") - val isPictureInPictureEnabled = view.pictureInPictureConfig?.isEnabled == true || + val isPictureInPictureEnabled = view.config?.pictureInPictureConfig?.isEnabled == true || playbackConfig?.getBooleanOrNull("isPictureInPictureEnabled") == true val pictureInPictureHandler = view.pictureInPictureHandler ?: RNPictureInPictureHandler(context) view.pictureInPictureHandler = pictureInPictureHandler @@ -270,7 +265,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple val playerView = PlayerView( currentActivity, player, - view.config ?: PlayerViewConfig(), + view.config?.playerViewConfig ?: PlayerViewConfig(), ) playerView.layoutParams = LayoutParams( LayoutParams.MATCH_PARENT, 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 f336ef41..f3b9b110 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 @@ -44,6 +44,7 @@ import com.bitmovin.player.api.ui.ScalingMode import com.bitmovin.player.api.ui.StyleConfig import com.bitmovin.player.api.ui.UiConfig import com.bitmovin.player.reactnative.BitmovinCastManagerOptions +import com.bitmovin.player.reactnative.RNPlayerViewConfigWrapper import com.bitmovin.player.reactnative.extensions.getBooleanOrNull import com.bitmovin.player.reactnative.extensions.getName import com.bitmovin.player.reactnative.extensions.getOrDefault @@ -1160,6 +1161,14 @@ class JsonConverter { ?: true, ), ) + + /** + * Converts the [json] to a `RNPlayerViewConfig` object. + */ + fun toRNPlayerViewConfigWrapper(json: ReadableMap) = RNPlayerViewConfigWrapper( + playerViewConfig = toPlayerViewConfig(json), + pictureInPictureConfig = toPictureInPictureConfig(json.getMap("pictureInPictureConfig")), + ) } } diff --git a/example/src/screens/BasicPictureInPicture.tsx b/example/src/screens/BasicPictureInPicture.tsx index 5e77dd02..2b2b6975 100644 --- a/example/src/screens/BasicPictureInPicture.tsx +++ b/example/src/screens/BasicPictureInPicture.tsx @@ -7,9 +7,9 @@ import { PlayerView, SourceType, AudioSession, - PictureInPictureConfig, PictureInPictureEnterEvent, PictureInPictureExitEvent, + PlayerViewConfig, } from 'bitmovin-player-react-native'; import { useTVGestures } from '../hooks'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -34,11 +34,13 @@ export default function BasicPictureInPicture({ useState(false); const [isInPictureInPicture, setIsInPictureInPicture] = useState(false); - const pictureInPictureConfig: PictureInPictureConfig = { - // Enable picture in picture UI option on player controls. - isEnabled: true, - // Enable entering picture in picture mode when transitioning the application to the background - shouldEnterOnBackground: true, + const config: PlayerViewConfig = { + pictureInPictureConfig: { + // Enable picture in picture UI option on player controls. + isEnabled: true, + // Enable entering picture in picture mode when transitioning the application to the background + shouldEnterOnBackground: true, + }, }; const player = usePlayer({ @@ -115,12 +117,20 @@ export default function BasicPictureInPicture({ ); return ( - + RNPlayerViewConfig? { - guard let json = json as? [String: Any?], - let uiConfigJson = json["uiConfig"] as? [String: Any?] else { + static func rnPlayerViewConfig(_ json: Any?) -> RNPlayerViewConfig? { + guard let json = json as? [String: Any?] else { return nil } return RNPlayerViewConfig( - uiConfig: UiConfig( - playbackSpeedSelectionEnabled: uiConfigJson["playbackSpeedSelectionEnabled"] as? Bool ?? true - ) + uiConfig: rnUiConfig(json["uiConfig"]), + pictureInPictureConfig: pictureInPictureConfig(json["pictureInPictureConfig"]) + ) + } + + /** + Utility method to instantiate a `RNUiConfig` from a JS object. + - Parameter json: JS object + - Returns: The produced `RNUiConfig` object + */ + static func rnUiConfig(_ json: Any?) -> RNUiConfig? { + guard let json = json as? [String: Any?] else { + return nil + } + + return RNUiConfig( + playbackSpeedSelectionEnabled: json["playbackSpeedSelectionEnabled"] as? Bool ?? true ) } } @@ -1146,12 +1159,28 @@ internal struct RNPlayerViewConfig { /** * The react native specific ui configuration. */ - let uiConfig: UiConfig + let uiConfig: RNUiConfig? + + /** + * Picture in picture config + */ + let pictureInPictureConfig: PictureInPictureConfig? + + /** + * PlayerView config considering all properties + */ + var playerViewConfig: PlayerViewConfig { + let config = PlayerViewConfig() + if let pictureInPictureConfig { + config.pictureInPictureConfig = pictureInPictureConfig + } + return config + } } /** * React native specific UiConfig. */ -internal struct UiConfig { +internal struct RNUiConfig { let playbackSpeedSelectionEnabled: Bool } diff --git a/ios/RNPlayerView.swift b/ios/RNPlayerView.swift index b196614d..b4f0e1b2 100644 --- a/ios/RNPlayerView.swift +++ b/ios/RNPlayerView.swift @@ -63,7 +63,6 @@ public class RNPlayerView: UIView { @objc var onCastTimeUpdated: RCTBubblingEventBlock? @objc var onCastWaitingForDevice: RCTBubblingEventBlock? @objc var onPictureInPictureAvailabilityChanged: RCTBubblingEventBlock? - @objc var pictureInPictureConfig: [String: Any]? @objc var config: [String: Any]? /// The `PlayerView` subview. diff --git a/ios/RNPlayerViewManager.m b/ios/RNPlayerViewManager.m index 64e542e8..b9c94d5e 100644 --- a/ios/RNPlayerViewManager.m +++ b/ios/RNPlayerViewManager.m @@ -62,7 +62,6 @@ @interface RCT_EXTERN_REMAP_MODULE(NativePlayerView, RNPlayerViewManager, RCTVie RCT_EXPORT_VIEW_PROPERTY(onCastTimeUpdated, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onCastWaitingForDevice, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureAvailabilityChanged, RCTBubblingEventBlock) -RCT_EXPORT_VIEW_PROPERTY(pictureInPictureConfig, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary) RCT_EXTERN_METHOD(attachPlayer:(nonnull NSNumber *)viewId playerId:(NSString *)playerId playerConfig:(nullable NSDictionary *)playerConfig) diff --git a/ios/RNPlayerViewManager.swift b/ios/RNPlayerViewManager.swift index b5a7f411..89b72ca0 100644 --- a/ios/RNPlayerViewManager.swift +++ b/ios/RNPlayerViewManager.swift @@ -31,6 +31,7 @@ public class RNPlayerViewManager: RCTViewManager { else { return } + let playerViewConfig = RCTConvert.rnPlayerViewConfig(view.config) #if os(iOS) if player.config.styleConfig.userInterfaceType == .bitmovin { let bitmovinUserInterfaceConfig = player @@ -38,9 +39,9 @@ public class RNPlayerViewManager: RCTViewManager { .styleConfig .userInterfaceConfig as? BitmovinUserInterfaceConfig ?? BitmovinUserInterfaceConfig() player.config.styleConfig.userInterfaceConfig = bitmovinUserInterfaceConfig - if let config = RCTConvert.playerViewConfig(view.config) { + if let uiConfig = playerViewConfig?.uiConfig { bitmovinUserInterfaceConfig - .playbackSpeedSelectionEnabled = config.uiConfig.playbackSpeedSelectionEnabled + .playbackSpeedSelectionEnabled = uiConfig.playbackSpeedSelectionEnabled } if let customMessageHandlerBridgeId = self.customMessageHandlerBridgeId, @@ -56,14 +57,10 @@ public class RNPlayerViewManager: RCTViewManager { playerView.player = player previousPictureInPictureAvailableValue = playerView.isPictureInPictureAvailable } else { - let playerViewConfig = PlayerViewConfig() - if let pictureInPictureConfig = RCTConvert.pictureInPictureConfig(view.pictureInPictureConfig) { - playerViewConfig.pictureInPictureConfig = pictureInPictureConfig - } view.playerView = PlayerView( player: player, frame: view.bounds, - playerViewConfig: playerViewConfig + playerViewConfig: playerViewConfig?.playerViewConfig ?? PlayerViewConfig() ) previousPictureInPictureAvailableValue = false } diff --git a/src/components/PlayerView/index.tsx b/src/components/PlayerView/index.tsx index 0f12802b..718a4479 100644 --- a/src/components/PlayerView/index.tsx +++ b/src/components/PlayerView/index.tsx @@ -51,7 +51,6 @@ export function PlayerView({ isFullscreenRequested = false, scalingMode, isPictureInPictureRequested = false, - pictureInPictureConfig, ...props }: PlayerViewProps) { // Workaround React Native UIManager commands not sent until UI refresh @@ -151,7 +150,6 @@ export function PlayerView({ style={nativeViewStyle} fullscreenBridge={fullscreenBridge.current} customMessageHandlerBridge={customMessageHandlerBridge.current} - pictureInPictureConfig={pictureInPictureConfig} config={config} onAdBreakFinished={proxy(props.onAdBreakFinished)} onAdBreakStarted={proxy(props.onAdBreakStarted)} diff --git a/src/components/PlayerView/playerViewConfig.ts b/src/components/PlayerView/playerViewConfig.ts index 15f9584b..18502bc1 100644 --- a/src/components/PlayerView/playerViewConfig.ts +++ b/src/components/PlayerView/playerViewConfig.ts @@ -1,3 +1,5 @@ +import { PictureInPictureConfig } from './pictureInPictureConfig'; + /** * Configures the visual presentation and behaviour of the `PlayerView`. */ @@ -11,7 +13,12 @@ export interface PlayerViewConfig { * Limitations: * Configuring the `uiConfig` only has an effect if the {@link StyleConfig.userInterfaceType} is set to {@link UserInterfaceType.Bitmovin}. */ - uiConfig: UiConfig; + uiConfig?: UiConfig; + + /** + * Provides options to configure Picture in Picture playback. + */ + pictureInPictureConfig?: PictureInPictureConfig; } /** diff --git a/src/components/PlayerView/properties.ts b/src/components/PlayerView/properties.ts index e391f8c1..4a199155 100644 --- a/src/components/PlayerView/properties.ts +++ b/src/components/PlayerView/properties.ts @@ -2,7 +2,6 @@ import { PlayerViewEvents } from './events'; import { Player } from '../../player'; import { FullscreenHandler, CustomMessageHandler } from '../../ui'; import { ScalingMode } from '../../styleConfig'; -import { PictureInPictureConfig } from './pictureInPictureConfig'; import { ViewStyle } from 'react-native'; import { PlayerViewConfig } from './playerViewConfig'; @@ -26,11 +25,6 @@ export interface BasePlayerViewProps { */ style?: ViewStyle; - /** - * Provides options to configure Picture in Picture playback. - */ - pictureInPictureConfig?: PictureInPictureConfig; - /** * Configures the visual presentation and behaviour of the {@link PlayerView}. * The value must not be altered after setting it initially.