react-native-theoplayer 2.1.0 → 2.3.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.
Files changed (27) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/android/build.gradle +3 -1
  3. package/android/src/main/AndroidManifest.xml +1 -0
  4. package/android/src/main/java/com/theoplayer/PlayerConfigAdapter.kt +28 -0
  5. package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +1 -1
  6. package/android/src/main/java/com/theoplayer/presentation/PipUtils.kt +244 -0
  7. package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +22 -55
  8. package/android/src/main/res/drawable/ic_fast_forward.xml +9 -0
  9. package/android/src/main/res/drawable/ic_rewind.xml +9 -0
  10. package/android/src/main/res/values/strings.xml +8 -0
  11. package/ios/THEOplayerRCTMainEventHandler.swift +1 -1
  12. package/ios/THEOplayerRCTPlayerAPI.swift +2 -3
  13. package/ios/THEOplayerRCTView.swift +7 -2
  14. package/ios/pip/THEOplayerRCTPipControlsManager.swift +33 -28
  15. package/ios/pip/THEOplayerRCTView+PipConfig.swift +9 -14
  16. package/lib/commonjs/api/config/PlayerConfiguration.js.map +1 -1
  17. package/lib/commonjs/api/utils/RetryConfiguration.js +2 -0
  18. package/lib/commonjs/api/utils/RetryConfiguration.js.map +1 -0
  19. package/lib/module/api/config/PlayerConfiguration.js.map +1 -1
  20. package/lib/module/api/utils/RetryConfiguration.js +2 -0
  21. package/lib/module/api/utils/RetryConfiguration.js.map +1 -0
  22. package/lib/typescript/api/config/PlayerConfiguration.d.ts +8 -0
  23. package/lib/typescript/api/utils/RetryConfiguration.d.ts +21 -0
  24. package/package.json +2 -2
  25. package/react-native-theoplayer.podspec +2 -3
  26. package/src/api/config/PlayerConfiguration.ts +9 -0
  27. package/src/api/utils/RetryConfiguration.ts +23 -0
package/CHANGELOG.md CHANGED
@@ -5,7 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased 2.1.0]
8
+ ## [2.3.0] - 23-04-14
9
+
10
+ ### Changed
11
+
12
+ - Updated picture-in-picture controls on Android to include forward/rewind buttons and disabled pause button for ads.
13
+
14
+ ## [2.2.0] - 23-04-12
15
+
16
+ ### Fixed
17
+
18
+ - Fixed an issue on Android and iOS where error codes were not correctly formatted.
19
+
20
+ ### Added
21
+
22
+ - Added `RetryConfiguration` on `PlayerConfiguration` for Web and Android.
23
+
24
+ ### Changed
25
+
26
+ - Set minimum THEOplayer dependency version to 5.0.1 for Web, iOS and Android.
27
+ - Set `MediaPlaybackService` disabled by default on Android.
28
+
29
+ ## [2.1.0] - 23-04-09
9
30
 
10
31
  ### Fixed
11
32
 
@@ -105,7 +105,9 @@ dependencies {
105
105
  implementation "com.theoplayer.theoplayer-sdk-android:ads-wrapper:4.8.0"
106
106
  implementation "androidx.appcompat:appcompat:1.4.+"
107
107
 
108
- def theoplayer_sdk_version = safeExtGet('THEOplayer_sdk', '+')
108
+ // The minimum supported version is 5.0.1
109
+ def theoplayer_sdk_version = safeExtGet('THEOplayer_sdk', '[5.0.1,)')
110
+
109
111
  // def theoplayer_mediasession_version = safeExtGet('THEOplayer_mediasession', theoplayer_sdk_version)
110
112
  def theoplayer_mediasession_version = "4.12.0-local"
111
113
  def enabledV4 = theoplayer_sdk_version.toString().startsWith("4.")
@@ -32,6 +32,7 @@
32
32
  android:name="com.theoplayer.audio.MediaPlaybackService"
33
33
  android:description="@string/background_playback_service_description"
34
34
  android:exported="false"
35
+ android:enabled="false"
35
36
  android:foregroundServiceType="mediaPlayback">
36
37
  <intent-filter>
37
38
  <action android:name="android.media.browse.MediaBrowserService" />
@@ -10,6 +10,7 @@ import com.theoplayer.android.api.ads.GoogleImaConfiguration
10
10
  import com.theoplayer.android.api.cast.CastStrategy
11
11
  import com.google.android.gms.cast.framework.CastContext
12
12
  import com.theoplayer.android.api.pip.PipConfiguration
13
+ import com.theoplayer.android.api.player.NetworkConfiguration
13
14
 
14
15
  private const val TAG = "PlayerConfigAdapter"
15
16
  private const val PROP_ADS_CONFIGURATION = "ads"
@@ -24,6 +25,10 @@ private const val PROP_CAST_CONFIGURATION = "cast"
24
25
  private const val PROP_CAST_STRATEGY = "strategy"
25
26
  private const val PROP_CHROMECAST_CONFIG = "chromecast"
26
27
  private const val PROP_CHROMECAST_APPID = "appID"
28
+ private const val PROP_RETRY_CONFIG = "retryConfiguration"
29
+ private const val PROP_RETRY_MAX_RETRIES = "maxRetries"
30
+ private const val PROP_RETRY_MIN_BACKOFF = "minimumBackoff"
31
+ private const val PROP_RETRY_MAX_BACKOFF = "maximumBackoff"
27
32
 
28
33
  object PlayerConfigAdapter {
29
34
 
@@ -45,6 +50,12 @@ object PlayerConfigAdapter {
45
50
  if (configProps.hasKey(PROP_CHROMELESS)) {
46
51
  configBuilder.chromeless(configProps.getBoolean(PROP_CHROMELESS))
47
52
  }
53
+ if (configProps.hasKey(PROP_RETRY_CONFIG)) {
54
+ val networkConfig = networkConfigurationFromProps(configProps.getMap(PROP_RETRY_CONFIG))
55
+ if (networkConfig != null) {
56
+ configBuilder.networkConfiguration(networkConfig)
57
+ }
58
+ }
48
59
  applyCastConfigurationFromProps(configBuilder, configProps.getMap(PROP_CAST_CONFIGURATION))
49
60
  configBuilder.pipConfiguration(PipConfiguration.Builder().build())
50
61
  }
@@ -72,6 +83,23 @@ object PlayerConfigAdapter {
72
83
  return builder.build()
73
84
  }
74
85
 
86
+ private fun networkConfigurationFromProps(configProps: ReadableMap?): NetworkConfiguration? {
87
+ if (configProps == null) {
88
+ return null
89
+ }
90
+ val builder = NetworkConfiguration.Builder()
91
+ if (configProps.hasKey(PROP_RETRY_MAX_RETRIES)) {
92
+ builder.maxRetries(configProps.getInt(PROP_RETRY_MAX_RETRIES))
93
+ }
94
+ if (configProps.hasKey(PROP_RETRY_MIN_BACKOFF)) {
95
+ builder.minimumBackOff(configProps.getDouble(PROP_RETRY_MIN_BACKOFF).toLong())
96
+ }
97
+ if (configProps.hasKey(PROP_RETRY_MAX_BACKOFF)) {
98
+ builder.maximumBackOff(configProps.getDouble(PROP_RETRY_MAX_BACKOFF).toLong())
99
+ }
100
+ return builder.build()
101
+ }
102
+
75
103
  private fun applyCastConfigurationFromProps(
76
104
  configBuilder: THEOplayerConfig.Builder,
77
105
  castConfig: ReadableMap?
@@ -249,7 +249,7 @@ class PlayerEventEmitter internal constructor(
249
249
  }
250
250
 
251
251
  fun emitError(exception: THEOplayerException) {
252
- emitError(exception.code.name, exception.message)
252
+ emitError(exception.code.id.toString(), exception.message)
253
253
  }
254
254
 
255
255
  fun emitPresentationModeChange(
@@ -0,0 +1,244 @@
1
+ package com.theoplayer.presentation
2
+
3
+ import android.app.PendingIntent
4
+ import android.app.PictureInPictureParams
5
+ import android.app.RemoteAction
6
+ import android.content.BroadcastReceiver
7
+ import android.content.Context
8
+ import android.content.Intent
9
+ import android.content.IntentFilter
10
+ import android.graphics.Rect
11
+ import android.graphics.drawable.Icon
12
+ import android.os.Build
13
+ import android.util.Rational
14
+ import android.view.SurfaceView
15
+ import android.view.TextureView
16
+ import android.view.View
17
+ import android.view.ViewGroup
18
+ import androidx.annotation.RequiresApi
19
+ import com.facebook.react.uimanager.ThemedReactContext
20
+ import com.theoplayer.R
21
+ import com.theoplayer.ReactTHEOplayerContext
22
+ import com.theoplayer.android.api.ads.ima.GoogleImaAdEvent
23
+ import com.theoplayer.android.api.ads.ima.GoogleImaAdEventType
24
+ import com.theoplayer.android.api.event.EventListener
25
+ import com.theoplayer.android.api.event.player.PlayerEvent
26
+ import com.theoplayer.android.api.event.player.PlayerEventTypes
27
+ import com.theoplayer.android.api.player.Player
28
+
29
+ private const val EXTRA_ACTION = "EXTRA_ACTION"
30
+ private const val ACTION_MEDIA_CONTROL = "pip_media_control"
31
+ private const val ACTION_PLAY = 0
32
+ private const val ACTION_PAUSE = ACTION_PLAY + 1
33
+ private const val ACTION_RWD = ACTION_PLAY + 2
34
+ private const val ACTION_FFD = ACTION_PLAY + 3
35
+ private const val ACTION_IGNORE = ACTION_PLAY + 999
36
+ private const val SKIP_TIME = 15
37
+
38
+ private val PIP_ASPECT_RATIO_DEFAULT = Rational(16, 9)
39
+ private val PIP_ASPECT_RATIO_MIN = Rational(100, 239)
40
+ private val PIP_ASPECT_RATIO_MAX = Rational(239, 100)
41
+
42
+ class PipUtils(
43
+ private val viewCtx: ReactTHEOplayerContext,
44
+ private val reactContext: ThemedReactContext
45
+ ) {
46
+
47
+ private var enabled: Boolean = false
48
+ private var onPlayerAction: EventListener<PlayerEvent<*>>? = null
49
+ private var onAdAction: EventListener<GoogleImaAdEvent>? = null
50
+ private val playerEvents = listOf(PlayerEventTypes.PLAY, PlayerEventTypes.PAUSE)
51
+ private val adEvents = listOf(GoogleImaAdEventType.STARTED, GoogleImaAdEventType.CONTENT_RESUME_REQUESTED)
52
+ private val broadcastReceiver: BroadcastReceiver = buildBroadcastReceiver()
53
+
54
+ private val player: Player
55
+ get() = viewCtx.player
56
+
57
+ init {
58
+ onPlayerAction = EventListener {
59
+ updatePipParams()
60
+ }
61
+ onAdAction = EventListener {
62
+ updatePipParams()
63
+ }
64
+ }
65
+
66
+ fun enable() {
67
+ if (enabled) {
68
+ return
69
+ }
70
+ playerEvents.forEach { action ->
71
+ player.addEventListener(action, onPlayerAction)
72
+ }
73
+ adEvents.forEach { action ->
74
+ player.ads.addEventListener(action, onAdAction)
75
+ }
76
+ reactContext.currentActivity?.registerReceiver(
77
+ broadcastReceiver,
78
+ IntentFilter(ACTION_MEDIA_CONTROL)
79
+ )
80
+ enabled = true
81
+ }
82
+
83
+ fun disable() {
84
+ if (!enabled) {
85
+ return
86
+ }
87
+ playerEvents.forEach { action ->
88
+ player.removeEventListener(action, onPlayerAction)
89
+ }
90
+ adEvents.forEach { action ->
91
+ player.ads.removeEventListener(action, onAdAction)
92
+ }
93
+ try {
94
+ reactContext.currentActivity?.unregisterReceiver(broadcastReceiver)
95
+ } catch (ignore: IllegalArgumentException) { /*ignore*/}
96
+ enabled = false
97
+ }
98
+
99
+ fun destroy() {
100
+ disable()
101
+ }
102
+
103
+ @RequiresApi(Build.VERSION_CODES.O)
104
+ fun buildPipActions(
105
+ paused: Boolean,
106
+ enablePlayPause: Boolean,
107
+ enableTrickPlay: Boolean
108
+ ): List<RemoteAction> {
109
+ return mutableListOf<RemoteAction>().apply {
110
+
111
+ // Trick-play: Rewind
112
+ if (enableTrickPlay) {
113
+ add(
114
+ buildRemoteAction(
115
+ ACTION_RWD,
116
+ R.drawable.ic_rewind,
117
+ R.string.rwd_pip,
118
+ R.string.rwd_desc_pip
119
+ )
120
+ )
121
+ }
122
+
123
+ // Play/pause
124
+ // Always add this button, but send an ACTION_IGNORE if disabled.
125
+ add(
126
+ if (paused) {
127
+ buildRemoteAction(
128
+ if (enablePlayPause) ACTION_PLAY else ACTION_IGNORE,
129
+ R.drawable.ic_play,
130
+ R.string.play_pip,
131
+ R.string.play_desc_pip
132
+ )
133
+ } else {
134
+ buildRemoteAction(
135
+ if (enablePlayPause) ACTION_PAUSE else ACTION_IGNORE,
136
+ R.drawable.ic_pause,
137
+ R.string.pause_pip,
138
+ R.string.pause_desc_pip
139
+ )
140
+ }
141
+ )
142
+
143
+ // Trick-play: Fast Forward
144
+ if (enableTrickPlay) {
145
+ add(
146
+ buildRemoteAction(
147
+ ACTION_FFD,
148
+ R.drawable.ic_fast_forward,
149
+ R.string.ffd_pip,
150
+ R.string.ffd_desc_pip
151
+ )
152
+ )
153
+ }
154
+ }
155
+ }
156
+
157
+ private fun getSafeAspectRatio(width: Int, height: Int): Rational {
158
+ val aspectRatio = Rational(width, height)
159
+ if (aspectRatio.isNaN || aspectRatio.isInfinite || aspectRatio.isZero) {
160
+ // Default aspect ratio
161
+ return PIP_ASPECT_RATIO_DEFAULT
162
+ }
163
+ if (aspectRatio > PIP_ASPECT_RATIO_MAX) {
164
+ return PIP_ASPECT_RATIO_MAX
165
+ }
166
+ if (aspectRatio < PIP_ASPECT_RATIO_MIN) {
167
+ return PIP_ASPECT_RATIO_MIN
168
+ }
169
+ return aspectRatio
170
+ }
171
+
172
+ private fun getContentViewRect(view: ViewGroup): Rect? {
173
+ for (i in 0 until view.childCount) {
174
+ val child: View = view.getChildAt(i)
175
+ if (child is ViewGroup) {
176
+ return getContentViewRect(child)
177
+ } else if (child as? SurfaceView != null || child as? TextureView != null) {
178
+ val visibleRect = Rect()
179
+ child.getGlobalVisibleRect(visibleRect)
180
+ return visibleRect
181
+ }
182
+ }
183
+ return null
184
+ }
185
+
186
+ @RequiresApi(Build.VERSION_CODES.O)
187
+ fun getPipParams(): PictureInPictureParams {
188
+ val view = viewCtx.playerView
189
+ val player = view.player
190
+ val visibleRect = getContentViewRect(view)
191
+ val isAd = player.ads.isPlaying
192
+ val isLive = player.duration.isInfinite()
193
+ val enablePlayPause = !isAd
194
+ val enableTrickPlay = !isAd && !isLive
195
+
196
+ return PictureInPictureParams.Builder()
197
+ .setSourceRectHint(visibleRect)
198
+ // Must be between 2.39:1 and 1:2.39 (inclusive)
199
+ .setAspectRatio(getSafeAspectRatio(view.player.videoWidth, view.player.videoHeight))
200
+ .setActions(
201
+ buildPipActions(player.isPaused, enablePlayPause, enableTrickPlay)
202
+ )
203
+ .build()
204
+ }
205
+
206
+ private fun updatePipParams() {
207
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
208
+ reactContext.currentActivity?.setPictureInPictureParams(getPipParams())
209
+ }
210
+ }
211
+
212
+ private fun buildBroadcastReceiver(): BroadcastReceiver {
213
+ return object : BroadcastReceiver() {
214
+ @RequiresApi(Build.VERSION_CODES.O)
215
+ override fun onReceive(context: Context?, intent: Intent?) {
216
+ intent?.getIntExtra(EXTRA_ACTION, -1)?.let { action ->
217
+ when (action) {
218
+ ACTION_PLAY -> player.play()
219
+ ACTION_PAUSE -> player.pause()
220
+ ACTION_FFD -> player.currentTime += SKIP_TIME
221
+ ACTION_RWD -> player.currentTime -= SKIP_TIME
222
+ }
223
+ reactContext.currentActivity?.setPictureInPictureParams(getPipParams())
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ @RequiresApi(Build.VERSION_CODES.O)
230
+ private fun buildRemoteAction(
231
+ requestId: Int,
232
+ iconId: Int,
233
+ titleId: Int,
234
+ descId: Int
235
+ ): RemoteAction {
236
+ val intent = Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_ACTION, requestId)
237
+ val pendingIntent =
238
+ PendingIntent.getBroadcast(reactContext, requestId, intent, PendingIntent.FLAG_IMMUTABLE)
239
+ val icon: Icon = Icon.createWithResource(reactContext, iconId)
240
+ val title = reactContext.getString(titleId)
241
+ val desc = reactContext.getString(descId)
242
+ return RemoteAction(icon, title, desc, pendingIntent)
243
+ }
244
+ }
@@ -1,19 +1,12 @@
1
1
  package com.theoplayer.presentation
2
2
 
3
3
  import android.app.AppOpsManager
4
- import android.app.PictureInPictureParams
5
4
  import android.content.BroadcastReceiver
6
5
  import android.content.Context
7
6
  import android.content.Intent
8
7
  import android.content.IntentFilter
9
8
  import android.content.pm.PackageManager
10
- import android.graphics.Rect
11
9
  import android.os.Build
12
- import android.util.Rational
13
- import android.view.SurfaceView
14
- import android.view.TextureView
15
- import android.view.View
16
- import android.view.ViewGroup
17
10
  import androidx.activity.ComponentActivity
18
11
  import androidx.core.view.WindowInsetsCompat
19
12
  import androidx.core.view.WindowInsetsControllerCompat
@@ -25,10 +18,6 @@ import com.theoplayer.android.api.error.ErrorCode
25
18
  import com.theoplayer.android.api.error.THEOplayerException
26
19
  import com.theoplayer.android.api.player.PresentationMode
27
20
 
28
- private val PIP_ASPECT_RATIO_DEFAULT = Rational(16, 9)
29
- private val PIP_ASPECT_RATIO_MIN = Rational(100, 239)
30
- private val PIP_ASPECT_RATIO_MAX = Rational(239, 100)
31
-
32
21
  class PresentationManager(
33
22
  private val viewCtx: ReactTHEOplayerContext,
34
23
  private val reactContext: ThemedReactContext,
@@ -38,6 +27,8 @@ class PresentationManager(
38
27
  private var onUserLeaveHintReceiver: BroadcastReceiver? = null
39
28
  private var onPictureInPictureModeChanged: BroadcastReceiver? = null
40
29
 
30
+ private val pipUtils: PipUtils = PipUtils(viewCtx, reactContext)
31
+
41
32
  var currentPresentationMode: PresentationMode = PresentationMode.INLINE
42
33
  private set
43
34
 
@@ -57,15 +48,9 @@ class PresentationManager(
57
48
  // Dispatch event on every PiP mode change
58
49
  val inPip = intent?.getBooleanExtra("isInPictureInPictureMode", false) ?: false
59
50
  if (inPip) {
60
- updatePresentationMode(PresentationMode.PICTURE_IN_PICTURE)
51
+ onEnterPip()
61
52
  } else {
62
- val pipCtx: PresentationModeChangePipContext = if ((reactContext.currentActivity as? ComponentActivity)
63
- ?.lifecycle?.currentState == Lifecycle.State.CREATED) {
64
- PresentationModeChangePipContext.CLOSED
65
- } else {
66
- PresentationModeChangePipContext.RESTORED
67
- }
68
- updatePresentationMode(PresentationMode.INLINE, PresentationModeChangeContext(pipCtx))
53
+ onExitPip()
69
54
  }
70
55
  }
71
56
  }
@@ -85,6 +70,7 @@ class PresentationManager(
85
70
  try {
86
71
  reactContext.currentActivity?.unregisterReceiver(onUserLeaveHintReceiver)
87
72
  reactContext.currentActivity?.unregisterReceiver(onPictureInPictureModeChanged)
73
+ pipUtils.destroy()
88
74
  } catch (ignore: Exception) {
89
75
  }
90
76
  }
@@ -104,20 +90,6 @@ class PresentationManager(
104
90
  }
105
91
  }
106
92
 
107
- private fun getContentViewRect(view: ViewGroup): Rect? {
108
- for (i in 0 until view.childCount) {
109
- val child: View = view.getChildAt(i)
110
- if (child is ViewGroup) {
111
- return getContentViewRect(child)
112
- } else if (child as? SurfaceView != null || child as? TextureView != null) {
113
- val visibleRect = Rect()
114
- child.getGlobalVisibleRect(visibleRect)
115
- return visibleRect
116
- }
117
- }
118
- return null
119
- }
120
-
121
93
  private fun enterPip() {
122
94
  // PiP not supported
123
95
  if (!supportsPip || Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
@@ -141,33 +113,28 @@ class PresentationManager(
141
113
  }
142
114
 
143
115
  try {
144
- val view = viewCtx.playerView
145
- val visibleRect = getContentViewRect(view)
146
- reactContext.currentActivity?.enterPictureInPictureMode(
147
- PictureInPictureParams.Builder().setSourceRectHint(visibleRect)
148
- // Must be between 2.39:1 and 1:2.39 (inclusive)
149
- .setAspectRatio(getSafeAspectRatio(view.player.videoWidth, view.player.videoHeight))
150
- // The active MediaSession will connect the controls
151
- .build()
152
- )
116
+ pipUtils.enable()
117
+ reactContext.currentActivity?.enterPictureInPictureMode(pipUtils.getPipParams())
153
118
  } catch (_: Exception) {
154
119
  onPipError()
155
120
  }
156
121
  }
157
122
 
158
- private fun getSafeAspectRatio(width: Int, height: Int): Rational {
159
- val aspectRatio = Rational(width, height)
160
- if (aspectRatio.isNaN || aspectRatio.isInfinite || aspectRatio.isZero) {
161
- // Default aspect ratio
162
- return PIP_ASPECT_RATIO_DEFAULT
163
- }
164
- if (aspectRatio > PIP_ASPECT_RATIO_MAX) {
165
- return PIP_ASPECT_RATIO_MAX
166
- }
167
- if (aspectRatio < PIP_ASPECT_RATIO_MIN) {
168
- return PIP_ASPECT_RATIO_MIN
169
- }
170
- return aspectRatio
123
+ private fun onEnterPip() {
124
+ updatePresentationMode(PresentationMode.PICTURE_IN_PICTURE)
125
+ }
126
+
127
+ private fun onExitPip() {
128
+ val pipCtx: PresentationModeChangePipContext =
129
+ if ((reactContext.currentActivity as? ComponentActivity)
130
+ ?.lifecycle?.currentState == Lifecycle.State.CREATED
131
+ ) {
132
+ PresentationModeChangePipContext.CLOSED
133
+ } else {
134
+ PresentationModeChangePipContext.RESTORED
135
+ }
136
+ updatePresentationMode(PresentationMode.INLINE, PresentationModeChangeContext(pipCtx))
137
+ pipUtils.disable()
171
138
  }
172
139
 
173
140
  private fun hasPipPermission(): Boolean {
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FF000000"
8
+ android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z"/>
9
+ </vector>
@@ -0,0 +1,9 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:fillColor="#FF000000"
8
+ android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z"/>
9
+ </vector>
@@ -2,6 +2,14 @@
2
2
  <resources>
3
3
  <string name="pause">Pause</string>
4
4
  <string name="play">Play</string>
5
+ <string name="pause_pip">Pause</string>
6
+ <string name="pause_desc_pip">Pause video</string>
7
+ <string name="play_pip">Play</string>
8
+ <string name="play_desc_pip">Play video</string>
9
+ <string name="ffd_pip">Fast Forward</string>
10
+ <string name="ffd_desc_pip">Fast Forward video</string>
11
+ <string name="rwd_pip">Rewind</string>
12
+ <string name="rwd_desc_pip">Rewind video</string>
5
13
 
6
14
  <string name="background_playback_service_description">THEOplayer service providing background playback.</string>
7
15
  <string name="notification_channel_id">theoplayer_default_channel</string>
@@ -239,7 +239,7 @@ class THEOplayerRCTMainEventHandler {
239
239
  if let forwardedErrorEvent = self?.onNativeError,
240
240
  let errorObject = event.errorObject
241
241
  {
242
- let errorCodeString = String(describing: errorObject.code)
242
+ let errorCodeString = String(errorObject.code.rawValue)
243
243
  let errorCodeMessage = errorObject.message
244
244
  forwardedErrorEvent(
245
245
  [
@@ -124,9 +124,9 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
124
124
  @objc(setPipConfig:pipConfig:)
125
125
  func setPipConfig(_ node: NSNumber, pipConfig: NSDictionary) -> Void {
126
126
  DispatchQueue.main.async {
127
- let newPipConfig: PipConfig = self.parsePipConfig(configDict: pipConfig)
128
127
  if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView {
129
- theView.pipConfig = newPipConfig
128
+ let pipConfig = self.parsePipConfig(configDict: pipConfig)
129
+ theView.pipConfig = pipConfig
130
130
  }
131
131
  }
132
132
  return
@@ -134,7 +134,6 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
134
134
 
135
135
  private func parsePipConfig(configDict: NSDictionary) -> PipConfig {
136
136
  var pipConfig = PipConfig()
137
- pipConfig.retainPresentationModeOnSourceChange = configDict["retainPresentationModeOnSourceChange"] as? Bool ?? false
138
137
  pipConfig.canStartPictureInPictureAutomaticallyFromInline = configDict["startsAutomatically"] as? Bool ?? false
139
138
  return pipConfig
140
139
  }
@@ -15,10 +15,15 @@ public class THEOplayerRCTView: UIView {
15
15
  var nowPlayingManager: THEOplayerRCTNowPlayingManager
16
16
  var remoteCommandsManager: THEOplayerRCTRemoteCommandsManager
17
17
  var pipControlsManager: THEOplayerRCTPipControlsManager
18
+ var presentationModeContext = THEOplayerRCTPresentationModeContext()
18
19
  var adsConfig = AdsConfig()
19
20
  var castConfig = CastConfig()
20
- var pipConfig = PipConfig()
21
- var presentationModeContext = THEOplayerRCTPresentationModeContext()
21
+
22
+ var pipConfig = PipConfig() {
23
+ didSet {
24
+ self.pipControlsManager.setPipConfig(pipConfig)
25
+ }
26
+ }
22
27
  var backgroundAudioConfig = BackgroundAudioConfig() {
23
28
  didSet {
24
29
  self.remoteCommandsManager.setBackGroundAudioConfig(backgroundAudioConfig)
@@ -8,18 +8,9 @@ import MediaPlayer
8
8
  class THEOplayerRCTPipControlsManager: NSObject {
9
9
  // MARK: Members
10
10
  private weak var player: THEOplayer?
11
- private var _nativePictureInPictureController: Any?
12
11
  private var isLive: Bool = false
13
12
  private var inAd: Bool = false
14
-
15
- @available(tvOS 14.0, *)
16
- private weak var nativePictureInPictureController: AVPictureInPictureController? {
17
- get {
18
- return _nativePictureInPictureController as? AVPictureInPictureController
19
- } set {
20
- _nativePictureInPictureController = newValue
21
- }
22
- }
13
+ private var pipConfig = PipConfig()
23
14
 
24
15
  // MARK: player Listeners
25
16
  private var durationChangeListener: EventListener?
@@ -43,11 +34,13 @@ class THEOplayerRCTPipControlsManager: NSObject {
43
34
  self.attachListeners()
44
35
  }
45
36
 
46
- @available(tvOS 14.0, *)
47
- func setNativePictureInPictureController(_ nativePictureInPictureController: AVPictureInPictureController?) {
48
- self.nativePictureInPictureController = nativePictureInPictureController;
49
- if self.nativePictureInPictureController != nil,
50
- let player = self.player,
37
+ func setPipConfig(_ newPipConfig: PipConfig) {
38
+ self.pipConfig = newPipConfig
39
+ self.updatePipControls()
40
+ }
41
+
42
+ func willStartPip() {
43
+ if let player = self.player,
51
44
  let duration = player.duration {
52
45
  self.isLive = duration.isInfinite
53
46
  #if (GOOGLE_IMA || GOOGLE_DAI) || canImport(THEOplayerGoogleIMAIntegration)
@@ -60,16 +53,32 @@ class THEOplayerRCTPipControlsManager: NSObject {
60
53
  #endif
61
54
  }
62
55
  }
63
-
64
- @available(tvOS 14.0, *)
56
+
65
57
  private func updatePipControls() {
66
- if let controller = self.nativePictureInPictureController {
67
- if #available(iOS 14.0, *) {
68
- controller.requiresLinearPlayback = self.isLive || self.inAd
69
- if DEBUG_PIPCONTROLS { print("[NATIVE] Pip controls updated for \(self.isLive ? "LIVE" : "VOD") (\(self.inAd ? "AD IS PLAYING" : "NO AD PLAYING")).") }
70
- }
58
+ if let player = self.player,
59
+ let pip = player.pip {
60
+ pip.configure(configuration: self.newPipConfiguration())
61
+ if DEBUG_PIPCONTROLS { print("[NATIVE] Pip controls updated for \(self.isLive ? "LIVE" : "VOD") (\(self.inAd ? "AD IS PLAYING" : "NO AD PLAYING")). (requiresLinearPlayback = \(self.isLive || self.inAd), canStartPictureInPictureAutomaticallyFromInline = \(self.pipConfig.canStartPictureInPictureAutomaticallyFromInline))") }
71
62
  }
72
63
  }
64
+
65
+ #if os(iOS)
66
+
67
+ func newPipConfiguration() -> PiPConfiguration {
68
+ return PiPConfiguration(retainPresentationModeOnSourceChange: false,
69
+ nativePictureInPicture: true,
70
+ canStartPictureInPictureAutomaticallyFromInline: self.pipConfig.canStartPictureInPictureAutomaticallyFromInline,
71
+ requiresLinearPlayback: self.isLive || self.inAd)
72
+ }
73
+
74
+ #elseif os(tvOS)
75
+
76
+ func newPipConfiguration() -> PiPConfiguration {
77
+ return PiPConfiguration(retainPresentationModeOnSourceChange: false,
78
+ requiresLinearPlayback: self.isLive || self.inAd)
79
+ }
80
+
81
+ #endif
73
82
 
74
83
  private func attachListeners() {
75
84
  guard let player = self.player else {
@@ -80,9 +89,7 @@ class THEOplayerRCTPipControlsManager: NSObject {
80
89
  self.durationChangeListener = player.addEventListener(type: PlayerEventTypes.DURATION_CHANGE) { [weak self] event in
81
90
  if let duration = event.duration {
82
91
  self?.isLive = duration.isInfinite
83
- if #available(iOS 14.0, tvOS 14.0, *) {
84
- self?.updatePipControls()
85
- }
92
+ self?.updatePipControls()
86
93
  }
87
94
  }
88
95
 
@@ -90,9 +97,7 @@ class THEOplayerRCTPipControlsManager: NSObject {
90
97
  self.sourceChangeListener = player.addEventListener(type: PlayerEventTypes.SOURCE_CHANGE) { [weak self] event in
91
98
  self?.isLive = false
92
99
  self?.inAd = false
93
- if #available(iOS 14.0, tvOS 14.0, *) {
94
- self?.updatePipControls()
95
- }
100
+ self?.updatePipControls()
96
101
  }
97
102
 
98
103
  #if (GOOGLE_IMA || GOOGLE_DAI) || canImport(THEOplayerGoogleIMAIntegration)
@@ -5,25 +5,25 @@ import AVKit
5
5
  import THEOplayerSDK
6
6
 
7
7
  struct PipConfig {
8
- var retainPresentationModeOnSourceChange: Bool = false // external config
9
- var canStartPictureInPictureAutomaticallyFromInline: Bool = false // external config
8
+ var canStartPictureInPictureAutomaticallyFromInline: Bool = false
10
9
  }
11
10
 
12
-
13
11
  extension THEOplayerRCTView: AVPictureInPictureControllerDelegate {
14
12
 
15
13
  #if os(iOS)
16
14
 
17
- func playerPipConfiguration() -> PiPConfiguration? {
18
- return PiPConfiguration(retainPresentationModeOnSourceChange: self.pipConfig.retainPresentationModeOnSourceChange,
15
+ func playerPipConfiguration() -> PiPConfiguration {
16
+ return PiPConfiguration(retainPresentationModeOnSourceChange: false,
19
17
  nativePictureInPicture: true,
20
- canStartPictureInPictureAutomaticallyFromInline: self.pipConfig.canStartPictureInPictureAutomaticallyFromInline)
18
+ canStartPictureInPictureAutomaticallyFromInline: self.pipConfig.canStartPictureInPictureAutomaticallyFromInline,
19
+ requiresLinearPlayback: false)
21
20
  }
22
21
 
23
22
  #elseif os(tvOS)
24
23
 
25
- func playerPipConfiguration() -> PiPConfiguration? {
26
- return PiPConfiguration(retainPresentationModeOnSourceChange: self.pipConfig.retainPresentationModeOnSourceChange)
24
+ func playerPipConfiguration() -> PiPConfiguration {
25
+ return PiPConfiguration(retainPresentationModeOnSourceChange: false,
26
+ requiresLinearPlayback: false)
27
27
  }
28
28
 
29
29
  #endif
@@ -41,12 +41,7 @@ extension THEOplayerRCTView: AVPictureInPictureControllerDelegate {
41
41
  @available(tvOS 14.0, *)
42
42
  public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
43
43
  self.presentationModeContext.pipContext = .PIP_CLOSED
44
- self.pipControlsManager.setNativePictureInPictureController(pictureInPictureController)
45
- }
46
-
47
- @available(tvOS 14.0, *)
48
- public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
49
- self.pipControlsManager.setNativePictureInPictureController(nil)
44
+ self.pipControlsManager.willStartPip()
50
45
  }
51
46
 
52
47
  @available(tvOS 14.0, *)
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["PlayerConfiguration.ts"],"sourcesContent":["import type { AdsConfiguration } from '../ads/AdsConfiguration';\nimport type { CastConfiguration } from '../cast/CastConfiguration';\nimport type { MediaControlConfiguration } from '../media/MediaControlConfiguration';\n\nexport interface PlayerConfiguration {\n /**\n * The directory in which the THEOplayer library worker files are located.\n * These worker files are THEOplayer.transmux.*\n *\n * @remarks\n * <br/> - This parameter is required when using a HLS source and has no default.\n *\n * @example\n * `'/lib/theoplayer/'`\n */\n libraryLocation?: string;\n\n /**\n * The muted autoplay policy for web.\n *\n * @remarks\n * <br/> - The muted autoplay policy is impacted by this property and {@link SourceConfiguration.mutedAutoplay}.\n *\n * @defaultValue `'none'`.\n */\n mutedAutoplay?: MutedAutoplayConfiguration;\n\n /**\n * The ads configuration for the player.\n */\n ads?: AdsConfiguration;\n\n /**\n * The cast configuration for the player.\n */\n cast?: CastConfiguration;\n\n /**\n * The configuration of media controls and media sessions across platforms.\n */\n mediaControl?: MediaControlConfiguration;\n\n /**\n * The license for the player\n */\n readonly license?: string;\n\n /**\n * The url to fetch the license for the player\n */\n readonly licenseUrl?: string;\n\n /**\n * Sets whether the native player is chromeless (without UI).\n */\n readonly chromeless?: boolean;\n}\n\n/**\n * The muted autoplay policy of a player for web.\n * <br/> - `'none'`: Disallow muted autoplay. If the player is requested to autoplay while unmuted, and the platform does not support unmuted autoplay, the player will not start playback.\n * <br/> - `'all'`: Allow muted autoplay. If the player is requested to autoplay while unmuted, and the platform supports muted autoplay, the player will start muted playback.\n * <br/> - `'content'`: Allow muted autoplay only for the main content. Disallow muted autoplay for e.g. advertisements. (Not yet supported.)\n *\n * @public\n */\nexport type MutedAutoplayConfiguration = 'none' | 'all' | 'content';\n"],"mappings":""}
1
+ {"version":3,"names":[],"sources":["PlayerConfiguration.ts"],"sourcesContent":["import type { AdsConfiguration } from '../ads/AdsConfiguration';\nimport type { CastConfiguration } from '../cast/CastConfiguration';\nimport type { MediaControlConfiguration } from '../media/MediaControlConfiguration';\nimport type { RetryConfiguration } from '../utils/RetryConfiguration';\n\nexport interface PlayerConfiguration {\n /**\n * The directory in which the THEOplayer library worker files are located.\n * These worker files are THEOplayer.transmux.*\n *\n * @remarks\n * <br/> - This parameter is required when using a HLS source and has no default.\n *\n * @example\n * `'/lib/theoplayer/'`\n */\n libraryLocation?: string;\n\n /**\n * The muted autoplay policy for web.\n *\n * @remarks\n * <br/> - The muted autoplay policy is impacted by this property and {@link SourceConfiguration.mutedAutoplay}.\n *\n * @defaultValue `'none'`.\n */\n mutedAutoplay?: MutedAutoplayConfiguration;\n\n /**\n * The ads configuration for the player.\n */\n ads?: AdsConfiguration;\n\n /**\n * The cast configuration for the player.\n */\n cast?: CastConfiguration;\n\n /**\n * The configuration of media controls and media sessions across platforms.\n */\n mediaControl?: MediaControlConfiguration;\n\n /**\n * The license for the player\n */\n readonly license?: string;\n\n /**\n * The url to fetch the license for the player\n */\n readonly licenseUrl?: string;\n\n /**\n * Sets whether the native player is chromeless (without UI).\n */\n readonly chromeless?: boolean;\n\n /**\n * The retry configuration for the player.\n *\n * @remarks\n * <br/> - This parameter only applies to Web and Android platforms.\n */\n readonly retryConfiguration?: RetryConfiguration;\n}\n\n/**\n * The muted autoplay policy of a player for web.\n * <br/> - `'none'`: Disallow muted autoplay. If the player is requested to autoplay while unmuted, and the platform does not support unmuted autoplay, the player will not start playback.\n * <br/> - `'all'`: Allow muted autoplay. If the player is requested to autoplay while unmuted, and the platform supports muted autoplay, the player will start muted playback.\n * <br/> - `'content'`: Allow muted autoplay only for the main content. Disallow muted autoplay for e.g. advertisements. (Not yet supported.)\n *\n * @public\n */\nexport type MutedAutoplayConfiguration = 'none' | 'all' | 'content';\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=RetryConfiguration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["RetryConfiguration.ts"],"sourcesContent":["/**\n * Object containing values used for the player's retry mechanisms.\n */\nexport interface RetryConfiguration {\n /**\n * The maximum amount of retries before the player throws a fatal error.\n * Defaults to `Infinity`.\n */\n readonly maxRetries?: number;\n\n /**\n * The initial delay in milliseconds before a retry request occurs.\n * Exponential backoff will be applied on this value.\n * Defaults to `200`.\n */\n readonly minimumBackoff?: number;\n\n /**\n * The maximum amount of delay in milliseconds between retry requests.\n * Defaults to `30000`.\n */\n readonly maximumBackoff?: number;\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["PlayerConfiguration.ts"],"sourcesContent":["import type { AdsConfiguration } from '../ads/AdsConfiguration';\nimport type { CastConfiguration } from '../cast/CastConfiguration';\nimport type { MediaControlConfiguration } from '../media/MediaControlConfiguration';\n\nexport interface PlayerConfiguration {\n /**\n * The directory in which the THEOplayer library worker files are located.\n * These worker files are THEOplayer.transmux.*\n *\n * @remarks\n * <br/> - This parameter is required when using a HLS source and has no default.\n *\n * @example\n * `'/lib/theoplayer/'`\n */\n libraryLocation?: string;\n\n /**\n * The muted autoplay policy for web.\n *\n * @remarks\n * <br/> - The muted autoplay policy is impacted by this property and {@link SourceConfiguration.mutedAutoplay}.\n *\n * @defaultValue `'none'`.\n */\n mutedAutoplay?: MutedAutoplayConfiguration;\n\n /**\n * The ads configuration for the player.\n */\n ads?: AdsConfiguration;\n\n /**\n * The cast configuration for the player.\n */\n cast?: CastConfiguration;\n\n /**\n * The configuration of media controls and media sessions across platforms.\n */\n mediaControl?: MediaControlConfiguration;\n\n /**\n * The license for the player\n */\n readonly license?: string;\n\n /**\n * The url to fetch the license for the player\n */\n readonly licenseUrl?: string;\n\n /**\n * Sets whether the native player is chromeless (without UI).\n */\n readonly chromeless?: boolean;\n}\n\n/**\n * The muted autoplay policy of a player for web.\n * <br/> - `'none'`: Disallow muted autoplay. If the player is requested to autoplay while unmuted, and the platform does not support unmuted autoplay, the player will not start playback.\n * <br/> - `'all'`: Allow muted autoplay. If the player is requested to autoplay while unmuted, and the platform supports muted autoplay, the player will start muted playback.\n * <br/> - `'content'`: Allow muted autoplay only for the main content. Disallow muted autoplay for e.g. advertisements. (Not yet supported.)\n *\n * @public\n */\nexport type MutedAutoplayConfiguration = 'none' | 'all' | 'content';\n"],"mappings":""}
1
+ {"version":3,"names":[],"sources":["PlayerConfiguration.ts"],"sourcesContent":["import type { AdsConfiguration } from '../ads/AdsConfiguration';\nimport type { CastConfiguration } from '../cast/CastConfiguration';\nimport type { MediaControlConfiguration } from '../media/MediaControlConfiguration';\nimport type { RetryConfiguration } from '../utils/RetryConfiguration';\n\nexport interface PlayerConfiguration {\n /**\n * The directory in which the THEOplayer library worker files are located.\n * These worker files are THEOplayer.transmux.*\n *\n * @remarks\n * <br/> - This parameter is required when using a HLS source and has no default.\n *\n * @example\n * `'/lib/theoplayer/'`\n */\n libraryLocation?: string;\n\n /**\n * The muted autoplay policy for web.\n *\n * @remarks\n * <br/> - The muted autoplay policy is impacted by this property and {@link SourceConfiguration.mutedAutoplay}.\n *\n * @defaultValue `'none'`.\n */\n mutedAutoplay?: MutedAutoplayConfiguration;\n\n /**\n * The ads configuration for the player.\n */\n ads?: AdsConfiguration;\n\n /**\n * The cast configuration for the player.\n */\n cast?: CastConfiguration;\n\n /**\n * The configuration of media controls and media sessions across platforms.\n */\n mediaControl?: MediaControlConfiguration;\n\n /**\n * The license for the player\n */\n readonly license?: string;\n\n /**\n * The url to fetch the license for the player\n */\n readonly licenseUrl?: string;\n\n /**\n * Sets whether the native player is chromeless (without UI).\n */\n readonly chromeless?: boolean;\n\n /**\n * The retry configuration for the player.\n *\n * @remarks\n * <br/> - This parameter only applies to Web and Android platforms.\n */\n readonly retryConfiguration?: RetryConfiguration;\n}\n\n/**\n * The muted autoplay policy of a player for web.\n * <br/> - `'none'`: Disallow muted autoplay. If the player is requested to autoplay while unmuted, and the platform does not support unmuted autoplay, the player will not start playback.\n * <br/> - `'all'`: Allow muted autoplay. If the player is requested to autoplay while unmuted, and the platform supports muted autoplay, the player will start muted playback.\n * <br/> - `'content'`: Allow muted autoplay only for the main content. Disallow muted autoplay for e.g. advertisements. (Not yet supported.)\n *\n * @public\n */\nexport type MutedAutoplayConfiguration = 'none' | 'all' | 'content';\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+
2
+ //# sourceMappingURL=RetryConfiguration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["RetryConfiguration.ts"],"sourcesContent":["/**\n * Object containing values used for the player's retry mechanisms.\n */\nexport interface RetryConfiguration {\n /**\n * The maximum amount of retries before the player throws a fatal error.\n * Defaults to `Infinity`.\n */\n readonly maxRetries?: number;\n\n /**\n * The initial delay in milliseconds before a retry request occurs.\n * Exponential backoff will be applied on this value.\n * Defaults to `200`.\n */\n readonly minimumBackoff?: number;\n\n /**\n * The maximum amount of delay in milliseconds between retry requests.\n * Defaults to `30000`.\n */\n readonly maximumBackoff?: number;\n}\n"],"mappings":""}
@@ -1,6 +1,7 @@
1
1
  import type { AdsConfiguration } from '../ads/AdsConfiguration';
2
2
  import type { CastConfiguration } from '../cast/CastConfiguration';
3
3
  import type { MediaControlConfiguration } from '../media/MediaControlConfiguration';
4
+ import type { RetryConfiguration } from '../utils/RetryConfiguration';
4
5
  export interface PlayerConfiguration {
5
6
  /**
6
7
  * The directory in which the THEOplayer library worker files are located.
@@ -46,6 +47,13 @@ export interface PlayerConfiguration {
46
47
  * Sets whether the native player is chromeless (without UI).
47
48
  */
48
49
  readonly chromeless?: boolean;
50
+ /**
51
+ * The retry configuration for the player.
52
+ *
53
+ * @remarks
54
+ * <br/> - This parameter only applies to Web and Android platforms.
55
+ */
56
+ readonly retryConfiguration?: RetryConfiguration;
49
57
  }
50
58
  /**
51
59
  * The muted autoplay policy of a player for web.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Object containing values used for the player's retry mechanisms.
3
+ */
4
+ export interface RetryConfiguration {
5
+ /**
6
+ * The maximum amount of retries before the player throws a fatal error.
7
+ * Defaults to `Infinity`.
8
+ */
9
+ readonly maxRetries?: number;
10
+ /**
11
+ * The initial delay in milliseconds before a retry request occurs.
12
+ * Exponential backoff will be applied on this value.
13
+ * Defaults to `200`.
14
+ */
15
+ readonly minimumBackoff?: number;
16
+ /**
17
+ * The maximum amount of delay in milliseconds between retry requests.
18
+ * Defaults to `30000`.
19
+ */
20
+ readonly maximumBackoff?: number;
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-theoplayer",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "A THEOplayer video component for react-native.",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -116,7 +116,7 @@
116
116
  "react-native-svg": "^13.8.0",
117
117
  "react-native-svg-web": "^1.0.0",
118
118
  "react-native-url-polyfill": "^1.3.0",
119
- "theoplayer": "^4.6.0",
119
+ "theoplayer": ">=5.0.1",
120
120
  "url-polyfill": "^1.1.12"
121
121
  }
122
122
  }
@@ -2,7 +2,7 @@ require "json"
2
2
 
3
3
  package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
4
  theoconfigpath = File.join(__dir__ + "/../../", "react-native-theoplayer.json")
5
- if File.exists?(theoconfigpath)
5
+ if File.exist?(theoconfigpath)
6
6
  theoconfig = JSON.parse(File.read(theoconfigpath))
7
7
  theofeatures = theoconfig["ios"]["features"]
8
8
  else
@@ -36,7 +36,7 @@ Pod::Spec.new do |s|
36
36
  }
37
37
  else
38
38
  puts "Using THEOplayer-core SDK"
39
- s.dependency "THEOplayerSDK-core"
39
+ s.dependency "THEOplayerSDK-core", "~> 5.0.1"
40
40
  if theofeatures.include?("GOOGLE_IMA")
41
41
  puts "Adding THEOplayer-Integration-GoogleIMA"
42
42
  s.dependency "THEOplayer-Integration-GoogleIMA"
@@ -44,7 +44,6 @@ Pod::Spec.new do |s|
44
44
  if theofeatures.include?("CHROMECAST")
45
45
  puts "Adding THEOplayer-Integration-GoogleCast"
46
46
  s.ios.dependency "THEOplayer-Integration-GoogleCast"
47
- s.ios.dependency "google-cast-sdk-dynamic-xcframework-no-bluetooth"
48
47
  end
49
48
  end
50
49
 
@@ -1,6 +1,7 @@
1
1
  import type { AdsConfiguration } from '../ads/AdsConfiguration';
2
2
  import type { CastConfiguration } from '../cast/CastConfiguration';
3
3
  import type { MediaControlConfiguration } from '../media/MediaControlConfiguration';
4
+ import type { RetryConfiguration } from '../utils/RetryConfiguration';
4
5
 
5
6
  export interface PlayerConfiguration {
6
7
  /**
@@ -54,6 +55,14 @@ export interface PlayerConfiguration {
54
55
  * Sets whether the native player is chromeless (without UI).
55
56
  */
56
57
  readonly chromeless?: boolean;
58
+
59
+ /**
60
+ * The retry configuration for the player.
61
+ *
62
+ * @remarks
63
+ * <br/> - This parameter only applies to Web and Android platforms.
64
+ */
65
+ readonly retryConfiguration?: RetryConfiguration;
57
66
  }
58
67
 
59
68
  /**
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Object containing values used for the player's retry mechanisms.
3
+ */
4
+ export interface RetryConfiguration {
5
+ /**
6
+ * The maximum amount of retries before the player throws a fatal error.
7
+ * Defaults to `Infinity`.
8
+ */
9
+ readonly maxRetries?: number;
10
+
11
+ /**
12
+ * The initial delay in milliseconds before a retry request occurs.
13
+ * Exponential backoff will be applied on this value.
14
+ * Defaults to `200`.
15
+ */
16
+ readonly minimumBackoff?: number;
17
+
18
+ /**
19
+ * The maximum amount of delay in milliseconds between retry requests.
20
+ * Defaults to `30000`.
21
+ */
22
+ readonly maximumBackoff?: number;
23
+ }