react-native-theoplayer 10.3.0 → 10.4.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 +25 -1
- package/README.md +2 -2
- package/android/build.gradle +1 -1
- package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +38 -9
- package/android/src/main/java/com/theoplayer/media/MediaPlaybackService.kt +17 -34
- package/android/src/main/java/com/theoplayer/media/MediaSessionConfig.kt +10 -0
- package/android/src/main/java/com/theoplayer/media/MediaSessionConfigAdapter.kt +8 -0
- package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +3 -0
- package/android/src/main/java/com/theoplayer/theoads/THEOadsEventAdapter.kt +1 -6
- package/ios/THEOplayerRCTMainEventHandler.swift +0 -1
- package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +5 -0
- package/ios/backgroundAudio/THEOplayerRCTRemoteCommandsManager.swift +26 -15
- package/ios/backgroundAudio/THEOplayerRCTView+MediaControlConfig.swift +8 -0
- package/ios/theoAds/THEOplayerRCTSourceDescriptionBuilder+TheoAds.swift +2 -0
- package/lib/commonjs/api/source/SourceDescription.js.map +1 -1
- package/lib/commonjs/internal/THEOplayerView.js +4 -1
- package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
- package/lib/commonjs/internal/adapter/WebEventForwarder.js +4 -1
- package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
- package/lib/commonjs/internal/adapter/event/native/NativeTheoAdsEvent.js +8 -2
- package/lib/commonjs/internal/adapter/event/native/NativeTheoAdsEvent.js.map +1 -1
- package/lib/commonjs/manifest.json +1 -1
- package/lib/module/api/source/SourceDescription.js.map +1 -1
- package/lib/module/internal/THEOplayerView.js +4 -1
- package/lib/module/internal/THEOplayerView.js.map +1 -1
- package/lib/module/internal/adapter/WebEventForwarder.js +5 -2
- package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
- package/lib/module/internal/adapter/event/native/NativeTheoAdsEvent.js +8 -2
- package/lib/module/internal/adapter/event/native/NativeTheoAdsEvent.js.map +1 -1
- package/lib/module/manifest.json +1 -1
- package/lib/typescript/api/media/MediaControlConfiguration.d.ts +17 -0
- package/lib/typescript/api/media/MediaControlConfiguration.d.ts.map +1 -1
- package/lib/typescript/api/source/SourceDescription.d.ts +6 -6
- package/lib/typescript/api/source/SourceDescription.d.ts.map +1 -1
- package/lib/typescript/internal/THEOplayerView.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/WebEventForwarder.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/event/native/NativeTheoAdsEvent.d.ts +1 -1
- package/lib/typescript/internal/adapter/event/native/NativeTheoAdsEvent.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/api/media/MediaControlConfiguration.ts +19 -0
- package/src/api/source/SourceDescription.ts +7 -7
- package/src/internal/THEOplayerView.tsx +4 -1
- package/src/internal/adapter/WebEventForwarder.ts +45 -32
- package/src/internal/adapter/event/native/NativeTheoAdsEvent.ts +9 -4
- package/src/manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,11 +5,26 @@ 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.1.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [10.4.0] - 25-11-13
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Fixed an issue on Android, where the app could crash when a `THEOads` error event was dispatched.
|
|
13
|
+
- Fixed an issue on Web, where the contents of the `reason` property of the THEOads `intenttofallback` event did not conform to its declared `PlayerError` type.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Pass `streamActivityMonitorId` property for `THEOAdDescription` on iOS and Android.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Upgraded example app to React-Native v0.82.
|
|
22
|
+
|
|
8
23
|
## [10.3.0] - 25-10-27
|
|
9
24
|
|
|
10
25
|
### Fixed
|
|
11
26
|
|
|
12
|
-
- Fixed an issue on
|
|
27
|
+
- Fixed an issue on Web, where adbreak related AdEvents did no longer contain the adBreak info.
|
|
13
28
|
|
|
14
29
|
### Changed
|
|
15
30
|
|
|
@@ -30,6 +45,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
30
45
|
|
|
31
46
|
## [10.2.0] - 25-10-20
|
|
32
47
|
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- React Native THEOplayer now supports [React Compiler](https://react.dev/learn/react-compiler).
|
|
51
|
+
- For Expo users: refer to the [Expo docs](https://docs.expo.dev/guides/react-compiler/) to set up React Compiler in your app.
|
|
52
|
+
|
|
33
53
|
### Fixed
|
|
34
54
|
|
|
35
55
|
- Fixed an issue on iOS where the player crashed when it was destroyed while in fullscreen.
|
|
@@ -40,6 +60,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
40
60
|
|
|
41
61
|
- Added `THEOplayer.theoLive` and deprecated `THEOplayer.theolive`, to be consistent with the THEOplayer SDKs for other platforms.
|
|
42
62
|
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- Moved the `hlsDateRange` property from `SourceConfiguration` to its correct location in `TypedSource`.
|
|
66
|
+
|
|
43
67
|
## [10.1.0] - 25-10-06
|
|
44
68
|
|
|
45
69
|
### Added
|
package/README.md
CHANGED
|
@@ -73,8 +73,8 @@ please reach out to us for support.
|
|
|
73
73
|
<tbody>
|
|
74
74
|
<tr>
|
|
75
75
|
<td><strong>Streaming</strong></td>
|
|
76
|
-
<td colspan="2">MPEG-DASH (fmp4, CMAF), HLS (TS, CMAF), Progressive MP4, MP3</td>
|
|
77
|
-
<td>HLS (TS, CMAF), Progressive MP4, MP3</td>
|
|
76
|
+
<td colspan="2">MPEG-DASH (fmp4, CMAF), HLS (TS, CMAF), Progressive MP4, MP3, M4A</td>
|
|
77
|
+
<td>HLS (TS, CMAF), Progressive MP4, MP3, M4A</td>
|
|
78
78
|
</tr>
|
|
79
79
|
<tr>
|
|
80
80
|
<td><strong>Content Protection</strong></td>
|
package/android/build.gradle
CHANGED
|
@@ -152,7 +152,7 @@ dependencies {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
//noinspection GradleDynamicVersion
|
|
155
|
-
implementation("com.facebook.react:react-
|
|
155
|
+
implementation("com.facebook.react:react-android:+")
|
|
156
156
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
|
157
157
|
implementation("androidx.appcompat:appcompat:$appcompatVersion")
|
|
158
158
|
implementation("androidx.core:core-ktx:$corektxVersion")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
distributionBase=GRADLE_USER_HOME
|
|
2
2
|
distributionPath=wrapper/dists
|
|
3
|
-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.
|
|
3
|
+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
|
4
4
|
zipStoreBase=GRADLE_USER_HOME
|
|
5
5
|
zipStorePath=wrapper/dists
|
|
@@ -31,6 +31,7 @@ import com.theoplayer.android.api.millicast.MillicastIntegrationFactory
|
|
|
31
31
|
import com.theoplayer.android.api.player.Player
|
|
32
32
|
import com.theoplayer.android.api.player.RenderingTarget
|
|
33
33
|
import com.theoplayer.android.connector.mediasession.MediaSessionConnector
|
|
34
|
+
import com.theoplayer.android.connector.mediasession.MediaSessionListener
|
|
34
35
|
import com.theoplayer.audio.AudioBecomingNoisyManager
|
|
35
36
|
import com.theoplayer.audio.AudioFocusManager
|
|
36
37
|
import com.theoplayer.audio.BackgroundAudioConfig
|
|
@@ -50,6 +51,11 @@ private const val ALLOWED_PLAYBACK_ACTIONS = (
|
|
|
50
51
|
PlaybackStateCompat.ACTION_REWIND or
|
|
51
52
|
PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED)
|
|
52
53
|
|
|
54
|
+
private const val ALLOWED_PLAY_PAUSE_ACTIONS = (
|
|
55
|
+
PlaybackStateCompat.ACTION_PLAY_PAUSE or
|
|
56
|
+
PlaybackStateCompat.ACTION_PLAY or
|
|
57
|
+
PlaybackStateCompat.ACTION_PAUSE)
|
|
58
|
+
|
|
53
59
|
class ReactTHEOplayerContext private constructor(
|
|
54
60
|
private val reactContext: ThemedReactContext,
|
|
55
61
|
private val configAdapter: PlayerConfigAdapter
|
|
@@ -82,14 +88,13 @@ class ReactTHEOplayerContext private constructor(
|
|
|
82
88
|
get() = playerView.player
|
|
83
89
|
|
|
84
90
|
private val uiModeManager by lazy {
|
|
85
|
-
|
|
91
|
+
reactContext.getSystemService(Context.UI_MODE_SERVICE) as? UiModeManager
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
var daiIntegration: GoogleDaiIntegration? = null
|
|
89
95
|
var imaIntegration: GoogleImaIntegration? = null
|
|
90
96
|
private var theoAdsIntegration: TheoAdsIntegration? = null
|
|
91
97
|
var castIntegration: CastIntegration? = null
|
|
92
|
-
@Suppress("UnstableApiUsage")
|
|
93
98
|
var wasPlayingOnHostPause: Boolean = false
|
|
94
99
|
private var isHostPaused: Boolean = false
|
|
95
100
|
private var millicastIntegration: MillicastIntegration? = null
|
|
@@ -131,6 +136,19 @@ class ReactTHEOplayerContext private constructor(
|
|
|
131
136
|
}
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
private val mediaSessionListener = object : MediaSessionListener() {
|
|
140
|
+
override fun onStop() {
|
|
141
|
+
binder?.stopForegroundService()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
override fun onPlay() {
|
|
145
|
+
// Optionally seek to live, if configured.
|
|
146
|
+
if (mediaSessionConfig.seekToLiveOnResume && player.duration.isInfinite()) {
|
|
147
|
+
player.currentTime = Double.POSITIVE_INFINITY
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
134
152
|
private fun applyBackgroundPlaybackConfig(
|
|
135
153
|
config: BackgroundAudioConfig,
|
|
136
154
|
prevConfig: BackgroundAudioConfig?
|
|
@@ -167,10 +185,12 @@ class ReactTHEOplayerContext private constructor(
|
|
|
167
185
|
val isLive = player.duration.isInfinite()
|
|
168
186
|
val isInAd = player.ads.isPlaying
|
|
169
187
|
mediaSessionConnector?.enabledPlaybackActions = when {
|
|
170
|
-
|
|
171
|
-
isLive &&
|
|
172
|
-
|
|
173
|
-
|
|
188
|
+
// Allow trick-play for live events if configured
|
|
189
|
+
isLive && mediaSessionConfig.allowLivePlayPause -> ALLOWED_PLAY_PAUSE_ACTIONS
|
|
190
|
+
isLive && !mediaSessionConfig.allowLivePlayPause -> 0
|
|
191
|
+
// Do not allow playback actions during ad play-out
|
|
192
|
+
isInAd -> 0
|
|
193
|
+
|
|
174
194
|
else -> ALLOWED_PLAYBACK_ACTIONS
|
|
175
195
|
}
|
|
176
196
|
}
|
|
@@ -263,9 +283,13 @@ class ReactTHEOplayerContext private constructor(
|
|
|
263
283
|
}
|
|
264
284
|
}
|
|
265
285
|
|
|
266
|
-
private fun applyMediaSessionConfig(
|
|
286
|
+
private fun applyMediaSessionConfig(
|
|
287
|
+
connector: MediaSessionConnector?,
|
|
288
|
+
config: MediaSessionConfig
|
|
289
|
+
) {
|
|
267
290
|
connector?.apply {
|
|
268
291
|
debug = BuildConfig.LOG_MEDIASESSION_EVENTS
|
|
292
|
+
removeListener(mediaSessionListener)
|
|
269
293
|
|
|
270
294
|
player = this@ReactTHEOplayerContext.player
|
|
271
295
|
|
|
@@ -279,10 +303,15 @@ class ReactTHEOplayerContext private constructor(
|
|
|
279
303
|
// Pass metadata from source description
|
|
280
304
|
setMediaSessionMetadata(player?.source)
|
|
281
305
|
|
|
306
|
+
// Do not let MediaButtons restart the player when media session is not active.
|
|
307
|
+
// https://developer.android.com/media/legacy/media-buttons#restarting-inactive-mediasessions
|
|
308
|
+
this.mediaSession.setMediaButtonReceiver(null)
|
|
309
|
+
|
|
282
310
|
// Install a queue navigator, but only if we want to handle skip buttons.
|
|
283
311
|
if (mediaSessionConfig.convertSkipToSeek) {
|
|
284
312
|
queueNavigator = MediaQueueNavigator(mediaSessionConfig)
|
|
285
313
|
}
|
|
314
|
+
addListener(mediaSessionListener)
|
|
286
315
|
}
|
|
287
316
|
}
|
|
288
317
|
|
|
@@ -339,8 +368,8 @@ class ReactTHEOplayerContext private constructor(
|
|
|
339
368
|
if (BuildConfig.EXTENSION_MILLICAST) {
|
|
340
369
|
millicastIntegration = MillicastIntegrationFactory.createMillicastIntegration()
|
|
341
370
|
.also {
|
|
342
|
-
|
|
343
|
-
|
|
371
|
+
playerView.player.addIntegration(it)
|
|
372
|
+
}
|
|
344
373
|
}
|
|
345
374
|
} catch (e: Exception) {
|
|
346
375
|
Log.w(TAG, "Failed to configure Millicast integration ${e.message}")
|
|
@@ -13,11 +13,9 @@ import android.util.Log
|
|
|
13
13
|
import androidx.core.app.ServiceCompat
|
|
14
14
|
import androidx.core.content.ContextCompat
|
|
15
15
|
import androidx.media.session.MediaButtonReceiver
|
|
16
|
-
import com.theoplayer.BuildConfig
|
|
17
16
|
import com.theoplayer.ReactTHEOplayerContext
|
|
18
17
|
import com.theoplayer.android.api.player.Player
|
|
19
18
|
import com.theoplayer.android.connector.mediasession.MediaSessionConnector
|
|
20
|
-
import com.theoplayer.android.connector.mediasession.MediaSessionListener
|
|
21
19
|
|
|
22
20
|
private const val STOP_SERVICE_IF_APP_REMOVED = true
|
|
23
21
|
|
|
@@ -39,7 +37,7 @@ class MediaPlaybackService : Service() {
|
|
|
39
37
|
|
|
40
38
|
private lateinit var mediaSessionConnector: MediaSessionConnector
|
|
41
39
|
private val mediaSession: MediaSessionCompat
|
|
42
|
-
get() =
|
|
40
|
+
get() = mediaSessionConnector.mediaSession
|
|
43
41
|
|
|
44
42
|
inner class MediaPlaybackBinder : Binder() {
|
|
45
43
|
private val service: MediaPlaybackService
|
|
@@ -119,7 +117,6 @@ class MediaPlaybackService : Service() {
|
|
|
119
117
|
|
|
120
118
|
override fun onDestroy() {
|
|
121
119
|
super.onDestroy()
|
|
122
|
-
removeListeners()
|
|
123
120
|
mediaSessionConnector.destroy()
|
|
124
121
|
playerContext = null
|
|
125
122
|
}
|
|
@@ -147,16 +144,7 @@ class MediaPlaybackService : Service() {
|
|
|
147
144
|
}
|
|
148
145
|
|
|
149
146
|
// Create a MediaSessionConnector.
|
|
150
|
-
mediaSessionConnector = MediaSessionConnector(mediaSession)
|
|
151
|
-
debug = BuildConfig.LOG_MEDIASESSION_EVENTS
|
|
152
|
-
|
|
153
|
-
// Set mediaSession active
|
|
154
|
-
setActive(BuildConfig.EXTENSION_MEDIASESSION)
|
|
155
|
-
|
|
156
|
-
// Do not let MediaButtons restart the player when media session is not active.
|
|
157
|
-
// https://developer.android.com/media/legacy/media-buttons#restarting-inactive-mediasessions
|
|
158
|
-
mediaSession.setMediaButtonReceiver(null)
|
|
159
|
-
}
|
|
147
|
+
mediaSessionConnector = MediaSessionConnector(mediaSession)
|
|
160
148
|
}
|
|
161
149
|
|
|
162
150
|
private fun stopForegroundService() {
|
|
@@ -165,28 +153,10 @@ class MediaPlaybackService : Service() {
|
|
|
165
153
|
}
|
|
166
154
|
|
|
167
155
|
private fun connectPlayerContext(playerContext: ReactTHEOplayerContext) {
|
|
168
|
-
if (this.playerContext != null) {
|
|
169
|
-
removeListeners()
|
|
170
|
-
}
|
|
171
156
|
this.playerContext = playerContext
|
|
172
|
-
addListeners()
|
|
173
157
|
updateNotification()
|
|
174
158
|
}
|
|
175
159
|
|
|
176
|
-
private val mediaSessionListener = object : MediaSessionListener() {
|
|
177
|
-
override fun onStop() {
|
|
178
|
-
stopForegroundService()
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private fun addListeners() {
|
|
183
|
-
mediaSessionConnector.addListener(mediaSessionListener)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private fun removeListeners() {
|
|
187
|
-
mediaSessionConnector.removeListener(mediaSessionListener)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
160
|
private fun updateNotification() {
|
|
191
161
|
val player = player
|
|
192
162
|
when {
|
|
@@ -204,9 +174,17 @@ class MediaPlaybackService : Service() {
|
|
|
204
174
|
PlaybackStateCompat.STATE_PAUSED -> {
|
|
205
175
|
// Fetch large icon asynchronously
|
|
206
176
|
fetchImageFromMetadata(player?.source) { largeIcon ->
|
|
207
|
-
notificationManager.notify(
|
|
177
|
+
notificationManager.notify(
|
|
178
|
+
NOTIFICATION_ID,
|
|
179
|
+
notificationBuilder.build(
|
|
180
|
+
playbackState,
|
|
181
|
+
largeIcon,
|
|
182
|
+
mediaSessionConfig.mediaSessionEnabled
|
|
183
|
+
)
|
|
184
|
+
)
|
|
208
185
|
}
|
|
209
186
|
}
|
|
187
|
+
|
|
210
188
|
PlaybackStateCompat.STATE_PLAYING -> {
|
|
211
189
|
// When a service runs in the foreground, it must display a notification, ideally
|
|
212
190
|
// with one or more transport controls. The notification should also include useful
|
|
@@ -220,6 +198,7 @@ class MediaPlaybackService : Service() {
|
|
|
220
198
|
startForegroundWithPlaybackState(playbackState, largeIcon)
|
|
221
199
|
}
|
|
222
200
|
}
|
|
201
|
+
|
|
223
202
|
PlaybackStateCompat.STATE_STOPPED -> {
|
|
224
203
|
// Remove this service from foreground state, allowing it to be killed if more memory is
|
|
225
204
|
// needed. This does not stop the service from running (for that you use stopSelf()
|
|
@@ -227,13 +206,17 @@ class MediaPlaybackService : Service() {
|
|
|
227
206
|
// Also remove the notification.
|
|
228
207
|
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
|
229
208
|
}
|
|
209
|
+
|
|
230
210
|
else -> {
|
|
231
211
|
// Ignore
|
|
232
212
|
}
|
|
233
213
|
}
|
|
234
214
|
}
|
|
235
215
|
|
|
236
|
-
private fun startForegroundWithPlaybackState(
|
|
216
|
+
private fun startForegroundWithPlaybackState(
|
|
217
|
+
@PlaybackStateCompat.State playbackState: Int,
|
|
218
|
+
largeIcon: Bitmap? = null
|
|
219
|
+
) {
|
|
237
220
|
try {
|
|
238
221
|
ServiceCompat.startForeground(
|
|
239
222
|
this,
|
|
@@ -20,4 +20,14 @@ data class MediaSessionConfig (
|
|
|
20
20
|
* Whether "skip track" events should be handled the same as "fast-forward/rewind".
|
|
21
21
|
*/
|
|
22
22
|
var convertSkipToSeek: Boolean = false,
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether to allow play/pause of live assets.
|
|
26
|
+
*/
|
|
27
|
+
var allowLivePlayPause: Boolean = false,
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether to seek to live when resuming a live stream.
|
|
31
|
+
*/
|
|
32
|
+
var seekToLiveOnResume: Boolean = false,
|
|
23
33
|
)
|
|
@@ -7,6 +7,8 @@ object MediaSessionConfigAdapter {
|
|
|
7
7
|
private const val PROP_SKIP_FORWARD_INTERVAL = "skipForwardInterval"
|
|
8
8
|
private const val PROP_SKIP_BACKWARD_INTERVAL = "skipBackwardInterval"
|
|
9
9
|
private const val PROP_CONVERT_SKIP = "convertSkipToSeek"
|
|
10
|
+
private const val PROP_ALLOW_LIVE_PLAY_PAUSE = "allowLivePlayPause"
|
|
11
|
+
private const val PROP_SEEK_TO_LIVE_RESUME = "seekToLiveOnResume"
|
|
10
12
|
|
|
11
13
|
fun fromProps(props: ReadableMap?): MediaSessionConfig {
|
|
12
14
|
return MediaSessionConfig().apply {
|
|
@@ -22,6 +24,12 @@ object MediaSessionConfigAdapter {
|
|
|
22
24
|
if (props?.hasKey(PROP_CONVERT_SKIP) == true) {
|
|
23
25
|
convertSkipToSeek = props.getBoolean(PROP_CONVERT_SKIP)
|
|
24
26
|
}
|
|
27
|
+
if (props?.hasKey(PROP_ALLOW_LIVE_PLAY_PAUSE) == true) {
|
|
28
|
+
allowLivePlayPause = props.getBoolean(PROP_ALLOW_LIVE_PLAY_PAUSE)
|
|
29
|
+
}
|
|
30
|
+
if (props?.hasKey(PROP_SEEK_TO_LIVE_RESUME) == true) {
|
|
31
|
+
seekToLiveOnResume = props.getBoolean(PROP_SEEK_TO_LIVE_RESUME)
|
|
32
|
+
}
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
}
|
|
@@ -69,6 +69,7 @@ private const val PROP_USE_ID3 = "useId3"
|
|
|
69
69
|
private const val PROP_RETRIEVE_POD_ID_URI = "retrievePodIdURI"
|
|
70
70
|
private const val PROP_INITIALIZATION_DELAY = "initializationDelay"
|
|
71
71
|
private const val PROP_SSE_ENDPOINT = "sseEndpoint"
|
|
72
|
+
private const val PROP_STREAM_ACTIVITY_MONITOR_ID = "streamActivityMonitorId"
|
|
72
73
|
private const val PROP_LATENCY_CONFIGURATION = "latencyConfiguration"
|
|
73
74
|
|
|
74
75
|
private const val ERROR_IMA_NOT_ENABLED = "Google IMA support not enabled."
|
|
@@ -272,6 +273,7 @@ class SourceAdapter {
|
|
|
272
273
|
fileName.endsWith(".m3u8") -> return SourceType.HLSX
|
|
273
274
|
fileName.endsWith(".mp4") -> return SourceType.MP4
|
|
274
275
|
fileName.endsWith(".mp3") -> return SourceType.MP3
|
|
276
|
+
fileName.endsWith(".m4a") -> return SourceType.M4A
|
|
275
277
|
}
|
|
276
278
|
}
|
|
277
279
|
}
|
|
@@ -334,6 +336,7 @@ class SourceAdapter {
|
|
|
334
336
|
retrievePodIdURI = jsonAdDescription.optString(PROP_RETRIEVE_POD_ID_URI).takeIf { it.isNotEmpty() },
|
|
335
337
|
initializationDelay = jsonAdDescription.optDouble(PROP_INITIALIZATION_DELAY).takeIf { it.isFinite() },
|
|
336
338
|
sseEndpoint = jsonAdDescription.optString(PROP_SSE_ENDPOINT).takeIf { it.isNotEmpty() },
|
|
339
|
+
streamActivityMonitorId = jsonAdDescription.optString(PROP_STREAM_ACTIVITY_MONITOR_ID).takeIf { it.isNotEmpty() },
|
|
337
340
|
)
|
|
338
341
|
}
|
|
339
342
|
|
|
@@ -5,7 +5,6 @@ import com.facebook.react.bridge.WritableMap
|
|
|
5
5
|
import com.theoplayer.android.api.ads.theoads.TheoAdsIntegration
|
|
6
6
|
import com.theoplayer.android.api.ads.theoads.event.InterstitialErrorEvent
|
|
7
7
|
import com.theoplayer.android.api.ads.theoads.event.InterstitialEvent
|
|
8
|
-
import com.theoplayer.android.api.ads.theoads.event.TheoAdsErrorEvent
|
|
9
8
|
import com.theoplayer.android.api.ads.theoads.event.TheoAdsEvent
|
|
10
9
|
import com.theoplayer.android.api.ads.theoads.event.TheoAdsEventTypes
|
|
11
10
|
import com.theoplayer.android.api.event.EventListener
|
|
@@ -19,8 +18,7 @@ private val FORWARDED_EVENTS = listOf(
|
|
|
19
18
|
TheoAdsEventTypes.INTERSTITIAL_BEGIN,
|
|
20
19
|
TheoAdsEventTypes.INTERSTITIAL_END,
|
|
21
20
|
TheoAdsEventTypes.INTERSTITIAL_UPDATE,
|
|
22
|
-
TheoAdsEventTypes.INTERSTITIAL_ERROR
|
|
23
|
-
TheoAdsEventTypes.THEOADS_ERROR
|
|
21
|
+
TheoAdsEventTypes.INTERSTITIAL_ERROR
|
|
24
22
|
)
|
|
25
23
|
|
|
26
24
|
class THEOadsEventAdapter(private val api: TheoAdsIntegration, private val emitter: Emitter) {
|
|
@@ -44,9 +42,6 @@ class THEOadsEventAdapter(private val api: TheoAdsIntegration, private val emitt
|
|
|
44
42
|
(event as? InterstitialEvent)?.let {
|
|
45
43
|
putMap(EVENT_PROP_INTERSTITIAL, THEOadsAdapter.fromInterstitial(event.interstitial))
|
|
46
44
|
}
|
|
47
|
-
(event as? TheoAdsErrorEvent)?.let {
|
|
48
|
-
putString(EVENT_PROP_MESSAGE, event.message)
|
|
49
|
-
}
|
|
50
45
|
(event as? InterstitialErrorEvent)?.let {
|
|
51
46
|
putString(EVENT_PROP_MESSAGE, event.message)
|
|
52
47
|
}
|
|
@@ -277,7 +277,6 @@ public class THEOplayerRCTMainEventHandler {
|
|
|
277
277
|
let welf = self,
|
|
278
278
|
let forwardedLoadedMetadataEvent = self?.onNativeLoadedMetadata {
|
|
279
279
|
let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: wplayer, metadataTracksInfo: welf.loadedMetadataAndChapterTracksInfo)
|
|
280
|
-
print(metadata)
|
|
281
280
|
forwardedLoadedMetadataEvent(metadata)
|
|
282
281
|
}
|
|
283
282
|
}
|
|
@@ -22,6 +22,7 @@ let SD_PROP_INTEGRATION_PARAMETERS: String = "integrationParameters"
|
|
|
22
22
|
let SD_PROP_AVAILABILITY_TYPE: String = "availabilityType"
|
|
23
23
|
let SD_PROP_AUTH_TOKEN: String = "authToken"
|
|
24
24
|
let SD_PROP_STREAM_ACTIVITY_MONITOR_ID: String = "streamActivityMonitorID"
|
|
25
|
+
let SD_PROP_STREAM_ACTIVITY_MONITOR_ID_THEOADS: String = "streamActivityMonitorId"
|
|
25
26
|
let SD_PROP_AD_TAG_PARAMETERS: String = "adTagParameters"
|
|
26
27
|
let SD_PROP_APIKEY: String = "apiKey"
|
|
27
28
|
let SD_PROP_VIDEOID: String = "videoID"
|
|
@@ -66,10 +67,12 @@ let SD_PROP_CMCD: String = "cmcd"
|
|
|
66
67
|
let EXTENSION_HLS: String = ".m3u8"
|
|
67
68
|
let EXTENSION_MP4: String = ".mp4"
|
|
68
69
|
let EXTENSION_MP3: String = ".mp3"
|
|
70
|
+
let EXTENSION_M4A: String = ".m4a"
|
|
69
71
|
|
|
70
72
|
let MIMETYPE_HLS = "application/x-mpegurl"
|
|
71
73
|
let MIMETYPE_MP4 = "video/mp4"
|
|
72
74
|
let MIMETYPE_MP3 = "audio/mpeg"
|
|
75
|
+
let MIMETYPE_M4A = "audio/mp4"
|
|
73
76
|
|
|
74
77
|
let DRM_INTEGRATION_ID_EZDRM = "ezdrm"
|
|
75
78
|
let DRM_INTEGRATION_ID_KEYOS = "keyos"
|
|
@@ -419,6 +422,8 @@ class THEOplayerRCTSourceDescriptionBuilder {
|
|
|
419
422
|
return MIMETYPE_MP4
|
|
420
423
|
} else if src.suffix(4) == EXTENSION_MP3 {
|
|
421
424
|
return MIMETYPE_MP3
|
|
425
|
+
} else if src.suffix(4) == EXTENSION_M4A {
|
|
426
|
+
return MIMETYPE_M4A
|
|
422
427
|
}
|
|
423
428
|
return MIMETYPE_HLS
|
|
424
429
|
}
|
|
@@ -81,28 +81,34 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
|
|
|
81
81
|
func updateRemoteCommands() {
|
|
82
82
|
let commandCenter = MPRemoteCommandCenter.shared()
|
|
83
83
|
|
|
84
|
+
let skipControlsEnabled = self.hasSource && !self.inAd && !self.isLive
|
|
85
|
+
let playPauseControlsEnabled = self.hasSource && !self.inAd && (!self.isLive || mediaControlConfig.allowLivePlayPause)
|
|
86
|
+
|
|
84
87
|
// update the enabled state to have correct visual representation in the lockscreen
|
|
85
|
-
commandCenter.pauseCommand.isEnabled =
|
|
86
|
-
commandCenter.playCommand.isEnabled =
|
|
87
|
-
commandCenter.togglePlayPauseCommand.isEnabled =
|
|
88
|
-
commandCenter.stopCommand.isEnabled =
|
|
89
|
-
commandCenter.changePlaybackPositionCommand.isEnabled =
|
|
90
|
-
commandCenter.skipForwardCommand.isEnabled =
|
|
91
|
-
commandCenter.skipBackwardCommand.isEnabled =
|
|
92
|
-
commandCenter.nextTrackCommand.isEnabled =
|
|
93
|
-
commandCenter.previousTrackCommand.isEnabled =
|
|
88
|
+
commandCenter.pauseCommand.isEnabled = playPauseControlsEnabled
|
|
89
|
+
commandCenter.playCommand.isEnabled = playPauseControlsEnabled
|
|
90
|
+
commandCenter.togglePlayPauseCommand.isEnabled = playPauseControlsEnabled
|
|
91
|
+
commandCenter.stopCommand.isEnabled = playPauseControlsEnabled
|
|
92
|
+
commandCenter.changePlaybackPositionCommand.isEnabled = skipControlsEnabled
|
|
93
|
+
commandCenter.skipForwardCommand.isEnabled = skipControlsEnabled
|
|
94
|
+
commandCenter.skipBackwardCommand.isEnabled = skipControlsEnabled
|
|
95
|
+
commandCenter.nextTrackCommand.isEnabled = skipControlsEnabled
|
|
96
|
+
commandCenter.previousTrackCommand.isEnabled = skipControlsEnabled
|
|
94
97
|
|
|
95
98
|
// set configured skip forward/backward intervals
|
|
96
99
|
commandCenter.skipForwardCommand.preferredIntervals = [NSNumber(value: self.mediaControlConfig.skipForwardInterval)]
|
|
97
100
|
commandCenter.skipBackwardCommand.preferredIntervals = [NSNumber(value: self.mediaControlConfig.skipBackwardInterval)]
|
|
98
101
|
|
|
99
|
-
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Remote commands updated for \(self.isLive ? "LIVE" : "VOD") (\(self.inAd ? "AD IS PLAYING" : "NO AD PLAYING")).") }
|
|
102
|
+
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Remote commands updated for \(self.isLive ? "LIVE" : "VOD") (ALLOWLIVEPLAYPAUSE: \(mediaControlConfig.allowLivePlayPause)) (\(self.inAd ? "AD IS PLAYING" : "NO AD PLAYING")).") }
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
@objc private func onPlayCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
|
103
106
|
if let player = self.player,
|
|
104
|
-
!self.inAd
|
|
105
|
-
|
|
107
|
+
!self.inAd {
|
|
108
|
+
if self.isLive && self.mediaControlConfig.seekToLiveOnResume {
|
|
109
|
+
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Seek to live.") }
|
|
110
|
+
player.currentTime = .infinity
|
|
111
|
+
}
|
|
106
112
|
player.play()
|
|
107
113
|
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Play command handled.") }
|
|
108
114
|
} else {
|
|
@@ -113,8 +119,7 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
|
|
|
113
119
|
|
|
114
120
|
@objc private func onPauseCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
|
115
121
|
if let player = self.player,
|
|
116
|
-
!self.inAd
|
|
117
|
-
!player.paused {
|
|
122
|
+
!self.inAd {
|
|
118
123
|
player.pause()
|
|
119
124
|
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Pause command handled.") }
|
|
120
125
|
} else {
|
|
@@ -127,8 +132,14 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
|
|
|
127
132
|
if let player = self.player,
|
|
128
133
|
!self.inAd {
|
|
129
134
|
if player.paused {
|
|
135
|
+
if self.isLive && self.mediaControlConfig.seekToLiveOnResume {
|
|
136
|
+
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Seek to live.") }
|
|
137
|
+
player.currentTime = .infinity
|
|
138
|
+
}
|
|
139
|
+
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Toggled to playing.") }
|
|
130
140
|
player.play()
|
|
131
141
|
} else {
|
|
142
|
+
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Toggled to paused.") }
|
|
132
143
|
player.pause()
|
|
133
144
|
}
|
|
134
145
|
if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Toggle play/pause command handled.") }
|
|
@@ -176,7 +187,7 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
|
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
@objc private func onSkipBackwardCommand(_ event: MPSkipIntervalCommandEvent) -> MPRemoteCommandHandlerStatus {
|
|
179
|
-
if let player = self.player
|
|
190
|
+
if let player = self.player,
|
|
180
191
|
!self.isLive,
|
|
181
192
|
!self.inAd {
|
|
182
193
|
player.currentTime = player.currentTime - event.interval
|
|
@@ -7,6 +7,8 @@ struct MediaControlConfig {
|
|
|
7
7
|
var skipForwardInterval: Int = 15
|
|
8
8
|
var skipBackwardInterval: Int = 15
|
|
9
9
|
var convertSkipToSeek: Bool = false
|
|
10
|
+
var allowLivePlayPause: Bool = true
|
|
11
|
+
var seekToLiveOnResume: Bool = false
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
extension THEOplayerRCTView {
|
|
@@ -22,6 +24,12 @@ extension THEOplayerRCTView {
|
|
|
22
24
|
if let convertSkipToSeek = mediaControlConfig["convertSkipToSeek"] as? Bool {
|
|
23
25
|
self.mediaControlConfig.convertSkipToSeek = convertSkipToSeek
|
|
24
26
|
}
|
|
27
|
+
if let allowLivePlayPause = mediaControlConfig["allowLivePlayPause"] as? Bool {
|
|
28
|
+
self.mediaControlConfig.allowLivePlayPause = allowLivePlayPause
|
|
29
|
+
}
|
|
30
|
+
if let seekToLiveOnResume = mediaControlConfig["seekToLiveOnResume"] as? Bool {
|
|
31
|
+
self.mediaControlConfig.seekToLiveOnResume = seekToLiveOnResume
|
|
32
|
+
}
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
}
|
|
@@ -47,6 +47,7 @@ extension THEOplayerRCTSourceDescriptionBuilder {
|
|
|
47
47
|
}
|
|
48
48
|
let adTagParameters = adsData[SD_PROP_AD_TAG_PARAMETERS] as? [String:String]
|
|
49
49
|
let useId3 = adsData[SD_PROP_USE_ID3] as? Bool
|
|
50
|
+
let streamActivityMonitorId = adsData[SD_PROP_STREAM_ACTIVITY_MONITOR_ID_THEOADS] as? String
|
|
50
51
|
let retrievePodIdURI = adsData[SD_PROP_RETRIEVE_POD_ID_URI] as? String
|
|
51
52
|
let initializationDelay = adsData[SD_PROP_INITIALIZATION_DELAY] as? Double
|
|
52
53
|
|
|
@@ -58,6 +59,7 @@ extension THEOplayerRCTSourceDescriptionBuilder {
|
|
|
58
59
|
overrideAdSrc: overrideAdSrc,
|
|
59
60
|
adTagParameters: adTagParameters,
|
|
60
61
|
useId3: useId3,
|
|
62
|
+
streamActivityMonitorId: streamActivityMonitorId,
|
|
61
63
|
sseEndpoint: sseEndpoint,
|
|
62
64
|
retrievePodIdURI: retrievePodIdURI,
|
|
63
65
|
initializationDelay: initializationDelay)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["SourceIntegrationId","exports"],"sourceRoot":"../../../../src","sources":["api/source/SourceDescription.ts"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA,IAQYA,mBAAmB,GAAAC,OAAA,CAAAD,mBAAA,0BAAnBA,mBAAmB;EAAnBA,mBAAmB;EAAA,OAAnBA,mBAAmB;AAAA;AAI/B;AACA;AACA;AACA;AACA;AACA;
|
|
1
|
+
{"version":3,"names":["SourceIntegrationId","exports"],"sourceRoot":"../../../../src","sources":["api/source/SourceDescription.ts"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA,IAQYA,mBAAmB,GAAAC,OAAA,CAAAD,mBAAA,0BAAnBA,mBAAmB;EAAnBA,mBAAmB;EAAA,OAAnBA,mBAAmB;AAAA;AAI/B;AACA;AACA;AACA;AACA;AACA;AAyDA;AACA;AACA;AACA;AACA;AACA;AAmDA;AACA;AACA;AACA;AACA;AACA;AACA;AA2FA;AACA;AACA;AACA;AACA;AACA;AAyEA;AACA;AACA;AACA;AACA;AACA","ignoreList":[]}
|
|
@@ -207,7 +207,10 @@ class THEOplayerView extends _react.PureComponent {
|
|
|
207
207
|
this._facade.dispatchEvent((0, _NativeTheoLiveEvent.fromNativeTheoLiveEvent)(event));
|
|
208
208
|
};
|
|
209
209
|
_onTHEOadsEvent = event => {
|
|
210
|
-
|
|
210
|
+
const theoAdsEvent = (0, _NativeTheoAdsEvent.fromNativeTheoAdsEvent)(this.nativeHandle, event);
|
|
211
|
+
if (theoAdsEvent !== undefined) {
|
|
212
|
+
this._facade.dispatchEvent(theoAdsEvent);
|
|
213
|
+
}
|
|
211
214
|
};
|
|
212
215
|
_onCastEvent = event => {
|
|
213
216
|
switch (event.nativeEvent.type) {
|