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.
Files changed (46) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +2 -2
  3. package/android/build.gradle +1 -1
  4. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  5. package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +38 -9
  6. package/android/src/main/java/com/theoplayer/media/MediaPlaybackService.kt +17 -34
  7. package/android/src/main/java/com/theoplayer/media/MediaSessionConfig.kt +10 -0
  8. package/android/src/main/java/com/theoplayer/media/MediaSessionConfigAdapter.kt +8 -0
  9. package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +3 -0
  10. package/android/src/main/java/com/theoplayer/theoads/THEOadsEventAdapter.kt +1 -6
  11. package/ios/THEOplayerRCTMainEventHandler.swift +0 -1
  12. package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +5 -0
  13. package/ios/backgroundAudio/THEOplayerRCTRemoteCommandsManager.swift +26 -15
  14. package/ios/backgroundAudio/THEOplayerRCTView+MediaControlConfig.swift +8 -0
  15. package/ios/theoAds/THEOplayerRCTSourceDescriptionBuilder+TheoAds.swift +2 -0
  16. package/lib/commonjs/api/source/SourceDescription.js.map +1 -1
  17. package/lib/commonjs/internal/THEOplayerView.js +4 -1
  18. package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
  19. package/lib/commonjs/internal/adapter/WebEventForwarder.js +4 -1
  20. package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
  21. package/lib/commonjs/internal/adapter/event/native/NativeTheoAdsEvent.js +8 -2
  22. package/lib/commonjs/internal/adapter/event/native/NativeTheoAdsEvent.js.map +1 -1
  23. package/lib/commonjs/manifest.json +1 -1
  24. package/lib/module/api/source/SourceDescription.js.map +1 -1
  25. package/lib/module/internal/THEOplayerView.js +4 -1
  26. package/lib/module/internal/THEOplayerView.js.map +1 -1
  27. package/lib/module/internal/adapter/WebEventForwarder.js +5 -2
  28. package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
  29. package/lib/module/internal/adapter/event/native/NativeTheoAdsEvent.js +8 -2
  30. package/lib/module/internal/adapter/event/native/NativeTheoAdsEvent.js.map +1 -1
  31. package/lib/module/manifest.json +1 -1
  32. package/lib/typescript/api/media/MediaControlConfiguration.d.ts +17 -0
  33. package/lib/typescript/api/media/MediaControlConfiguration.d.ts.map +1 -1
  34. package/lib/typescript/api/source/SourceDescription.d.ts +6 -6
  35. package/lib/typescript/api/source/SourceDescription.d.ts.map +1 -1
  36. package/lib/typescript/internal/THEOplayerView.d.ts.map +1 -1
  37. package/lib/typescript/internal/adapter/WebEventForwarder.d.ts.map +1 -1
  38. package/lib/typescript/internal/adapter/event/native/NativeTheoAdsEvent.d.ts +1 -1
  39. package/lib/typescript/internal/adapter/event/native/NativeTheoAdsEvent.d.ts.map +1 -1
  40. package/package.json +5 -5
  41. package/src/api/media/MediaControlConfiguration.ts +19 -0
  42. package/src/api/source/SourceDescription.ts +7 -7
  43. package/src/internal/THEOplayerView.tsx +4 -1
  44. package/src/internal/adapter/WebEventForwarder.ts +45 -32
  45. package/src/internal/adapter/event/native/NativeTheoAdsEvent.ts +9 -4
  46. 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 web, where adbreak related AdEvents did no longer contain the adBreak info.
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>
@@ -152,7 +152,7 @@ dependencies {
152
152
  }
153
153
 
154
154
  //noinspection GradleDynamicVersion
155
- implementation("com.facebook.react:react-native:+")
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.6-bin.zip
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
- reactContext.getSystemService(Context.UI_MODE_SERVICE) as? UiModeManager
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
- isInAd || isLive && !isTV -> 0
171
- isLive && isTV -> ALLOWED_PLAYBACK_ACTIONS xor
172
- PlaybackStateCompat.ACTION_FAST_FORWARD xor
173
- PlaybackStateCompat.ACTION_REWIND
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(connector: MediaSessionConnector?, config: MediaSessionConfig) {
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
- playerView.player.addIntegration(it)
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() = mediaSessionConnector.mediaSession
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).apply {
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(NOTIFICATION_ID, notificationBuilder.build(playbackState, largeIcon, mediaSessionConfig.mediaSessionEnabled))
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(@PlaybackStateCompat.State playbackState: Int, largeIcon: Bitmap? = null) {
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 = self.hasSource && !self.inAd
86
- commandCenter.playCommand.isEnabled = self.hasSource && !self.inAd
87
- commandCenter.togglePlayPauseCommand.isEnabled = self.hasSource && !self.inAd
88
- commandCenter.stopCommand.isEnabled = self.hasSource && !self.inAd
89
- commandCenter.changePlaybackPositionCommand.isEnabled = self.hasSource && !self.isLive && !self.inAd
90
- commandCenter.skipForwardCommand.isEnabled = self.hasSource && !self.isLive && !self.inAd
91
- commandCenter.skipBackwardCommand.isEnabled = self.hasSource && !self.isLive && !self.inAd
92
- commandCenter.nextTrackCommand.isEnabled = !self.isLive && !self.inAd
93
- commandCenter.previousTrackCommand.isEnabled = !self.isLive && !self.inAd
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
- player.paused {
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;AAgEA;AACA;AACA;AACA;AACA;AACA;AAmDA;AACA;AACA;AACA;AACA;AACA;AACA;AA2FA;AACA;AACA;AACA;AACA;AACA;AAkEA;AACA;AACA;AACA;AACA;AACA","ignoreList":[]}
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
- this._facade.dispatchEvent((0, _NativeTheoAdsEvent.fromNativeTheoAdsEvent)(this.nativeHandle, event));
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) {