bitmovin-player-react-native 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/RNBitmovinPlayer.podspec +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +32 -64
- package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +5 -3
- package/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +5 -1
- package/android/src/main/java/com/bitmovin/player/reactnative/ui/RNPictureInPictureHandler.kt +39 -180
- package/ios/RCTConvert+BitmovinPlayer.swift +11 -1
- package/ios/RNPlayerViewManager.swift +46 -18
- package/lib/index.d.mts +14 -0
- package/lib/index.d.ts +14 -0
- package/package.json +1 -8
- package/src/components/PlayerView/playerViewConfig.ts +8 -0
- package/src/tweaksConfig.ts +7 -0
package/RNBitmovinPlayer.podspec
CHANGED
|
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
|
|
|
19
19
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
20
20
|
|
|
21
21
|
s.dependency "React-Core"
|
|
22
|
-
s.dependency "BitmovinPlayer", "3.
|
|
22
|
+
s.dependency "BitmovinPlayer", "3.59.0"
|
|
23
23
|
s.ios.dependency "GoogleAds-IMA-iOS-SDK", "3.18.4"
|
|
24
24
|
s.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "4.8.2"
|
|
25
25
|
end
|
package/android/build.gradle
CHANGED
|
@@ -103,5 +103,5 @@ dependencies {
|
|
|
103
103
|
// Bitmovin
|
|
104
104
|
implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.31.0'
|
|
105
105
|
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
|
|
106
|
-
implementation 'com.bitmovin.player:player:3.
|
|
106
|
+
implementation 'com.bitmovin.player:player:3.65.0+jason'
|
|
107
107
|
}
|
|
@@ -2,8 +2,7 @@ package com.bitmovin.player.reactnative
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.res.Configuration
|
|
5
|
-
import android.
|
|
6
|
-
import android.view.View
|
|
5
|
+
import android.os.Build
|
|
7
6
|
import android.view.ViewGroup
|
|
8
7
|
import android.widget.FrameLayout
|
|
9
8
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
@@ -17,8 +16,6 @@ import com.bitmovin.player.api.event.SourceEvent
|
|
|
17
16
|
import com.bitmovin.player.api.ui.PlayerViewConfig
|
|
18
17
|
import com.bitmovin.player.api.ui.StyleConfig
|
|
19
18
|
import com.bitmovin.player.reactnative.converter.toJson
|
|
20
|
-
import com.bitmovin.player.reactnative.ui.RNPictureInPictureDelegate
|
|
21
|
-
import com.bitmovin.player.reactnative.ui.RNPictureInPictureHandler
|
|
22
19
|
import com.facebook.react.ReactActivity
|
|
23
20
|
import com.facebook.react.bridge.*
|
|
24
21
|
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
@@ -101,7 +98,7 @@ private val EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING_UI = mapOf<KClass<out Event
|
|
|
101
98
|
@SuppressLint("ViewConstructor")
|
|
102
99
|
class RNPlayerView(
|
|
103
100
|
private val context: ReactApplicationContext,
|
|
104
|
-
) : FrameLayout(context)
|
|
101
|
+
) : FrameLayout(context) {
|
|
105
102
|
private val activityLifecycle = (context.currentActivity as? ReactActivity)?.lifecycle
|
|
106
103
|
?: error("Trying to create an instance of ${this::class.simpleName} while not attached to a ReactActivity")
|
|
107
104
|
|
|
@@ -153,7 +150,6 @@ class RNPlayerView(
|
|
|
153
150
|
|
|
154
151
|
private var _playerView: PlayerView? = null
|
|
155
152
|
set(value) {
|
|
156
|
-
field?.removeOnLayoutChangeListener(this)
|
|
157
153
|
field = value
|
|
158
154
|
viewEventRelay.eventEmitter = field
|
|
159
155
|
playerEventRelay.eventEmitter = field?.player
|
|
@@ -176,11 +172,6 @@ class RNPlayerView(
|
|
|
176
172
|
playerEventRelay.eventEmitter = value
|
|
177
173
|
}
|
|
178
174
|
|
|
179
|
-
/**
|
|
180
|
-
* Object that handles PiP mode changes in React Native.
|
|
181
|
-
*/
|
|
182
|
-
var pictureInPictureHandler: RNPictureInPictureHandler? = null
|
|
183
|
-
|
|
184
175
|
/**
|
|
185
176
|
* Configures the visual presentation and behaviour of the [playerView].
|
|
186
177
|
*/
|
|
@@ -214,11 +205,6 @@ class RNPlayerView(
|
|
|
214
205
|
(playerView.parent as ViewGroup?)?.removeView(playerView)
|
|
215
206
|
addView(playerView, 0)
|
|
216
207
|
}
|
|
217
|
-
pictureInPictureHandler?.let {
|
|
218
|
-
it.setDelegate(this)
|
|
219
|
-
playerView.setPictureInPictureHandler(it)
|
|
220
|
-
playerView.addOnLayoutChangeListener(this)
|
|
221
|
-
}
|
|
222
208
|
}
|
|
223
209
|
|
|
224
210
|
/**
|
|
@@ -232,61 +218,38 @@ class RNPlayerView(
|
|
|
232
218
|
addView(subtitleView)
|
|
233
219
|
}
|
|
234
220
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Called when the player has just entered PiP mode.
|
|
245
|
-
*/
|
|
246
|
-
override fun onEnterPictureInPicture() {
|
|
247
|
-
// Nothing to do
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Called when the player has just exited PiP mode.
|
|
252
|
-
*/
|
|
253
|
-
override fun onExitPictureInPicture() {
|
|
254
|
-
// Explicitly call `exitPictureInPicture()` on PlayerView when exiting PiP state, otherwise
|
|
255
|
-
// the `PictureInPictureExit` event won't get dispatched.
|
|
256
|
-
playerView?.exitPictureInPicture()
|
|
221
|
+
private fun isInPictureInPictureMode(): Boolean {
|
|
222
|
+
val activity = context.currentActivity ?: return false
|
|
223
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
224
|
+
activity.isInPictureInPictureMode
|
|
225
|
+
} else {
|
|
226
|
+
false
|
|
227
|
+
}
|
|
257
228
|
}
|
|
258
229
|
|
|
259
|
-
|
|
260
|
-
* Called when the player's PiP mode changes with a new configuration object.
|
|
261
|
-
*/
|
|
262
|
-
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
|
|
263
|
-
playerView?.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
|
264
|
-
}
|
|
230
|
+
private var isCurrentActivityInPictureInPictureMode: Boolean = isInPictureInPictureMode()
|
|
265
231
|
|
|
266
232
|
/**
|
|
267
|
-
* Called whenever
|
|
233
|
+
* Called whenever this view's activity configuration changes.
|
|
268
234
|
*/
|
|
269
|
-
override fun
|
|
270
|
-
|
|
235
|
+
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
236
|
+
super.onConfigurationChanged(newConfig)
|
|
237
|
+
if (isCurrentActivityInPictureInPictureMode != isInPictureInPictureMode()) {
|
|
238
|
+
isCurrentActivityInPictureInPictureMode = isInPictureInPictureMode()
|
|
239
|
+
onPictureInPictureModeChanged(isCurrentActivityInPictureInPictureMode, newConfig)
|
|
240
|
+
}
|
|
271
241
|
}
|
|
272
242
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
override fun onLayoutChange(
|
|
277
|
-
view: View?,
|
|
278
|
-
left: Int,
|
|
279
|
-
top: Int,
|
|
280
|
-
right: Int,
|
|
281
|
-
bottom: Int,
|
|
282
|
-
oldLeft: Int,
|
|
283
|
-
oldTop: Int,
|
|
284
|
-
oldRight: Int,
|
|
285
|
-
oldBottom: Int,
|
|
243
|
+
private fun onPictureInPictureModeChanged(
|
|
244
|
+
isInPictureInPictureMode: Boolean,
|
|
245
|
+
newConfig: Configuration,
|
|
286
246
|
) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
247
|
+
val playerView = playerView ?: return
|
|
248
|
+
playerView.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
|
249
|
+
if (isInPictureInPictureMode) {
|
|
250
|
+
playerView.enterPictureInPicture()
|
|
251
|
+
} else {
|
|
252
|
+
playerView.exitPictureInPicture()
|
|
290
253
|
}
|
|
291
254
|
}
|
|
292
255
|
|
|
@@ -341,7 +304,7 @@ class RNPlayerView(
|
|
|
341
304
|
*/
|
|
342
305
|
data class RNPlayerViewConfigWrapper(
|
|
343
306
|
val playerViewConfig: PlayerViewConfig?,
|
|
344
|
-
val pictureInPictureConfig:
|
|
307
|
+
val pictureInPictureConfig: PictureInPictureConfig?,
|
|
345
308
|
)
|
|
346
309
|
|
|
347
310
|
data class RNStyleConfigWrapper(
|
|
@@ -352,3 +315,8 @@ data class RNStyleConfigWrapper(
|
|
|
352
315
|
enum class UserInterfaceType {
|
|
353
316
|
Bitmovin, Subtitle
|
|
354
317
|
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Configuration type for picture in picture behaviors.
|
|
321
|
+
*/
|
|
322
|
+
data class PictureInPictureConfig(val isEnabled: Boolean)
|
|
@@ -164,6 +164,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
|
|
|
164
164
|
val command = commandId?.toInt()?.toCommand() ?: throw IllegalArgumentException(
|
|
165
165
|
"The received command is not supported by the Bitmovin Player View",
|
|
166
166
|
)
|
|
167
|
+
|
|
167
168
|
fun <T> T?.require(): T = this ?: throw InvalidParameterException("Missing parameter")
|
|
168
169
|
when (command) {
|
|
169
170
|
Commands.ATTACH_PLAYER -> attachPlayer(view, args?.getString(1).require(), args?.getMap(2))
|
|
@@ -172,6 +173,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
|
|
|
172
173
|
view,
|
|
173
174
|
args?.getString(1).require(),
|
|
174
175
|
)
|
|
176
|
+
|
|
175
177
|
Commands.SET_FULLSCREEN -> setFullscreen(view, args?.getBoolean(1).require())
|
|
176
178
|
Commands.SET_SCALING_MODE -> setScalingMode(view, args?.getString(1).require())
|
|
177
179
|
Commands.SET_PICTURE_IN_PICTURE -> setPictureInPicture(view, args?.getBoolean(1).require())
|
|
@@ -246,9 +248,6 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
|
|
|
246
248
|
val playbackConfig = playerConfig?.getMap("playbackConfig")
|
|
247
249
|
val isPictureInPictureEnabled = view.config?.pictureInPictureConfig?.isEnabled == true ||
|
|
248
250
|
playbackConfig?.getBooleanOrNull("isPictureInPictureEnabled") == true
|
|
249
|
-
val pictureInPictureHandler = view.pictureInPictureHandler ?: RNPictureInPictureHandler(context)
|
|
250
|
-
view.pictureInPictureHandler = pictureInPictureHandler
|
|
251
|
-
view.pictureInPictureHandler?.isPictureInPictureEnabled = isPictureInPictureEnabled
|
|
252
251
|
|
|
253
252
|
val rnStyleConfigWrapper = playerConfig?.toRNStyleConfigWrapperFromPlayerConfig()
|
|
254
253
|
val configuredPlayerViewConfig = view.config?.playerViewConfig ?: PlayerViewConfig()
|
|
@@ -272,6 +271,9 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple
|
|
|
272
271
|
LayoutParams.MATCH_PARENT,
|
|
273
272
|
LayoutParams.MATCH_PARENT,
|
|
274
273
|
)
|
|
274
|
+
if (isPictureInPictureEnabled) {
|
|
275
|
+
playerView.setPictureInPictureHandler(RNPictureInPictureHandler(currentActivity, player))
|
|
276
|
+
}
|
|
275
277
|
view.setPlayerView(playerView)
|
|
276
278
|
attachCustomMessageHandlerBridge(view)
|
|
277
279
|
}
|
|
@@ -47,6 +47,7 @@ import com.bitmovin.player.api.ui.ScalingMode
|
|
|
47
47
|
import com.bitmovin.player.api.ui.StyleConfig
|
|
48
48
|
import com.bitmovin.player.api.ui.UiConfig
|
|
49
49
|
import com.bitmovin.player.reactnative.BitmovinCastManagerOptions
|
|
50
|
+
import com.bitmovin.player.reactnative.PictureInPictureConfig
|
|
50
51
|
import com.bitmovin.player.reactnative.RNBufferLevels
|
|
51
52
|
import com.bitmovin.player.reactnative.RNPlayerViewConfigWrapper
|
|
52
53
|
import com.bitmovin.player.reactnative.RNStyleConfigWrapper
|
|
@@ -70,7 +71,6 @@ import com.bitmovin.player.reactnative.extensions.withInt
|
|
|
70
71
|
import com.bitmovin.player.reactnative.extensions.withMap
|
|
71
72
|
import com.bitmovin.player.reactnative.extensions.withString
|
|
72
73
|
import com.bitmovin.player.reactnative.extensions.withStringArray
|
|
73
|
-
import com.bitmovin.player.reactnative.ui.RNPictureInPictureHandler.PictureInPictureConfig
|
|
74
74
|
import com.facebook.react.bridge.*
|
|
75
75
|
import java.util.UUID
|
|
76
76
|
|
|
@@ -181,6 +181,7 @@ fun ReadableMap.toTweaksConfig(): TweaksConfig = TweaksConfig().apply {
|
|
|
181
181
|
withBoolean("useDrmSessionForClearPeriods") { useDrmSessionForClearPeriods = it }
|
|
182
182
|
withBoolean("useDrmSessionForClearSources") { useDrmSessionForClearSources = it }
|
|
183
183
|
withBoolean("useFiletypeExtractorFallbackForHls") { useFiletypeExtractorFallbackForHls = it }
|
|
184
|
+
withBoolean("preferSoftwareDecodingForAds") { preferSoftwareDecodingForAds = it }
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
/**
|
|
@@ -216,6 +217,7 @@ fun ReadableMap.toAdSource(): AdSource? {
|
|
|
216
217
|
* Converts any JS string into an `AdSourceType` enum value.
|
|
217
218
|
*/
|
|
218
219
|
private fun String.toAdSourceType(): AdSourceType? = when (this) {
|
|
220
|
+
"bitmovin" -> AdSourceType.Bitmovin
|
|
219
221
|
"ima" -> AdSourceType.Ima
|
|
220
222
|
"progressive" -> AdSourceType.Progressive
|
|
221
223
|
"unknown" -> AdSourceType.Unknown
|
|
@@ -625,6 +627,7 @@ fun AdSource.toJson(): WritableMap = Arguments.createMap().apply {
|
|
|
625
627
|
* Converts any `AdSourceType` value into its json representation.
|
|
626
628
|
*/
|
|
627
629
|
fun AdSourceType.toJson(): String = when (this) {
|
|
630
|
+
AdSourceType.Bitmovin -> "bitmovin"
|
|
628
631
|
AdSourceType.Ima -> "ima"
|
|
629
632
|
AdSourceType.Unknown -> "unknown"
|
|
630
633
|
AdSourceType.Progressive -> "progressive"
|
|
@@ -750,6 +753,7 @@ fun toPlayerViewConfig(json: ReadableMap) = PlayerViewConfig(
|
|
|
750
753
|
?.getBooleanOrNull("playbackSpeedSelectionEnabled")
|
|
751
754
|
?: true,
|
|
752
755
|
),
|
|
756
|
+
hideFirstFrame = json.getBooleanOrNull("hideFirstFrame") ?: false,
|
|
753
757
|
)
|
|
754
758
|
|
|
755
759
|
private fun ReadableMap.toUserInterfaceTypeFromPlayerConfig(): UserInterfaceType? =
|
package/android/src/main/java/com/bitmovin/player/reactnative/ui/RNPictureInPictureHandler.kt
CHANGED
|
@@ -1,200 +1,59 @@
|
|
|
1
1
|
package com.bitmovin.player.reactnative.ui
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
3
4
|
import android.app.PictureInPictureParams
|
|
4
|
-
import android.content.pm.PackageManager
|
|
5
|
-
import android.content.res.Configuration
|
|
6
|
-
import android.graphics.Rect
|
|
7
5
|
import android.os.Build
|
|
6
|
+
import android.util.Log
|
|
8
7
|
import android.util.Rational
|
|
9
8
|
import androidx.annotation.RequiresApi
|
|
10
|
-
import
|
|
11
|
-
import com.bitmovin.player.
|
|
12
|
-
|
|
9
|
+
import com.bitmovin.player.api.Player
|
|
10
|
+
import com.bitmovin.player.ui.DefaultPictureInPictureHandler
|
|
11
|
+
|
|
12
|
+
private const val TAG = "RNPiPHandler"
|
|
13
|
+
|
|
14
|
+
class RNPictureInPictureHandler(
|
|
15
|
+
private val activity: Activity,
|
|
16
|
+
private val player: Player,
|
|
17
|
+
) : DefaultPictureInPictureHandler(activity, player) {
|
|
18
|
+
// Current PiP implementation on the native side requires playerView.exitPictureInPicture() to be called
|
|
19
|
+
// for `PictureInPictureExit` event to be emitted.
|
|
20
|
+
// Additionally, the event is only emitted if `isPictureInPicture` is true. At the point in time we call
|
|
21
|
+
// playerView.exitPictureInPicture() the activity will already have exited the PiP mode,
|
|
22
|
+
// and thus the event won't be emitted. To work around this we keep track of the PiP state ourselves.
|
|
23
|
+
private var _isPictureInPicture = false
|
|
13
24
|
|
|
14
|
-
/**
|
|
15
|
-
* Delegate object for `RNPictureInPictureHandler`. It delegates all view logic that needs
|
|
16
|
-
* to be performed during each PiP state to this object.
|
|
17
|
-
*/
|
|
18
|
-
interface RNPictureInPictureDelegate {
|
|
19
|
-
/**
|
|
20
|
-
* Called whenever the handler's `isInPictureInPictureMode` changes to `true`.
|
|
21
|
-
*/
|
|
22
|
-
fun onExitPictureInPicture()
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Called whenever the handler's `isInPictureInPictureMode` changes to `false`.
|
|
26
|
-
*/
|
|
27
|
-
fun onEnterPictureInPicture()
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Called whenever the activity's PiP mode state changes with the new resources configuration.
|
|
31
|
-
*/
|
|
32
|
-
fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?)
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Called whenever the handler needs to compute a new `sourceRectHint` for PiP params.
|
|
36
|
-
* The passed rect reference is expected to be fulfilled with the PlayerView's global visible
|
|
37
|
-
* rect.
|
|
38
|
-
*/
|
|
39
|
-
fun setSourceRectHint(sourceRectHint: Rect)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Custom PictureInPictureHandler` concrete implementation designed for React Native. It relies on
|
|
44
|
-
* React Native's application context to manage the application's PiP state. Can be subclassed in
|
|
45
|
-
* order to provide custom PiP capabilities.
|
|
46
|
-
*/
|
|
47
|
-
open class RNPictureInPictureHandler(val context: ReactApplicationContext) : PictureInPictureHandler {
|
|
48
|
-
/**
|
|
49
|
-
* Configuration type for picture in picture behaviors.
|
|
50
|
-
*/
|
|
51
|
-
data class PictureInPictureConfig(val isEnabled: Boolean)
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* PiP delegate object that contains the view logic to be performed on each PiP state change.
|
|
55
|
-
*/
|
|
56
|
-
private var delegate: RNPictureInPictureDelegate? = null
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Whether the user has enabled PiP support via `isPictureInPictureEnabled` playback configuration in JS.
|
|
60
|
-
*/
|
|
61
|
-
var isPictureInPictureEnabled = false
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Whether this view is currently in PiP mode.
|
|
65
|
-
*/
|
|
66
|
-
private var isInPictureInPictureMode = false
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Whether the current Android version supports PiP mode.
|
|
70
|
-
*/
|
|
71
|
-
private val isPictureInPictureSupported: Boolean
|
|
72
|
-
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
|
|
73
|
-
context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Whether the picture in picture feature is available and enabled.
|
|
77
|
-
*/
|
|
78
|
-
override val isPictureInPictureAvailable: Boolean
|
|
79
|
-
get() = isPictureInPictureEnabled && isPictureInPictureSupported
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Whether this view is currently in PiP mode. Required for PictureInPictureHandler interface.
|
|
83
|
-
*/
|
|
84
25
|
override val isPictureInPicture: Boolean
|
|
85
|
-
get() =
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Current React activity computed property.
|
|
89
|
-
*/
|
|
90
|
-
private val currentActivity: AppCompatActivity?
|
|
91
|
-
get() {
|
|
92
|
-
if (context.hasCurrentActivity()) {
|
|
93
|
-
return context.currentActivity as AppCompatActivity
|
|
94
|
-
}
|
|
95
|
-
return null
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Sets the new delegate object and update the activity's PiP parameters accordingly.
|
|
100
|
-
*/
|
|
101
|
-
open fun setDelegate(delegate: RNPictureInPictureDelegate?) {
|
|
102
|
-
this.delegate = delegate
|
|
103
|
-
// Update the activity's PiP params once the delegate has been set.
|
|
104
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPictureAvailable) {
|
|
105
|
-
applyPictureInPictureParams()
|
|
106
|
-
}
|
|
107
|
-
}
|
|
26
|
+
get() = _isPictureInPicture
|
|
108
27
|
|
|
109
|
-
|
|
110
|
-
* Called whenever bitmovin's `PlayerView` needs to enter PiP mode.
|
|
111
|
-
*/
|
|
28
|
+
@RequiresApi(Build.VERSION_CODES.O)
|
|
112
29
|
override fun enterPictureInPicture() {
|
|
113
|
-
if (isPictureInPictureAvailable) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
it.enterPictureInPictureMode()
|
|
117
|
-
}
|
|
30
|
+
if (!isPictureInPictureAvailable) {
|
|
31
|
+
Log.w(TAG, "Calling enterPictureInPicture without PiP support.")
|
|
32
|
+
return
|
|
118
33
|
}
|
|
119
|
-
}
|
|
120
34
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*/
|
|
124
|
-
override fun exitPictureInPicture() {
|
|
125
|
-
if (isPictureInPictureAvailable) {
|
|
126
|
-
currentActivity?.supportActionBar?.show()
|
|
35
|
+
if (isPictureInPicture) {
|
|
36
|
+
return
|
|
127
37
|
}
|
|
128
|
-
}
|
|
129
38
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
handlePictureInPictureModeChanges(newConfig)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
39
|
+
// The default implementation doesn't properly handle the case where source isn't loaded yet.
|
|
40
|
+
// To work around it we just use a 16:9 aspect ratio if we cannot calculate it from `playbackVideoData`.
|
|
41
|
+
val aspectRatio =
|
|
42
|
+
player.playbackVideoData
|
|
43
|
+
?.let { Rational(it.width, it.height) }
|
|
44
|
+
?: Rational(16, 9)
|
|
139
45
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
@RequiresApi(Build.VERSION_CODES.N)
|
|
145
|
-
private fun handlePictureInPictureModeChanges(newConfig: Configuration?) = currentActivity?.let {
|
|
146
|
-
if (isInPictureInPictureMode != it.isInPictureInPictureMode) {
|
|
147
|
-
delegate?.onPictureInPictureModeChanged(it.isInPictureInPictureMode, newConfig)
|
|
148
|
-
if (it.isInPictureInPictureMode) {
|
|
149
|
-
delegate?.onEnterPictureInPicture()
|
|
150
|
-
} else {
|
|
151
|
-
delegate?.onExitPictureInPicture()
|
|
152
|
-
}
|
|
153
|
-
isInPictureInPictureMode = it.isInPictureInPictureMode
|
|
154
|
-
}
|
|
155
|
-
}
|
|
46
|
+
val params =
|
|
47
|
+
PictureInPictureParams.Builder()
|
|
48
|
+
.setAspectRatio(aspectRatio)
|
|
49
|
+
.build()
|
|
156
50
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
*
|
|
160
|
-
* You can read more about the recommended settings for PiP here:
|
|
161
|
-
* - https://developer.android.com/develop/ui/views/picture-in-picture#smoother-transition
|
|
162
|
-
* - https://developer.android.com/develop/ui/views/picture-in-picture#smoother-exit
|
|
163
|
-
*/
|
|
164
|
-
@RequiresApi(Build.VERSION_CODES.O)
|
|
165
|
-
private fun applyPictureInPictureParams() = currentActivity?.let {
|
|
166
|
-
// See also: https://developer.android.com/develop/ui/views/picture-in-picture#smoother-transition
|
|
167
|
-
val sourceRectHint = Rect()
|
|
168
|
-
delegate?.setSourceRectHint(sourceRectHint)
|
|
169
|
-
val ratio = Rational(16, 9)
|
|
170
|
-
val params = PictureInPictureParams.Builder()
|
|
171
|
-
.setAspectRatio(ratio)
|
|
172
|
-
.setSourceRectHint(sourceRectHint)
|
|
173
|
-
when {
|
|
174
|
-
// See also: https://developer.android.com/develop/ui/views/picture-in-picture#smoother-exit
|
|
175
|
-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
|
|
176
|
-
params.setAutoEnterEnabled(true).setSeamlessResizeEnabled(true)
|
|
177
|
-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ->
|
|
178
|
-
params.setExpandedAspectRatio(ratio)
|
|
179
|
-
}
|
|
180
|
-
it.setPictureInPictureParams(params.build())
|
|
51
|
+
activity.enterPictureInPictureMode(params)
|
|
52
|
+
_isPictureInPicture = true
|
|
181
53
|
}
|
|
182
54
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
open fun updateSourceRectHint() {
|
|
187
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isPictureInPictureAvailable) {
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
currentActivity?.let {
|
|
191
|
-
val sourceRectHint = Rect()
|
|
192
|
-
delegate?.setSourceRectHint(sourceRectHint)
|
|
193
|
-
it.setPictureInPictureParams(
|
|
194
|
-
PictureInPictureParams.Builder()
|
|
195
|
-
.setSourceRectHint(sourceRectHint)
|
|
196
|
-
.build(),
|
|
197
|
-
)
|
|
198
|
-
}
|
|
55
|
+
override fun exitPictureInPicture() {
|
|
56
|
+
super.exitPictureInPicture()
|
|
57
|
+
_isPictureInPicture = false
|
|
199
58
|
}
|
|
200
59
|
}
|
|
@@ -1156,7 +1156,8 @@ extension RCTConvert {
|
|
|
1156
1156
|
|
|
1157
1157
|
return RNPlayerViewConfig(
|
|
1158
1158
|
uiConfig: rnUiConfig(json["uiConfig"]),
|
|
1159
|
-
pictureInPictureConfig: pictureInPictureConfig(json["pictureInPictureConfig"])
|
|
1159
|
+
pictureInPictureConfig: pictureInPictureConfig(json["pictureInPictureConfig"]),
|
|
1160
|
+
hideFirstFrame: json["hideFirstFrame"] as? Bool
|
|
1160
1161
|
)
|
|
1161
1162
|
}
|
|
1162
1163
|
|
|
@@ -1256,6 +1257,15 @@ internal struct RNPlayerViewConfig {
|
|
|
1256
1257
|
}
|
|
1257
1258
|
return config
|
|
1258
1259
|
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* When set to `true` the first frame of the main content will not be rendered before playback starts.
|
|
1263
|
+
* Default is `false`.
|
|
1264
|
+
*
|
|
1265
|
+
* To reliably hide the first frame before a pre-roll ad, please ensure that you are using the
|
|
1266
|
+
* `AdvertisingConfig` to schedule ads and not the `scheduleAd` API call.
|
|
1267
|
+
*/
|
|
1268
|
+
var hideFirstFrame: Bool?
|
|
1259
1269
|
}
|
|
1260
1270
|
|
|
1261
1271
|
/**
|
|
@@ -32,25 +32,13 @@ public class RNPlayerViewManager: RCTViewManager {
|
|
|
32
32
|
return
|
|
33
33
|
}
|
|
34
34
|
let playerViewConfig = RCTConvert.rnPlayerViewConfig(view.config)
|
|
35
|
-
|
|
36
|
-
if
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
player.config.styleConfig.userInterfaceConfig = bitmovinUserInterfaceConfig
|
|
42
|
-
if let uiConfig = playerViewConfig?.uiConfig {
|
|
43
|
-
bitmovinUserInterfaceConfig
|
|
44
|
-
.playbackSpeedSelectionEnabled = uiConfig.playbackSpeedSelectionEnabled
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if let customMessageHandlerBridgeId = self.customMessageHandlerBridgeId,
|
|
48
|
-
let customMessageHandlerBridge = self.bridge[CustomMessageHandlerModule.self]?
|
|
49
|
-
.retrieve(customMessageHandlerBridgeId) {
|
|
50
|
-
bitmovinUserInterfaceConfig.customMessageHandler = customMessageHandlerBridge.customMessageHandler
|
|
51
|
-
}
|
|
35
|
+
|
|
36
|
+
if let userInterfaceConfig = maybeCreateUserInterfaceConfig(
|
|
37
|
+
styleConfig: player.config.styleConfig,
|
|
38
|
+
playerViewConfig: playerViewConfig
|
|
39
|
+
) {
|
|
40
|
+
player.config.styleConfig.userInterfaceConfig = userInterfaceConfig
|
|
52
41
|
}
|
|
53
|
-
#endif
|
|
54
42
|
|
|
55
43
|
let previousPictureInPictureAvailableValue: Bool
|
|
56
44
|
if let playerView = view.playerView {
|
|
@@ -74,6 +62,46 @@ public class RNPlayerViewManager: RCTViewManager {
|
|
|
74
62
|
}
|
|
75
63
|
}
|
|
76
64
|
|
|
65
|
+
private func maybeCreateUserInterfaceConfig(
|
|
66
|
+
styleConfig: StyleConfig,
|
|
67
|
+
playerViewConfig: RNPlayerViewConfig?
|
|
68
|
+
) -> UserInterfaceConfig? {
|
|
69
|
+
#if os(iOS)
|
|
70
|
+
if styleConfig.userInterfaceType == .bitmovin {
|
|
71
|
+
let bitmovinUserInterfaceConfig = styleConfig
|
|
72
|
+
.userInterfaceConfig as? BitmovinUserInterfaceConfig ?? BitmovinUserInterfaceConfig()
|
|
73
|
+
|
|
74
|
+
if let uiConfig = playerViewConfig?.uiConfig {
|
|
75
|
+
bitmovinUserInterfaceConfig
|
|
76
|
+
.playbackSpeedSelectionEnabled = uiConfig.playbackSpeedSelectionEnabled
|
|
77
|
+
}
|
|
78
|
+
if let hideFirstFrame = playerViewConfig?.hideFirstFrame {
|
|
79
|
+
bitmovinUserInterfaceConfig.hideFirstFrame = hideFirstFrame
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if let customMessageHandlerBridgeId = self.customMessageHandlerBridgeId,
|
|
83
|
+
let customMessageHandlerBridge = self.bridge[CustomMessageHandlerModule.self]?
|
|
84
|
+
.retrieve(customMessageHandlerBridgeId) {
|
|
85
|
+
bitmovinUserInterfaceConfig.customMessageHandler = customMessageHandlerBridge.customMessageHandler
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return bitmovinUserInterfaceConfig
|
|
89
|
+
}
|
|
90
|
+
#endif
|
|
91
|
+
if styleConfig.userInterfaceType == .system {
|
|
92
|
+
let systemUserInterfaceConfig = styleConfig
|
|
93
|
+
.userInterfaceConfig as? SystemUserInterfaceConfig ?? SystemUserInterfaceConfig()
|
|
94
|
+
|
|
95
|
+
if let hideFirstFrame = playerViewConfig?.hideFirstFrame {
|
|
96
|
+
systemUserInterfaceConfig.hideFirstFrame = hideFirstFrame
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return systemUserInterfaceConfig
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return nil
|
|
103
|
+
}
|
|
104
|
+
|
|
77
105
|
@objc
|
|
78
106
|
func attachFullscreenBridge(_ viewId: NSNumber, fullscreenBridgeId: NativeId) {
|
|
79
107
|
bridge.uiManager.addUIBlock { [weak self] _, views in
|
package/lib/index.d.mts
CHANGED
|
@@ -2779,6 +2779,13 @@ interface TweaksConfig {
|
|
|
2779
2779
|
* @platform Android
|
|
2780
2780
|
*/
|
|
2781
2781
|
useFiletypeExtractorFallbackForHls?: boolean;
|
|
2782
|
+
/**
|
|
2783
|
+
* Specifies whether the player should prefer software decoding over hardware decoding for ad playback.
|
|
2784
|
+
* This only affects ads playback, the player will still prefer hardware decoding for the main content.
|
|
2785
|
+
*
|
|
2786
|
+
* @platform Android
|
|
2787
|
+
*/
|
|
2788
|
+
preferSoftwareDecodingForAds?: boolean;
|
|
2782
2789
|
}
|
|
2783
2790
|
|
|
2784
2791
|
/**
|
|
@@ -3496,6 +3503,13 @@ interface PlayerViewConfig {
|
|
|
3496
3503
|
* Provides options to configure Picture in Picture playback.
|
|
3497
3504
|
*/
|
|
3498
3505
|
pictureInPictureConfig?: PictureInPictureConfig;
|
|
3506
|
+
/**
|
|
3507
|
+
* When set to `true`, the first frame of the main content will not be rendered before playback starts. Default is `false`.
|
|
3508
|
+
* This configuration has no effect for the {@link UserInterfaceType.Subtitle} on iOS/tvOS.
|
|
3509
|
+
*
|
|
3510
|
+
* To reliably hide the first frame before a pre-roll ad, please ensure that you are using the {@link AdvertisingConfig} to schedule ads and not the {@link Player.scheduleAd} API call.
|
|
3511
|
+
*/
|
|
3512
|
+
hideFirstFrame?: boolean;
|
|
3499
3513
|
}
|
|
3500
3514
|
/**
|
|
3501
3515
|
* Configures the visual presentation and behaviour of the Bitmovin Player UI.
|
package/lib/index.d.ts
CHANGED
|
@@ -2779,6 +2779,13 @@ interface TweaksConfig {
|
|
|
2779
2779
|
* @platform Android
|
|
2780
2780
|
*/
|
|
2781
2781
|
useFiletypeExtractorFallbackForHls?: boolean;
|
|
2782
|
+
/**
|
|
2783
|
+
* Specifies whether the player should prefer software decoding over hardware decoding for ad playback.
|
|
2784
|
+
* This only affects ads playback, the player will still prefer hardware decoding for the main content.
|
|
2785
|
+
*
|
|
2786
|
+
* @platform Android
|
|
2787
|
+
*/
|
|
2788
|
+
preferSoftwareDecodingForAds?: boolean;
|
|
2782
2789
|
}
|
|
2783
2790
|
|
|
2784
2791
|
/**
|
|
@@ -3496,6 +3503,13 @@ interface PlayerViewConfig {
|
|
|
3496
3503
|
* Provides options to configure Picture in Picture playback.
|
|
3497
3504
|
*/
|
|
3498
3505
|
pictureInPictureConfig?: PictureInPictureConfig;
|
|
3506
|
+
/**
|
|
3507
|
+
* When set to `true`, the first frame of the main content will not be rendered before playback starts. Default is `false`.
|
|
3508
|
+
* This configuration has no effect for the {@link UserInterfaceType.Subtitle} on iOS/tvOS.
|
|
3509
|
+
*
|
|
3510
|
+
* To reliably hide the first frame before a pre-roll ad, please ensure that you are using the {@link AdvertisingConfig} to schedule ads and not the {@link Player.scheduleAd} API call.
|
|
3511
|
+
*/
|
|
3512
|
+
hideFirstFrame?: boolean;
|
|
3499
3513
|
}
|
|
3500
3514
|
/**
|
|
3501
3515
|
* Configures the visual presentation and behaviour of the Bitmovin Player UI.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bitmovin-player-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Official React Native bindings for Bitmovin's mobile Player SDKs.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/index.mjs",
|
|
@@ -59,14 +59,12 @@
|
|
|
59
59
|
"@babel/core": "7.22.10",
|
|
60
60
|
"@babel/preset-env": "7.22.10",
|
|
61
61
|
"@babel/runtime": "7.22.10",
|
|
62
|
-
"@commitlint/config-conventional": "17.7.0",
|
|
63
62
|
"@react-native-community/eslint-config": "3.1.0",
|
|
64
63
|
"@react-native/babel-preset": "0.73.18",
|
|
65
64
|
"@types/jest": "^28.1.2",
|
|
66
65
|
"@types/lodash.omit": "4.5.0",
|
|
67
66
|
"@types/react": "~18.2.26",
|
|
68
67
|
"babel-plugin-module-resolver": "5.0.0",
|
|
69
|
-
"commitlint": "17.7.1",
|
|
70
68
|
"eslint": "8.24.0",
|
|
71
69
|
"eslint-config-prettier": "8.5.0",
|
|
72
70
|
"eslint-plugin-prettier": "4.2.1",
|
|
@@ -92,11 +90,6 @@
|
|
|
92
90
|
"react": ">=17",
|
|
93
91
|
"react-native": ">=0.65"
|
|
94
92
|
},
|
|
95
|
-
"commitlint": {
|
|
96
|
-
"extends": [
|
|
97
|
-
"@commitlint/config-conventional"
|
|
98
|
-
]
|
|
99
|
-
},
|
|
100
93
|
"lint-staged": {
|
|
101
94
|
"*.(ts|tsx)": "eslint",
|
|
102
95
|
"*.(ts|tsx|js|jsx|md|json|yml|yaml)": "prettier --write"
|
|
@@ -19,6 +19,14 @@ export interface PlayerViewConfig {
|
|
|
19
19
|
* Provides options to configure Picture in Picture playback.
|
|
20
20
|
*/
|
|
21
21
|
pictureInPictureConfig?: PictureInPictureConfig;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* When set to `true`, the first frame of the main content will not be rendered before playback starts. Default is `false`.
|
|
25
|
+
* This configuration has no effect for the {@link UserInterfaceType.Subtitle} on iOS/tvOS.
|
|
26
|
+
*
|
|
27
|
+
* To reliably hide the first frame before a pre-roll ad, please ensure that you are using the {@link AdvertisingConfig} to schedule ads and not the {@link Player.scheduleAd} API call.
|
|
28
|
+
*/
|
|
29
|
+
hideFirstFrame?: boolean;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
/**
|
package/src/tweaksConfig.ts
CHANGED
|
@@ -150,4 +150,11 @@ export interface TweaksConfig {
|
|
|
150
150
|
* @platform Android
|
|
151
151
|
*/
|
|
152
152
|
useFiletypeExtractorFallbackForHls?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Specifies whether the player should prefer software decoding over hardware decoding for ad playback.
|
|
155
|
+
* This only affects ads playback, the player will still prefer hardware decoding for the main content.
|
|
156
|
+
*
|
|
157
|
+
* @platform Android
|
|
158
|
+
*/
|
|
159
|
+
preferSoftwareDecodingForAds?: boolean;
|
|
153
160
|
}
|