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.
- package/CHANGELOG.md +22 -1
- package/android/build.gradle +3 -1
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/theoplayer/PlayerConfigAdapter.kt +28 -0
- package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +1 -1
- package/android/src/main/java/com/theoplayer/presentation/PipUtils.kt +244 -0
- package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +22 -55
- package/android/src/main/res/drawable/ic_fast_forward.xml +9 -0
- package/android/src/main/res/drawable/ic_rewind.xml +9 -0
- package/android/src/main/res/values/strings.xml +8 -0
- package/ios/THEOplayerRCTMainEventHandler.swift +1 -1
- package/ios/THEOplayerRCTPlayerAPI.swift +2 -3
- package/ios/THEOplayerRCTView.swift +7 -2
- package/ios/pip/THEOplayerRCTPipControlsManager.swift +33 -28
- package/ios/pip/THEOplayerRCTView+PipConfig.swift +9 -14
- package/lib/commonjs/api/config/PlayerConfiguration.js.map +1 -1
- package/lib/commonjs/api/utils/RetryConfiguration.js +2 -0
- package/lib/commonjs/api/utils/RetryConfiguration.js.map +1 -0
- package/lib/module/api/config/PlayerConfiguration.js.map +1 -1
- package/lib/module/api/utils/RetryConfiguration.js +2 -0
- package/lib/module/api/utils/RetryConfiguration.js.map +1 -0
- package/lib/typescript/api/config/PlayerConfiguration.d.ts +8 -0
- package/lib/typescript/api/utils/RetryConfiguration.d.ts +21 -0
- package/package.json +2 -2
- package/react-native-theoplayer.podspec +2 -3
- package/src/api/config/PlayerConfiguration.ts +9 -0
- 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
|
-
## [
|
|
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
|
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
51
|
+
onEnterPip()
|
|
61
52
|
} else {
|
|
62
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
var
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
self.
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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.
|
|
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 @@
|
|
|
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 @@
|
|
|
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.
|
|
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": "
|
|
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.
|
|
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
|
+
}
|