Skip to content

Commit

Permalink
feat(pipconfig): introduce PictureInPictureConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
rolandkakonyi committed Oct 11, 2023
1 parent 8a7eeee commit 9178d96
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class RNPlayerView(val context: ReactApplicationContext) :
View.OnLayoutChangeListener,
RNPictureInPictureDelegate {

data class PictureInPictureConfig(val isEnabled: Boolean)

init {
// 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
Expand All @@ -107,6 +109,8 @@ class RNPlayerView(val context: ReactApplicationContext) :
getViewTreeObserver().addOnGlobalLayoutListener { requestLayout() }
}

var pictureInPictureConfig: PictureInPictureConfig? = null

/**
* Relays the provided set of events, emitted by the player, together with the associated name
* to the `eventOutput` callback.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.os.Looper
import android.util.Log
import android.view.ViewGroup.LayoutParams
import com.bitmovin.player.PlayerView
import com.bitmovin.player.reactnative.converter.JsonConverter
import com.bitmovin.player.reactnative.extensions.getBooleanOrNull
import com.bitmovin.player.reactnative.extensions.getModule
import com.bitmovin.player.reactnative.ui.CustomMessageHandlerModule
Expand All @@ -16,6 +17,7 @@ import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.annotations.ReactProp

private const val MODULE_NAME = "NativePlayerView"

Expand Down Expand Up @@ -43,7 +45,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
* modules in case a full-custom implementation is needed. A default implementation is provided
* out-of-the-box.
*/
var pictureInPictureHandler = RNPictureInPictureHandler(context)
private var pictureInPictureHandler = RNPictureInPictureHandler(context)

/**
* The component's native view factory. RN may call this method multiple times
Expand Down Expand Up @@ -174,6 +176,11 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
}
}

@ReactProp(name = "pictureInPictureConfig")
fun setPictureInPictureConfig(view: RNPlayerView, pictureInPictureConfig: ReadableMap?) {
view.pictureInPictureConfig = JsonConverter.toPictureInPictureConfig(pictureInPictureConfig)
}

private fun attachFullscreenBridge(view: RNPlayerView, fullscreenBridgeId: NativeId) {
Handler(Looper.getMainLooper()).post {
view.playerView?.setFullscreenHandler(
Expand Down Expand Up @@ -217,13 +224,15 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
private fun attachPlayer(view: RNPlayerView, playerId: NativeId?, playerConfig: ReadableMap?) {
Handler(Looper.getMainLooper()).post {
val player = getPlayerModule()?.getPlayer(playerId)
playerConfig
val isPictureInPictureEnabled = playerConfig
?.getMap("playbackConfig")
?.getBooleanOrNull("isPictureInPictureEnabled")
?.let {
pictureInPictureHandler.isPictureInPictureEnabled = it
view.pictureInPictureHandler = pictureInPictureHandler
}
?: view.pictureInPictureConfig?.isEnabled

isPictureInPictureEnabled?.let {
pictureInPictureHandler.isPictureInPictureEnabled = it
view.pictureInPictureHandler = pictureInPictureHandler
}
if (view.playerView != null) {
view.player = player
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import com.bitmovin.player.api.source.TimelineReferencePoint
import com.bitmovin.player.api.ui.ScalingMode
import com.bitmovin.player.api.ui.StyleConfig
import com.bitmovin.player.reactnative.BitmovinCastManagerOptions
import com.bitmovin.player.reactnative.RNPlayerView
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 @@ -1089,6 +1090,13 @@ class JsonConverter {
putInt("height", thumbnail.height)
}
}

@JvmStatic
fun toPictureInPictureConfig(json: ReadableMap?): RNPlayerView.PictureInPictureConfig? = json?.let {
RNPlayerView.PictureInPictureConfig(
isEnabled = it.getBoolean("isEnabled"),
)
}
}
}

Expand Down
10 changes: 6 additions & 4 deletions example/src/screens/BasicPictureInPicture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ function prettyPrint(header: string, obj: any) {
export default function BasicPictureInPicture() {
useTVGestures();

const pictureInPictureConfig = {
// Enable picture in picture UI option on player controls.
isEnabled: true,
};

const player = usePlayer({
playbackConfig: {
// Enable picture in picture UI option on player controls.
isPictureInPictureEnabled: true,
},
remoteControlConfig: {
isCastEnabled: false,
},
Expand Down Expand Up @@ -68,6 +69,7 @@ export default function BasicPictureInPicture() {
<PlayerView
player={player}
style={styles.player}
pictureInPictureConfig={pictureInPictureConfig}
onPictureInPictureAvailabilityChanged={onEvent}
onPictureInPictureEnter={onEvent}
onPictureInPictureEntered={onEvent}
Expand Down
17 changes: 17 additions & 0 deletions ios/RCTConvert+BitmovinPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1033,4 +1033,21 @@ extension RCTConvert {
)
}
#endif

/**
Utility method to instantiate a `PictureInPictureConfig` from a JS object.
- Parameter json: JS object
- Returns: The produced `PictureInPictureConfig` object
*/
static func pictureInPictureConfig(_ json: Any?) -> PictureInPictureConfig? {
guard let json = json as? [String: Any?] else {
return nil
}

let pictureInPictureConfig = PictureInPictureConfig()
if let isEnabled = json["isEnabled"] as? Bool {
pictureInPictureConfig.isEnabled = isEnabled
}
return pictureInPictureConfig
}
}
1 change: 1 addition & 0 deletions ios/RNPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class RNPlayerView: UIView {
@objc var onCastStopped: RCTBubblingEventBlock?
@objc var onCastTimeUpdated: RCTBubblingEventBlock?
@objc var onCastWaitingForDevice: RCTBubblingEventBlock?
@objc var pictureInPictureConfig: [String: Any]?

/// The `PlayerView` subview.
var playerView: PlayerView? {
Expand Down
1 change: 1 addition & 0 deletions ios/RNPlayerViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ @interface RCT_EXTERN_REMAP_MODULE(NativePlayerView, RNPlayerViewManager, RCTVie
RCT_EXPORT_VIEW_PROPERTY(onCastStopped, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onCastTimeUpdated, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onCastWaitingForDevice, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(pictureInPictureConfig, NSDictionary)

RCT_EXTERN_METHOD(attachPlayer:(nonnull NSNumber *)viewId playerId:(NSString *)playerId playerConfig:(nullable NSDictionary *)playerConfig)
RCT_EXTERN_METHOD(attachFullscreenBridge:(nonnull NSNumber *)viewId fullscreenBridgeId:(NSString *)fullscreenBridgeId)
Expand Down
18 changes: 14 additions & 4 deletions ios/RNPlayerViewManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ class RNPlayerViewManager: RCTViewManager {
- Parameter playerId: `Player` instance id inside `PlayerModule`'s registry.
*/
@objc func attachPlayer(_ viewId: NSNumber, playerId: NativeId, playerConfig: NSDictionary?) {
bridge.uiManager.addUIBlock { [weak self] _, views in
bridge.uiManager.addUIBlock {
[weak self] _,
views in
guard
let self,
let view = views?[viewId] as? RNPlayerView,
Expand All @@ -36,15 +38,23 @@ class RNPlayerViewManager: RCTViewManager {
player.config.styleConfig.userInterfaceType == .bitmovin {
let bitmovinUserInterfaceConfig = player.config.styleConfig.userInterfaceConfig as? BitmovinUserInterfaceConfig ?? BitmovinUserInterfaceConfig()
player.config.styleConfig.userInterfaceConfig = bitmovinUserInterfaceConfig

bitmovinUserInterfaceConfig.customMessageHandler = customMessageHandlerBridge.customMessageHandler
}
#endif

if let playerView = view.playerView {
playerView.player = player
} else {
view.playerView = PlayerView(player: player, frame: view.bounds)
let playerViewConfig = PlayerViewConfig()
if let pictureInPictureConfig = RCTConvert.pictureInPictureConfig(view.pictureInPictureConfig) {
playerViewConfig.pictureInPictureConfig = pictureInPictureConfig
}
view.playerView = PlayerView(
player: player,
frame: view.bounds,
playerViewConfig: playerViewConfig
)
}
player.add(listener: view)
view.playerView?.add(listener: view)
Expand Down
19 changes: 19 additions & 0 deletions src/components/PlayerView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,30 @@ import { FullscreenHandler, CustomMessageHandler } from '../../ui';
import { FullscreenHandlerBridge } from '../../ui/fullscreenhandlerbridge';
import { CustomMessageHandlerBridge } from '../../ui/custommessagehandlerbridge';

/**
* Provides options to configure Picture in Picture playback.
*/
export interface PictureInPictureConfig {
/**
* Whether Picture in Picture feature is enabled or not.
*
* Default is `false`.
*/
isEnabled?: boolean;
}

/**
* Base `PlayerView` component props. Used to stablish common
* props between `NativePlayerView` and `PlayerView`.
* @see NativePlayerView
*/
export interface BasePlayerViewProps {
style?: ViewStyle;

/**
* Provides options to configure Picture in Picture playback.
*/
pictureInPictureConfig?: PictureInPictureConfig;
}

/**
Expand Down Expand Up @@ -90,6 +107,7 @@ export function PlayerView({
fullscreenHandler,
customMessageHandler,
isFullscreenRequested = false,
pictureInPictureConfig,
...props
}: PlayerViewProps) {
// Workaround React Native UIManager commands not sent until UI refresh
Expand Down Expand Up @@ -174,6 +192,7 @@ export function PlayerView({
style={nativeViewStyle}
fullscreenBridge={fullscreenBridge.current}
customMessageHandlerBridge={customMessageHandlerBridge.current}
pictureInPictureConfig={pictureInPictureConfig}
onAdBreakFinished={proxy(props.onAdBreakFinished)}
onAdBreakStarted={proxy(props.onAdBreakStarted)}
onAdClicked={proxy(props.onAdClicked)}
Expand Down
1 change: 1 addition & 0 deletions src/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export interface PlaybackConfig {
* },
* });
* ```
* @deprecated Use {@link PictureInPIctureConfig} instead.
*/
isPictureInPictureEnabled?: boolean;
}
Expand Down

0 comments on commit 9178d96

Please sign in to comment.