Skip to content

Commit

Permalink
Merge pull request #302 from bitmovin/move-pip-config-to-playerviewco…
Browse files Browse the repository at this point in the history
…nfig

Move `PictureInPictureConfig` to `PlayerViewConfig`
  • Loading branch information
rolandkakonyi authored Oct 20, 2023
2 parents cc32c2e + e22eb83 commit 4a00ec7
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 52 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")),
)
}
}

Expand Down
26 changes: 18 additions & 8 deletions example/src/screens/BasicPictureInPicture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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({
Expand Down Expand Up @@ -115,12 +117,20 @@ export default function BasicPictureInPicture({
);

return (
<SafeAreaView edges={['bottom', 'left', 'right']} style={styles.container}>
<SafeAreaView
edges={['bottom', 'left', 'right']}
style={
// On Android, we need to remove the padding from the container when in PiP mode.
Platform.OS === 'android' && isInPictureInPicture
? [styles.container, { padding: 0 }]
: styles.container
}
>
<PlayerView
player={player}
style={styles.player}
isPictureInPictureRequested={isPictureInPictureRequested}
pictureInPictureConfig={pictureInPictureConfig}
config={config}
onPictureInPictureAvailabilityChanged={onEvent}
onPictureInPictureEnter={onPictureInPictureEnterEvent}
onPictureInPictureEntered={onEvent}
Expand Down
47 changes: 38 additions & 9 deletions ios/RCTConvert+BitmovinPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1122,20 +1122,33 @@ extension RCTConvert {
}

/**
Utility method to instantiate a `UiConfig` from a JS object.
Utility method to instantiate a `RNPlayerViewConfig` from a JS object.
- Parameter json: JS object
- Returns: The produced `UiConfig` object
*/
static func playerViewConfig(_ json: Any?) -> 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
)
}
}
Expand All @@ -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
}
1 change: 0 additions & 1 deletion ios/RNPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion ios/RNPlayerViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 4 additions & 7 deletions ios/RNPlayerViewManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,17 @@ public class RNPlayerViewManager: RCTViewManager {
else {
return
}
let playerViewConfig = RCTConvert.rnPlayerViewConfig(view.config)
#if os(iOS)
if player.config.styleConfig.userInterfaceType == .bitmovin {
let bitmovinUserInterfaceConfig = player
.config
.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,
Expand All @@ -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
}
Expand Down
2 changes: 0 additions & 2 deletions src/components/PlayerView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)}
Expand Down
9 changes: 8 additions & 1 deletion src/components/PlayerView/playerViewConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PictureInPictureConfig } from './pictureInPictureConfig';

/**
* Configures the visual presentation and behaviour of the `PlayerView`.
*/
Expand All @@ -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;
}

/**
Expand Down
6 changes: 0 additions & 6 deletions src/components/PlayerView/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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.
Expand Down

0 comments on commit 4a00ec7

Please sign in to comment.