react-native-theoplayer 3.0.2 → 3.2.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 (57) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/android/src/main/AndroidManifest.xml +1 -1
  3. package/android/src/main/java/com/theoplayer/PlayerConfigAdapter.kt +4 -0
  4. package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +5 -0
  5. package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +4 -29
  6. package/android/src/main/java/com/theoplayer/player/PlayerModule.kt +8 -0
  7. package/android/src/main/java/com/theoplayer/track/TrackListAdapter.kt +47 -2
  8. package/ios/THEOplayerRCTNetworkUtils.swift +3 -1
  9. package/ios/THEOplayerRCTTextTrackEventHandler.swift +21 -17
  10. package/ios/THEOplayerRCTTrackMetadataAggregator.swift +64 -8
  11. package/ios/THEOplayerRCTView.swift +4 -0
  12. package/ios/contentprotection/THEOplayerRCTContentProtectionAPI.swift +4 -0
  13. package/lib/commonjs/api/config/PlayerConfiguration.js.map +1 -1
  14. package/lib/commonjs/api/track/DateRangeCue.js +19 -0
  15. package/lib/commonjs/api/track/DateRangeCue.js.map +1 -0
  16. package/lib/commonjs/api/track/TextTrack.js +2 -0
  17. package/lib/commonjs/api/track/TextTrack.js.map +1 -1
  18. package/lib/commonjs/api/track/barrel.js +11 -0
  19. package/lib/commonjs/api/track/barrel.js.map +1 -1
  20. package/lib/commonjs/internal/THEOplayerView.js +17 -2
  21. package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
  22. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js +9 -3
  23. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js.map +1 -1
  24. package/lib/commonjs/internal/adapter/WebEventForwarder.js +3 -0
  25. package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
  26. package/lib/commonjs/internal/adapter/web/TrackUtils.js +17 -1
  27. package/lib/commonjs/internal/adapter/web/TrackUtils.js.map +1 -1
  28. package/lib/module/api/config/PlayerConfiguration.js.map +1 -1
  29. package/lib/module/api/track/DateRangeCue.js +13 -0
  30. package/lib/module/api/track/DateRangeCue.js.map +1 -0
  31. package/lib/module/api/track/TextTrack.js +2 -0
  32. package/lib/module/api/track/TextTrack.js.map +1 -1
  33. package/lib/module/api/track/barrel.js +1 -0
  34. package/lib/module/api/track/barrel.js.map +1 -1
  35. package/lib/module/internal/THEOplayerView.js +18 -2
  36. package/lib/module/internal/THEOplayerView.js.map +1 -1
  37. package/lib/module/internal/adapter/THEOplayerAdapter.js +9 -3
  38. package/lib/module/internal/adapter/THEOplayerAdapter.js.map +1 -1
  39. package/lib/module/internal/adapter/WebEventForwarder.js +4 -1
  40. package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
  41. package/lib/module/internal/adapter/web/TrackUtils.js +16 -1
  42. package/lib/module/internal/adapter/web/TrackUtils.js.map +1 -1
  43. package/lib/typescript/api/config/PlayerConfiguration.d.ts +4 -0
  44. package/lib/typescript/api/track/DateRangeCue.d.ts +61 -0
  45. package/lib/typescript/api/track/TextTrack.d.ts +4 -1
  46. package/lib/typescript/api/track/barrel.d.ts +1 -0
  47. package/lib/typescript/internal/THEOplayerView.d.ts +2 -1
  48. package/lib/typescript/internal/adapter/web/TrackUtils.d.ts +2 -1
  49. package/package.json +1 -1
  50. package/src/api/config/PlayerConfiguration.ts +5 -0
  51. package/src/api/track/DateRangeCue.ts +74 -0
  52. package/src/api/track/TextTrack.ts +3 -0
  53. package/src/api/track/barrel.ts +1 -0
  54. package/src/internal/THEOplayerView.tsx +19 -3
  55. package/src/internal/adapter/THEOplayerAdapter.ts +6 -3
  56. package/src/internal/adapter/WebEventForwarder.ts +5 -0
  57. package/src/internal/adapter/web/TrackUtils.ts +18 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +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.1.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.2.0] - 23-11-29
9
+
10
+ ### Fixed
11
+
12
+ - Fixed an issue on iOS and Android were selecting an audio, video or text track that has a `uid` with value `0`, would fail.
13
+
14
+ ### Added
15
+
16
+ - Added support for HLS #EXT-X-DATERANGE timed metadata tags. The tags appear as cues in a dedicated text track with type `daterange`.
17
+
18
+ ## [3.1.0] - 23-10-27
19
+
20
+ ### Changed
21
+
22
+ - Revised audio focus protocol on Android. When resuming an app, audio focus is retrieved only if the player is not paused.
23
+ - Changed the behaviour of the Android component supporting background playback. It is stopped but not disabled when setting `backgroundAudioConfiguration.enabled = true`.
24
+
25
+ ### Fixed
26
+
27
+ - Fixed an issue on Android where during play-out of a locally stored media asset the `seekable` property would not update.
28
+ - Fixed an issue on iOS where the error was not forwarded to theoplayer if a drm request fails on the iOS bridge
29
+
8
30
  ## [3.0.2] - 23-10-17
9
31
 
10
32
  ### Fixed
@@ -32,7 +32,7 @@
32
32
  android:name="com.theoplayer.media.MediaPlaybackService"
33
33
  android:description="@string/background_playback_service_description"
34
34
  android:exported="false"
35
- android:enabled="false"
35
+ android:enabled="true"
36
36
  android:foregroundServiceType="mediaPlayback">
37
37
  <intent-filter>
38
38
  <action android:name="android.media.browse.MediaBrowserService" />
@@ -18,6 +18,7 @@ private const val PROP_PRELOAD = "preload"
18
18
  private const val PROP_UI_ENABLED = "uiEnabled"
19
19
  private const val PROP_CAST_STRATEGY = "strategy"
20
20
  private const val PROP_RETRY_CONFIG = "retryConfiguration"
21
+ private const val PROP_HLS_DATE_RANGE = "hlsDateRange"
21
22
  private const val PROP_RETRY_MAX_RETRIES = "maxRetries"
22
23
  private const val PROP_RETRY_MIN_BACKOFF = "minimumBackoff"
23
24
  private const val PROP_RETRY_MAX_BACKOFF = "maximumBackoff"
@@ -44,6 +45,9 @@ class PlayerConfigAdapter(private val configProps: ReadableMap?) {
44
45
  if (hasKey(PROP_RETRY_CONFIG)) {
45
46
  networkConfiguration(networkConfig())
46
47
  }
48
+ if (hasKey(PROP_HLS_DATE_RANGE)) {
49
+ hlsDateRange(getBoolean(PROP_HLS_DATE_RANGE))
50
+ }
47
51
  pipConfiguration(PipConfiguration.Builder().build())
48
52
  }
49
53
  }.build()
@@ -38,6 +38,7 @@ import com.theoplayer.android.api.player.track.mediatrack.quality.AudioQuality
38
38
  import com.theoplayer.android.api.player.track.mediatrack.quality.Quality
39
39
  import com.theoplayer.android.api.player.track.mediatrack.quality.VideoQuality
40
40
  import com.theoplayer.android.api.player.track.texttrack.TextTrack
41
+ import com.theoplayer.android.api.player.track.texttrack.TextTrackKind
41
42
  import com.theoplayer.android.api.player.track.texttrack.TextTrackMode
42
43
  import com.theoplayer.cast.CastEventAdapter
43
44
  import com.theoplayer.presentation.PresentationModeChangeContext
@@ -474,6 +475,10 @@ class PlayerEventEmitter internal constructor(
474
475
  }
475
476
 
476
477
  private fun onTextTrackAdd(event: AddTrackEvent) {
478
+ // By default, set metadata tracks to mode 'hidden', until we add an API to set the mode.
479
+ if (event.track.kind == TextTrackKind.METADATA.type) {
480
+ event.track.mode = TextTrackMode.HIDDEN
481
+ }
477
482
  dispatchTextTrackEvent(TrackEventType.ADD_TRACK, event.track)
478
483
  }
479
484
 
@@ -5,7 +5,6 @@ import android.content.ComponentName
5
5
  import android.content.Context
6
6
  import android.content.Intent
7
7
  import android.content.ServiceConnection
8
- import android.content.pm.PackageManager
9
8
  import android.content.res.Configuration
10
9
  import android.os.Handler
11
10
  import android.os.IBinder
@@ -74,7 +73,7 @@ class ReactTHEOplayerContext private constructor(
74
73
  var imaIntegration: GoogleImaIntegration? = null
75
74
  var castIntegration: CastIntegration? = null
76
75
  var wasPlayingOnHostPause: Boolean = false
77
- var isHostPaused: Boolean = false
76
+ private var isHostPaused: Boolean = false
78
77
 
79
78
  private val isBackgroundAudioEnabled: Boolean
80
79
  get() = backgroundAudioConfig.enabled
@@ -113,27 +112,6 @@ class ReactTHEOplayerContext private constructor(
113
112
  }
114
113
  }
115
114
 
116
- private fun setPlaybackServiceEnabled(enabled: Boolean) {
117
- // Toggle the MediaPlaybackService.
118
- toggleComponent(enabled, ComponentName(reactContext.applicationContext, MediaPlaybackService::class.java))
119
-
120
- // Also toggle any registered MediaButtonReceiver broadcast receiver.
121
- // It will crash the app if it remains active and tries to find our disabled MediaBrowserService instance.
122
- toggleComponent(enabled, ComponentName(reactContext.applicationContext, androidx.media.session.MediaButtonReceiver::class.java))
123
- }
124
-
125
- /**
126
- * Enable or disable a receiver component.
127
- */
128
- private fun toggleComponent(enabled: Boolean, componentName: ComponentName) {
129
- reactContext.applicationContext.packageManager.setComponentEnabledSetting(
130
- componentName,
131
- if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
132
- else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
133
- PackageManager.DONT_KILL_APP
134
- )
135
- }
136
-
137
115
  private fun applyBackgroundPlaybackConfig(
138
116
  config: BackgroundAudioConfig,
139
117
  prevConfig: BackgroundAudioConfig?
@@ -148,13 +126,8 @@ class ReactTHEOplayerContext private constructor(
148
126
  if (BuildConfig.USE_PLAYBACK_SERVICE) {
149
127
  if (prevConfig?.enabled != true && config.enabled) {
150
128
  // Enable & bind background playback
151
- setPlaybackServiceEnabled(true)
152
129
  bindMediaPlaybackService()
153
130
  } else if (prevConfig?.enabled == true) {
154
- // First disable the MediaPlaybackService and MediaButtonReceiver so that no more media
155
- // button events can be captured.
156
- setPlaybackServiceEnabled(false)
157
-
158
131
  // Stop & unbind MediaPlaybackService.
159
132
  binder?.stopForegroundService()
160
133
  unbindMediaPlaybackService()
@@ -379,7 +352,9 @@ class ReactTHEOplayerContext private constructor(
379
352
  isHostPaused = false
380
353
  mediaSessionConnector?.setActive(true)
381
354
  playerView.onResume()
382
- audioFocusManager?.retrieveAudioFocus()
355
+ if (!player.isPaused) {
356
+ audioFocusManager?.retrieveAudioFocus()
357
+ }
383
358
  }
384
359
 
385
360
  fun destroy() {
@@ -98,6 +98,10 @@ class PlayerModule(context: ReactApplicationContext) : ReactContextBaseJavaModul
98
98
 
99
99
  @ReactMethod
100
100
  fun setSelectedAudioTrack(tag: Int, uid: Int) {
101
+ if (uid == -1) {
102
+ // Do not allow disabling all audio tracks
103
+ return
104
+ }
101
105
  viewResolver.resolveViewByTag(tag) { view: ReactTHEOplayerView? ->
102
106
  view?.player?.let {
103
107
  for (track in it.audioTracks) {
@@ -109,6 +113,10 @@ class PlayerModule(context: ReactApplicationContext) : ReactContextBaseJavaModul
109
113
 
110
114
  @ReactMethod
111
115
  fun setSelectedVideoTrack(tag: Int, uid: Int) {
116
+ if (uid == -1) {
117
+ // Do not allow disabling all video tracks
118
+ return
119
+ }
112
120
  viewResolver.resolveViewByTag(tag) { view: ReactTHEOplayerView? ->
113
121
  view?.player?.let {
114
122
  for (track in it.videoTracks) {
@@ -11,6 +11,7 @@ import com.theoplayer.android.api.player.track.mediatrack.quality.Quality
11
11
  import com.theoplayer.android.api.player.track.mediatrack.quality.AudioQuality
12
12
  import com.theoplayer.android.api.player.track.mediatrack.quality.VideoQuality
13
13
  import com.theoplayer.android.api.player.track.mediatrack.MediaTrackList
14
+ import com.theoplayer.android.api.player.track.texttrack.cue.DateRangeCue
14
15
  import com.theoplayer.util.TypeUtils
15
16
 
16
17
  private const val PROP_ID = "id"
@@ -35,6 +36,16 @@ private const val PROP_STARTTIME = "startTime"
35
36
  private const val PROP_ENDTIME = "endTime"
36
37
  private const val PROP_CUES = "cues"
37
38
  private const val PROP_CUE_CONTENT = "content"
39
+ private const val PROP_ATTRIBUTE_CLASS = "class"
40
+ private const val PROP_STARTDATE = "startDate"
41
+ private const val PROP_ENDDATE = "endDate"
42
+ private const val PROP_DURATION = "duration"
43
+ private const val PROP_PLANNED_DURATION = "plannedDuration"
44
+ private const val PROP_END_ON_NEXT = "endOnNext"
45
+ private const val PROP_SCTE35CMD = "scte35Cmd"
46
+ private const val PROP_SCTE35OUT = "scte35Out"
47
+ private const val PROP_SCTE35IN = "scte35In"
48
+ private const val PROP_CUSTOM_ATTRIBUTES = "customAttributes"
38
49
 
39
50
  object TrackListAdapter {
40
51
 
@@ -85,6 +96,38 @@ object TrackListAdapter {
85
96
  content.optString("content") ?: content.optString("contentString")
86
97
  )
87
98
  }
99
+
100
+ if (cue is DateRangeCue) {
101
+ cue.attributeClass?.run {
102
+ cuePayload.putString(PROP_ATTRIBUTE_CLASS, this)
103
+ }
104
+ cuePayload.putDouble(PROP_STARTDATE, cue.startDate.time.toDouble())
105
+ cue.endDate?.run {
106
+ cuePayload.putDouble(PROP_ENDDATE, this.time.toDouble())
107
+ }
108
+ cue.duration?.run {
109
+ cuePayload.putDouble(PROP_DURATION, TypeUtils.encodeInfNan(1e3 * this))
110
+ }
111
+ cue.plannedDuration?.run {
112
+ cuePayload.putDouble(PROP_PLANNED_DURATION, TypeUtils.encodeInfNan(1e3 * this))
113
+ }
114
+ cuePayload.putBoolean(PROP_END_ON_NEXT, cue.isEndOnNext)
115
+ cue.customAttributes?.asMap()?.run {
116
+ val attributes = Arguments.createMap()
117
+ forEach { (key, value) ->
118
+ when (value) {
119
+ is String -> attributes.putString(key, value)
120
+ is Boolean -> attributes.putBoolean(key, value)
121
+ is Int -> attributes.putInt(key, value)
122
+ is Double -> attributes.putDouble(key, value)
123
+ // TODO: support array & sub-objects
124
+ }
125
+ }
126
+ cuePayload.putMap(PROP_CUSTOM_ATTRIBUTES, attributes)
127
+ }
128
+ // TODO: Add SCTE marker properties
129
+ }
130
+
88
131
  return cuePayload
89
132
  }
90
133
 
@@ -128,7 +171,8 @@ object TrackListAdapter {
128
171
  qualityList?.forEach { quality ->
129
172
  qualities.pushMap(fromAudioQuality(quality))
130
173
  }
131
- } catch (ignore: NullPointerException) {}
174
+ } catch (ignore: NullPointerException) {
175
+ }
132
176
  audioTrackPayload.putArray(PROP_QUALITIES, qualities)
133
177
  val activeQuality = audioTrack.activeQuality
134
178
  if (activeQuality != null) {
@@ -178,7 +222,8 @@ object TrackListAdapter {
178
222
  qualities.pushMap(fromVideoQuality(quality as VideoQuality))
179
223
  }
180
224
  }
181
- } catch (ignore: java.lang.NullPointerException) {}
225
+ } catch (ignore: java.lang.NullPointerException) {
226
+ }
182
227
  videoTrackPayload.putArray(PROP_QUALITIES, qualities)
183
228
  val activeQuality = videoTrack.activeQuality
184
229
  if (activeQuality != null) {
@@ -31,7 +31,6 @@ class THEOplayerRCTNetworkUtils: NSObject, URLSessionDataDelegate {
31
31
  let task = self.defaultUrlSession.dataTask(with: request) { data, response, error in
32
32
  if let error = error {
33
33
  PrintUtils.printLog(logText: "request Error: \(error.localizedDescription)")
34
- return
35
34
  }
36
35
  if let urlResponse = response as? HTTPURLResponse {
37
36
  let statusCode = urlResponse.statusCode
@@ -44,7 +43,10 @@ class THEOplayerRCTNetworkUtils: NSObject, URLSessionDataDelegate {
44
43
  }
45
44
  }
46
45
  completion?(data, statusCode, allHeaders, error)
46
+ } else {
47
+ completion?(data, -1, [:], error)
47
48
  }
49
+
48
50
  }
49
51
  // start the task
50
52
  task.resume()
@@ -53,14 +53,14 @@ class THEOplayerRCTTextTrackEventHandler {
53
53
  }
54
54
 
55
55
  // ADD_TRACK
56
- self.addTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.ADD_TRACK) { [weak self] event in
57
- guard let welf = self else { return }
56
+ self.addTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.ADD_TRACK) { [weak self, weak player] event in
57
+ guard let welf = self, let wplayer = player else { return }
58
58
  if let forwardedTextTrackListEvent = welf.onNativeTextTrackListEvent,
59
59
  let textTrack = event.track as? TextTrack {
60
60
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received ADD_TRACK event from THEOplayer textTrack list: trackUid = \(textTrack.uid)") }
61
61
  // trigger tracklist event
62
62
  forwardedTextTrackListEvent([
63
- "track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack),
63
+ "track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack, player: wplayer),
64
64
  "type" : TrackListEventType.ADD_TRACK.rawValue
65
65
  ])
66
66
  // start listening for cue events on this track and keep listener for later removal
@@ -77,14 +77,14 @@ class THEOplayerRCTTextTrackEventHandler {
77
77
  if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] AddTrack listener attached to THEOplayer textTrack list") }
78
78
 
79
79
  // REMOVE_TRACK
80
- self.removeTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.REMOVE_TRACK) { [weak self] event in
81
- guard let welf = self else { return }
80
+ self.removeTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.REMOVE_TRACK) { [weak self, weak player] event in
81
+ guard let welf = self, let wplayer = player else { return }
82
82
  if let forwardedTextTrackListEvent = welf.onNativeTextTrackListEvent,
83
83
  let textTrack = event.track as? TextTrack {
84
84
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received REMOVE_TRACK event from THEOplayer textTrack list: trackUid = \(textTrack.uid)") }
85
85
  // trigger tracklist event
86
86
  forwardedTextTrackListEvent([
87
- "track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack),
87
+ "track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack, player: wplayer),
88
88
  "type" : TrackListEventType.REMOVE_TRACK.rawValue
89
89
  ])
90
90
  // stop listening for cue events on this track
@@ -109,14 +109,14 @@ class THEOplayerRCTTextTrackEventHandler {
109
109
  if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] RemoveTrack listener attached to THEOplayer textTrack list") }
110
110
 
111
111
  // CHANGE
112
- self.changeTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.CHANGE) { [weak self] event in
113
- guard let welf = self else { return }
112
+ self.changeTrackListener = player.textTracks.addEventListener(type: TextTrackListEventTypes.CHANGE) { [weak self, weak player] event in
113
+ guard let welf = self, let wplayer = player else { return }
114
114
  if let forwardedTextTrackListEvent = welf.onNativeTextTrackListEvent,
115
115
  let textTrack = event.track as? TextTrack {
116
116
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received CHANGE event from THEOplayer textTrack list: trackUid = \(textTrack.uid)") }
117
117
  // trigger tracklist event
118
118
  forwardedTextTrackListEvent([
119
- "track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack),
119
+ "track" : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack, player: wplayer),
120
120
  "type" : TrackListEventType.CHANGE_TRACK.rawValue
121
121
  ])
122
122
  }
@@ -176,48 +176,52 @@ class THEOplayerRCTTextTrackEventHandler {
176
176
  // MARK: - dynamic textTrack Listeners
177
177
  private func addCueListener(_ event: AddCueEvent) {
178
178
  if let forwardedTextTrackEvent = self.onNativeTextTrackEvent,
179
- let textTrack = event.cue.track {
179
+ let textTrack = event.cue.track,
180
+ let player = self.player {
180
181
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received ADD_CUE event from textTrack: trackUid = \(textTrack.uid), cueUid = \(event.cue.uid)") }
181
182
  forwardedTextTrackEvent([
182
183
  "trackUid" : textTrack.uid,
183
184
  "type": TrackCueEventType.ADD_CUE.rawValue,
184
- "cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue)
185
+ "cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue, player: player)
185
186
  ])
186
187
  }
187
188
  }
188
189
 
189
190
  private func removeCueListener(_ event: RemoveCueEvent) {
190
191
  if let forwardedTextTrackEvent = self.onNativeTextTrackEvent,
191
- let textTrack = event.cue.track {
192
+ let textTrack = event.cue.track,
193
+ let player = self.player {
192
194
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received REMOVE_CUE event from textTrack: trackUid = \(textTrack.uid), cueUid = \(event.cue.uid)") }
193
195
  forwardedTextTrackEvent([
194
196
  "trackUid" : textTrack.uid,
195
197
  "type": TrackCueEventType.REMOVE_CUE.rawValue,
196
- "cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue)
198
+ "cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue, player: player)
197
199
  ])
198
200
  }
199
201
  }
200
202
 
201
203
  private func enterCueListener(_ event: EnterCueEvent) {
202
204
  if let forwardedTextTrackEvent = self.onNativeTextTrackEvent,
203
- let textTrack = event.cue.track {
205
+ let textTrack = event.cue.track,
206
+ let player = self.player {
204
207
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received ENTER_CUE event from textTrack: trackUid = \(textTrack.uid), cueUid = \(event.cue.uid)") }
205
208
  forwardedTextTrackEvent([
206
209
  "trackUid" : textTrack.uid,
207
210
  "type": TrackCueEventType.ENTER_CUE.rawValue,
208
- "cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue)
211
+ "cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue, player: player)
209
212
  ])
210
213
  }
211
214
  }
212
215
 
213
216
  private func exitCueListener(_ event: ExitCueEvent) {
214
217
  if let forwardedTextTrackEvent = self.onNativeTextTrackEvent,
215
- let textTrack = event.cue.track {
218
+ let textTrack = event.cue.track,
219
+ let player = self.player {
216
220
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received EXIT_CUE event from textTrack: trackUid = \(textTrack.uid), cueUid = \(event.cue.uid)") }
217
221
  forwardedTextTrackEvent([
218
222
  "trackUid" : textTrack.uid,
219
223
  "type": TrackCueEventType.EXIT_CUE.rawValue,
220
- "cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue)
224
+ "cue": THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: event.cue, player: player)
221
225
  ])
222
226
  }
223
227
  }
@@ -24,7 +24,14 @@ let PROP_STARTTIME: String = "startTime"
24
24
  let PROP_ENDTIME: String = "endTime"
25
25
  let PROP_CUES: String = "cues"
26
26
  let PROP_CUE_CONTENT: String = "content"
27
+ let PROP_CUE_CUSTOM_ATTRIBUTES: String = "customAttributes"
27
28
  let PROP_SRC: String = "src"
29
+ let PROP_START_DATE: String = "startDate"
30
+ let PROP_END_DATE: String = "endDate"
31
+ let PROP_ATTRIBUTE_CLASS: String = "class"
32
+ let PROP_DURATION: String = "duration"
33
+ let PROP_PLANNED_DURATION: String = "plannedDuration"
34
+ let PROP_END_ON_NEXT: String = "endOnNext"
28
35
 
29
36
  class THEOplayerRCTTrackMetadataAggregator {
30
37
 
@@ -33,7 +40,7 @@ class THEOplayerRCTTrackMetadataAggregator {
33
40
  let audioTracks: AudioTrackList = player.audioTracks
34
41
  let videoTracks: VideoTrackList = player.videoTracks
35
42
  return [
36
- EVENT_PROP_TEXT_TRACKS : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackListInfo(textTracks: textTracks, metadataTracks: metadataTracksInfo),
43
+ EVENT_PROP_TEXT_TRACKS : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackListInfo(textTracks: textTracks, metadataTracks: metadataTracksInfo, player: player),
37
44
  EVENT_PROP_AUDIO_TRACKS : THEOplayerRCTTrackMetadataAggregator.aggregatedAudioTrackListInfo(audioTracks: audioTracks),
38
45
  EVENT_PROP_VIDEO_TRACKS : THEOplayerRCTTrackMetadataAggregator.aggregatedVideoTrackListInfo(videoTracks: videoTracks),
39
46
  EVENT_PROP_SELECTED_TEXT_TRACK: THEOplayerRCTTrackMetadataAggregator.selectedTextTrack(textTracks: textTracks),
@@ -44,19 +51,19 @@ class THEOplayerRCTTrackMetadataAggregator {
44
51
  }
45
52
 
46
53
  // MARK: TEXTTRACKS
47
- class func aggregatedTextTrackListInfo(textTracks: TextTrackList, metadataTracks: [[String:Any]]) -> [[String:Any]] {
54
+ class func aggregatedTextTrackListInfo(textTracks: TextTrackList, metadataTracks: [[String:Any]], player: THEOplayer) -> [[String:Any]] {
48
55
  var trackEntries:[[String:Any]] = metadataTracks
49
56
  guard textTracks.count > 0 else {
50
57
  return trackEntries
51
58
  }
52
59
  for i in 0...textTracks.count-1 {
53
60
  let textTrack: TextTrack = textTracks.get(i)
54
- trackEntries.append(THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack))
61
+ trackEntries.append(THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack, player: player))
55
62
  }
56
63
  return trackEntries
57
64
  }
58
65
 
59
- class func aggregatedTextTrackInfo(textTrack: TextTrack) -> [String:Any] {
66
+ class func aggregatedTextTrackInfo(textTrack: TextTrack, player: THEOplayer) -> [String:Any] {
60
67
  var entry: [String:Any] = [:]
61
68
  entry[PROP_ID] = textTrack.id
62
69
  entry[PROP_UID] = textTrack.uid
@@ -70,7 +77,7 @@ class THEOplayerRCTTrackMetadataAggregator {
70
77
  if !textTrack.cues.isEmpty {
71
78
  var cueList: [[String:Any]] = []
72
79
  for cue in textTrack.cues {
73
- cueList.append(THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: cue))
80
+ cueList.append(THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackCueInfo(textTrackCue: cue, player: player))
74
81
  }
75
82
  entry[PROP_CUES] = cueList
76
83
  }
@@ -91,17 +98,66 @@ class THEOplayerRCTTrackMetadataAggregator {
91
98
  }
92
99
 
93
100
  // MARK: TEXTTRACK CUES
94
- class func aggregatedTextTrackCueInfo(textTrackCue: TextTrackCue) -> [String:Any] {
101
+ class func aggregatedTextTrackCueInfo(textTrackCue: TextTrackCue, player: THEOplayer) -> [String:Any] {
95
102
  var entry: [String:Any] = [:]
96
103
  entry[PROP_ID] = textTrackCue.id
97
104
  entry[PROP_UID] = textTrackCue.uid
98
- entry[PROP_STARTTIME] = THEOplayerRCTTypeUtils.encodeInfNan((textTrackCue.startTime ?? 0) * 1000)
99
- entry[PROP_ENDTIME] = THEOplayerRCTTypeUtils.encodeInfNan((textTrackCue.endTime ?? 0) * 1000)
105
+ var startTime = textTrackCue.startTime
106
+ var endTime = textTrackCue.endTime
107
+ if let dateRangeCue = textTrackCue as? DateRangeCue,
108
+ let programDateTime = player.currentProgramDateTime?.timeIntervalSince1970 {
109
+ let currentTime = player.currentTime
110
+ let offset = programDateTime - currentTime
111
+ startTime = dateRangeCue.startDate.timeIntervalSince1970 - offset
112
+ endTime = dateRangeCue.endDate != nil ? dateRangeCue.endDate!.timeIntervalSince1970 - offset : .infinity
113
+ }
114
+ entry[PROP_STARTTIME] = THEOplayerRCTTypeUtils.encodeInfNan((startTime ?? 0) * 1000)
115
+ entry[PROP_ENDTIME] = THEOplayerRCTTypeUtils.encodeInfNan((endTime ?? 0) * 1000)
100
116
  if let content = textTrackCue.content {
101
117
  entry[PROP_CUE_CONTENT] = content
102
118
  } else if let contentString = textTrackCue.contentString {
103
119
  entry[PROP_CUE_CONTENT] = contentString
104
120
  }
121
+ if let dateRangeCue = textTrackCue as? DateRangeCue {
122
+ entry[PROP_START_DATE] = dateRangeCue.startDate.timeIntervalSince1970 * 1000
123
+ if let endDate = dateRangeCue.endDate {
124
+ entry[PROP_END_DATE] = endDate.timeIntervalSince1970 * 1000
125
+ }
126
+ if let attributeClass = dateRangeCue.attributeClass {
127
+ entry[PROP_ATTRIBUTE_CLASS] = attributeClass
128
+ }
129
+ if let duration = dateRangeCue.duration {
130
+ entry[PROP_DURATION] = THEOplayerRCTTypeUtils.encodeInfNan(duration * 1000)
131
+ }
132
+ if let plannedDuration = dateRangeCue.plannedDuration {
133
+ entry[PROP_PLANNED_DURATION] = THEOplayerRCTTypeUtils.encodeInfNan(plannedDuration * 1000)
134
+ }
135
+ entry[PROP_END_ON_NEXT] = dateRangeCue.endOnNext
136
+ let customAttributes = dateRangeCue.customAttributes
137
+ let customAttributesDict = customAttributes.getAttributesAsDictionary()
138
+ if !customAttributesDict.isEmpty {
139
+ var attributesEntry: [String:Any] = [:]
140
+ for (key, _) in customAttributesDict {
141
+ do {
142
+ // try reading as string
143
+ attributesEntry[key] = try customAttributes.getString(for: key)
144
+ } catch {
145
+ do {
146
+ // try reading as double
147
+ attributesEntry[key] = try customAttributes.getDouble(for: key)
148
+ } catch {
149
+ do {
150
+ // try reading as data
151
+ attributesEntry[key] = try customAttributes.getBytes(for: key)
152
+ } catch {
153
+ print("Unable to extract customAttribute from DateRange cue. Content is limited to String, Double or Data.")
154
+ }
155
+ }
156
+ }
157
+ }
158
+ entry[PROP_CUE_CUSTOM_ATTRIBUTES] = attributesEntry
159
+ }
160
+ }
105
161
  return entry
106
162
  }
107
163
 
@@ -37,6 +37,7 @@ public class THEOplayerRCTView: UIView {
37
37
  private var license: String?
38
38
  private var licenseUrl: String?
39
39
  private var chromeless: Bool = true
40
+ private var hlsDateRange: Bool = false
40
41
  private var config: THEOplayerConfiguration?
41
42
 
42
43
  // MARK: - Initialisation / view setup
@@ -108,6 +109,7 @@ public class THEOplayerRCTView: UIView {
108
109
  pip: self.playerPipConfiguration(),
109
110
  ads: self.playerAdsConfiguration(),
110
111
  cast: self.playerCastConfiguration(),
112
+ hlsDateRange: self.hlsDateRange,
111
113
  license: self.license,
112
114
  licenseUrl: self.licenseUrl))
113
115
  self.initAdsIntegration()
@@ -120,6 +122,7 @@ public class THEOplayerRCTView: UIView {
120
122
  private func initPlayer() -> THEOplayer? {
121
123
  self.player = THEOplayer(configuration: THEOplayerConfiguration(chromeless: self.chromeless,
122
124
  ads: self.playerAdsConfiguration(),
125
+ hlsDateRange: self.hlsDateRange,
123
126
  license: self.license,
124
127
  licenseUrl: self.licenseUrl,
125
128
  pip: self.playerPipConfiguration()))
@@ -168,6 +171,7 @@ public class THEOplayerRCTView: UIView {
168
171
  self.license = configDict["license"] as? String
169
172
  self.licenseUrl = configDict["licenseUrl"] as? String
170
173
  self.chromeless = configDict["chromeless"] as? Bool ?? true
174
+ self.hlsDateRange = configDict["hlsDateRange"] as? Bool ?? false
171
175
  self.parseAdsConfig(configDict: configDict)
172
176
  self.parseCastConfig(configDict: configDict)
173
177
  if DEBUG_VIEW { PrintUtils.printLog(logText: "[NATIVE] config prop updated.") }
@@ -211,8 +211,10 @@ class THEOplayerRCTContentProtectionAPI: RCTEventEmitter {
211
211
  THEOplayerRCTNetworkUtils.shared.requestFromUrl(url: url, method: method, body: bodyData, headers: headers) { data, statusCode, responseHeaders, error in
212
212
  if let responseError = error {
213
213
  print(CPI_TAG, "Certificate request failure: error = \(responseError.localizedDescription)")
214
+ completion(data, error)
214
215
  } else if statusCode >= 400 {
215
216
  PrintUtils.printLog(logText: "Certificate request failure: statusCode = \(statusCode)")
217
+ completion(data, nil)
216
218
  } else {
217
219
  if DEBUG_CONTENT_PROTECTION_API {print(CPI_TAG, "Certificate request success: statusCode = \(statusCode)") }
218
220
  let certificateRequest = CertificateRequest(url: urlString, method: method, headers: headers, body: bodyData)
@@ -278,8 +280,10 @@ class THEOplayerRCTContentProtectionAPI: RCTEventEmitter {
278
280
  THEOplayerRCTNetworkUtils.shared.requestFromUrl(url: url, method: method, body: bodyData, headers: headers) { data, statusCode, responseHeaders, error in
279
281
  if let responseError = error {
280
282
  print(CPI_TAG, "License request failure: error = \(responseError.localizedDescription)")
283
+ completion(data, error)
281
284
  } else if statusCode >= 400 {
282
285
  print(CPI_TAG, "License request failure: statusCode = \(statusCode)")
286
+ completion(data, nil)
283
287
  } else {
284
288
  if DEBUG_CONTENT_PROTECTION_API {print(CPI_TAG, "License request success: statusCode = \(statusCode)") }
285
289
  let licenseRequest = LicenseRequest(url: urlString, method: method, headers: headers, body: bodyData, fairplaySkdUrl: nil, useCredentials: false)
@@ -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';\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 * @remarks\n * <br/> - This parameter only applies to Web platforms.\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":""}
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 * @remarks\n * <br/> - This parameter only applies to Web platforms.\n */\n readonly chromeless?: boolean;\n\n /**\n * Sets whether DateRange tags from the playlists should be imported as a textTrack.\n */\n readonly hlsDateRange?: 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,19 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isDateRangeCue = isDateRangeCue;
7
+ /**
8
+ * Represents a cue of a HLS date range metadata text track.
9
+ *
10
+ * @public
11
+ */
12
+
13
+ /**
14
+ * Check whether a text track cue is of type DateRangeCue.
15
+ */
16
+ function isDateRangeCue(cue) {
17
+ return cue.customAttributes != undefined;
18
+ }
19
+ //# sourceMappingURL=DateRangeCue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["isDateRangeCue","cue","customAttributes","undefined"],"sources":["DateRangeCue.ts"],"sourcesContent":["import type { TextTrackCue } from './TextTrackCue';\n\n/**\n * Represents a cue of a HLS date range metadata text track.\n *\n * @public\n */\nexport interface DateRangeCue extends TextTrackCue {\n /**\n * The class of the date range cue.\n *\n * @remarks\n * <br/> - The class is a client-defined string specifying a set of attributes with associated value semantics.\n */\n class: string | undefined;\n\n /**\n * The playback position at which the date range cue becomes active, as a Date.\n */\n startDate: Date;\n\n /**\n * The playback position at which the date range cue becomes inactive, as a Date.\n */\n endDate: Date | undefined;\n\n /**\n * The duration of the date range cue, in milliseconds.\n */\n duration: number | undefined;\n\n /**\n * The planned duration of the date range cue, in milliseconds.\n *\n * @remarks\n * <br/> - This is used when the exact duration is not known yet.\n */\n plannedDuration: number | undefined;\n\n /**\n * Whether end-on-next is enabled for the date range cue.\n *\n * @remarks\n * <br/> - End-on-next results in the {@link DateRangeCue.endDate} of the date range cue becoming equal to the {@link DateRangeCue.startDate} of the next date range cue with the same {@link DateRangeCue.\"class\"}, once it is known.\n */\n endOnNext: boolean;\n\n /**\n * The SCTE 'cmd' splice_info_section of the date range cue.\n */\n scte35Cmd: ArrayBuffer | undefined;\n\n /**\n * The SCTE 'out' splice_info_section of the date range cue.\n */\n scte35Out: ArrayBuffer | undefined;\n\n /**\n * The SCTE 'in' splice_info_section of the date range cue.\n */\n scte35In: ArrayBuffer | undefined;\n\n /**\n * Custom attributes extracted from the cue source.\n */\n customAttributes: Record<string, string | number | ArrayBuffer>;\n}\n\n/**\n * Check whether a text track cue is of type DateRangeCue.\n */\nexport function isDateRangeCue(cue: TextTrackCue): cue is DateRangeCue {\n return (cue as DateRangeCue).customAttributes != undefined;\n}\n"],"mappings":";;;;;;AAEA;AACA;AACA;AACA;AACA;;AA8DA;AACA;AACA;AACO,SAASA,cAAcA,CAACC,GAAiB,EAAuB;EACrE,OAAQA,GAAG,CAAkBC,gBAAgB,IAAIC,SAAS;AAC5D"}
@@ -17,6 +17,8 @@ let TextTrackType = exports.TextTrackType = /*#__PURE__*/function (TextTrackType
17
17
  TextTrackType["srt"] = "srt";
18
18
  TextTrackType["ttml"] = "ttml";
19
19
  TextTrackType["webvtt"] = "webvtt";
20
+ TextTrackType["daterange"] = "daterange";
21
+ TextTrackType["eventstream"] = "eventstream";
20
22
  return TextTrackType;
21
23
  }({});
22
24
  let TextTrackKind = exports.TextTrackKind = /*#__PURE__*/function (TextTrackKind) {