react-native-theoplayer 2.13.0 → 2.15.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 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
+ ## [2.15.0] - 23-09-26
9
+
10
+ ### Changed
11
+
12
+ - Upgraded the iOS side-loaded text track connector.
13
+
14
+ ### Fixed
15
+
16
+ - Fixed an issue where the Android mediaSession connector would still process media button events when the app was in the background, and `enableBackgroundPlayback` was false.
17
+ - Fixed an issue on Android where play-out would still start when the app was put to the background during initial buffering, and `enableBackgroundPlayback` was false.
18
+ - Fixed an issue on Android where the MediaButtonReceiver would crash the app when it did not find a registered MediaBrowserService instance, when setting `enableBackgroundPlayback` to false while backgrounding the app.
19
+
20
+ ## [2.14.0] - 23-09-25
21
+
22
+ ### Fixed
23
+
24
+ - Fixed an issue on Android where the MediaButtonReceiver would crash the app when it did not find a registered MediaBrowserService instance.
25
+
26
+ ### Added
27
+
28
+ - Added support for side-loaded metadata tracks on iOS.
29
+
8
30
  ## [2.13.0] - 23-09-15
9
31
 
10
32
  ### Fixed
package/README.md CHANGED
@@ -9,9 +9,11 @@ This projects falls under the license as defined in https://github.com/THEOplaye
9
9
  ## Table of Contents
10
10
 
11
11
  1. [Overview](#overview)
12
- 2. [How to use these guides](#how-to-use-these-guides)
13
- 3. [Prerequisites](#prerequisites)
14
- 4. [Getting Started](#getting-started)
12
+ 2. [Prerequisites](#prerequisites)
13
+ 3. [How to use these guides](#how-to-use-these-guides)
14
+ 4. [Features](#features)
15
+ 5. [Available connectors](#available-connectors)
16
+ 6. [Getting Started](#getting-started)
15
17
 
16
18
  ## Overview
17
19
 
@@ -50,22 +52,31 @@ These are guides on how to use the THEOplayer React Native SDK in your React Nat
50
52
  linearly or by searching the specific section. It is recommended that you have a basic understanding of how
51
53
  React Native works to speed up the way of working with THEOplayer React Native SDK.
52
54
 
55
+ ## Features
56
+
57
+ Depending on the platform on which the application is deployed, a different set of features is available.
58
+
59
+ If a feature missing, additional help is needed, or you need to extend the package,
60
+ please reach out to us for support.
61
+
62
+ <img src="./doc/features.svg">
63
+
53
64
  ## Available connectors
54
65
 
55
66
  The `react-native-theoplayer` package can be combined with any number of connectors to provide extra
56
67
  functionality. Currently, the following connectors are available:
57
68
 
58
- | Package name | Purpose | Registry |
59
- |---------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
60
- | [`@theoplayer/react-native-analytics-adobe`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Adobe analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-adobe)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-adobe) |
61
- | [`@theoplayer/react-native-analytics-agama`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Agama analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-agama)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-agama) |
62
- | [`@theoplayer/react-native-analytics-comscore`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Comscore analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-comscore)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-comscore) |
63
- | [`@theoplayer/react-native-analytics-conviva`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Conviva analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-conviva)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-conviva) |
64
- | [`@theoplayer/react-native-analytics-nielsen`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Nielsen analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-nielsen)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-nielsen) |
65
- | [`@theoplayer/react-native-analytics-youbora`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Youbora analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-youbora)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-youbora) |
66
- | [`@theoplayer/react-native-drm`](https://github.com/THEOplayer/react-native-theoplayer-drm) | Content protection (DRM) connectors | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-drm)](https://www.npmjs.com/package/@theoplayer/react-native-drm) |
67
- | [`@theoplayer/react-native-ui`](https://github.com/THEOplayer/react-native-theoplayer-ui) | React Native user interface | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-ui)](https://www.npmjs.com/package/@theoplayer/react-native-ui) |
68
- | [`@theoplayer/react-native-connector-template`](https://github.com/THEOplayer/react-native-theoplayer-connector-template) | A template for `react-native-theoplayer` connectors. | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-connector-template)](https://www.npmjs.com/package/@theoplayer/react-native-connector-template) |
69
+ | Package name | Purpose | Registry |
70
+ |---------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
71
+ | [`@theoplayer/react-native-analytics-adobe`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Adobe analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-adobe)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-adobe) |
72
+ | [`@theoplayer/react-native-analytics-agama`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Agama analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-agama)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-agama) |
73
+ | [`@theoplayer/react-native-analytics-comscore`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Comscore analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-comscore)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-comscore) |
74
+ | [`@theoplayer/react-native-analytics-conviva`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Conviva analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-conviva)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-conviva) |
75
+ | [`@theoplayer/react-native-analytics-nielsen`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Nielsen analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-nielsen)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-nielsen) |
76
+ | [`@theoplayer/react-native-analytics-youbora`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Youbora analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-youbora)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-youbora) |
77
+ | [`@theoplayer/react-native-drm`](https://github.com/THEOplayer/react-native-theoplayer-drm) | Content protection (DRM) connectors | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-drm)](https://www.npmjs.com/package/@theoplayer/react-native-drm) |
78
+ | [`@theoplayer/react-native-ui`](https://github.com/THEOplayer/react-native-theoplayer-ui) | React Native user interface | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-ui)](https://www.npmjs.com/package/@theoplayer/react-native-ui) |
79
+ | [`@theoplayer/react-native-connector-template`](https://github.com/THEOplayer/react-native-theoplayer-connector-template) | A template for<br/>`react-native-theoplayer` connectors. | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-connector-template)](https://www.npmjs.com/package/@theoplayer/react-native-connector-template) |
69
80
 
70
81
  ## Getting Started
71
82
 
@@ -81,7 +92,6 @@ and discussed in the next section. Finally, an overview of features, limitations
81
92
  - [Getting started on Web](./doc/creating-minimal-app.md#getting-started-on-web)
82
93
  - [The THEOplayerView component](./doc/theoplayerview-component.md)
83
94
  - [The example application](./doc/example-app.md)
84
- - [Features](./doc/features.md)
85
95
  - Knowledge Base
86
96
  - [Adaptive Bitrate (ABR)](./doc/abr.md)
87
97
  - [Advertisements](./doc/ads.md)
@@ -109,7 +109,7 @@ dependencies {
109
109
  def theoplayer_sdk_version = safeExtGet('THEOplayer_sdk', '[5.10,6.0)')
110
110
 
111
111
  // def theoplayer_mediasession_version = safeExtGet('THEOplayer_mediasession', theoplayer_sdk_version)
112
- def theoplayer_mediasession_version = "5.2.0-local"
112
+ def theoplayer_mediasession_version = "5.11.0-local"
113
113
  def enabledV4 = theoplayer_sdk_version.toString().startsWith("4.")
114
114
  def core_prefix = enabledV4 ? 'unified' : 'core'
115
115
  def integration_prefix = enabledV4 ? 'unified' : 'integration'
@@ -3,7 +3,7 @@
3
3
  <modelVersion>4.0.0</modelVersion>
4
4
  <groupId>com.theoplayer.android-connector</groupId>
5
5
  <artifactId>mediasession</artifactId>
6
- <version>5.2.0-local</version>
6
+ <version>5.11.0-local</version>
7
7
  <packaging>aar</packaging>
8
8
  <dependencies>
9
9
  <dependency>
@@ -3,11 +3,11 @@
3
3
  <groupId>com.theoplayer.android-connector</groupId>
4
4
  <artifactId>mediasession</artifactId>
5
5
  <versioning>
6
- <latest>5.2.0-local</latest>
7
- <release>5.2.0-local</release>
6
+ <latest>5.11.0-local</latest>
7
+ <release>5.11.0-local</release>
8
8
  <versions>
9
- <version>5.2.0-local</version>
9
+ <version>5.11.0-local</version>
10
10
  </versions>
11
- <lastUpdated>20230531125422</lastUpdated>
11
+ <lastUpdated>20230926085823</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -76,6 +76,7 @@ class ReactTHEOplayerContext private constructor(
76
76
  var imaIntegration: GoogleImaIntegration? = null
77
77
  var castIntegration: CastIntegration? = null
78
78
  var wasPlayingOnHostPause: Boolean = false
79
+ var isHostPaused: Boolean = false
79
80
 
80
81
  private val isBackgroundAudioEnabled: Boolean
81
82
  get() = backgroundAudioConfig.enabled
@@ -115,8 +116,20 @@ class ReactTHEOplayerContext private constructor(
115
116
  }
116
117
 
117
118
  private fun setPlaybackServiceEnabled(enabled: Boolean) {
119
+ // Toggle the MediaPlaybackService.
120
+ toggleComponent(enabled, ComponentName(reactContext.applicationContext, MediaPlaybackService::class.java))
121
+
122
+ // Also toggle any registered MediaButtonReceiver broadcast receiver.
123
+ // It will crash the app if it remains active and tries to find our disabled MediaBrowserService instance.
124
+ toggleComponent(enabled, ComponentName(reactContext.applicationContext, androidx.media.session.MediaButtonReceiver::class.java))
125
+ }
126
+
127
+ /**
128
+ * Enable or disable a receiver component.
129
+ */
130
+ private fun toggleComponent(enabled: Boolean, componentName: ComponentName) {
118
131
  reactContext.applicationContext.packageManager.setComponentEnabledSetting(
119
- ComponentName(reactContext.applicationContext, MediaPlaybackService::class.java),
132
+ componentName,
120
133
  if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
121
134
  else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
122
135
  PackageManager.DONT_KILL_APP
@@ -136,15 +149,25 @@ class ReactTHEOplayerContext private constructor(
136
149
 
137
150
  if (BuildConfig.USE_PLAYBACK_SERVICE) {
138
151
  if (prevConfig?.enabled != true && config.enabled) {
139
- // Enabling background playback
152
+ // Enable & bind background playback
140
153
  setPlaybackServiceEnabled(true)
141
154
  bindMediaPlaybackService()
142
155
  } else if (prevConfig?.enabled == true) {
143
- // Disabling background playback
156
+ // First disable the MediaPlaybackService and MediaButtonReceiver so that no more media
157
+ // button events can be captured.
158
+ setPlaybackServiceEnabled(false)
159
+
160
+ // Stop & unbind MediaPlaybackService.
144
161
  binder?.stopForegroundService()
145
162
  unbindMediaPlaybackService()
146
- setPlaybackServiceEnabled(false)
163
+
164
+ // Create a new media session.
147
165
  initDefaultMediaSession()
166
+
167
+ // If the app is currently backgrounded, apply state changes.
168
+ if (isHostPaused) {
169
+ applyHostPaused()
170
+ }
148
171
  }
149
172
  }
150
173
  }
@@ -239,8 +262,9 @@ class ReactTHEOplayerContext private constructor(
239
262
  debug = BuildConfig.LOG_MEDIASESSION_EVENTS
240
263
  player = this@ReactTHEOplayerContext.player
241
264
 
242
- // Set mediaSession active
243
- setActive(true)
265
+ // Set mediaSession active and ready to receive media button events, but not if the player
266
+ // is backgrounded.
267
+ setActive(!isHostPaused)
244
268
  }
245
269
  }
246
270
 
@@ -332,11 +356,19 @@ class ReactTHEOplayerContext private constructor(
332
356
  * The host activity is paused.
333
357
  */
334
358
  fun onHostPause() {
359
+ isHostPaused = true
360
+ applyHostPaused()
361
+ }
362
+
363
+ private fun applyHostPaused() {
335
364
  // Keep current playing state when going to background
336
365
  wasPlayingOnHostPause = !player.isPaused
337
366
  playerView.onPause()
338
367
  if (!isBackgroundAudioEnabled) {
339
368
  mediaSessionConnector?.setActive(false)
369
+
370
+ // The player pauses and goes to the background, we can abandon audio focus.
371
+ audioFocusManager?.abandonAudioFocus()
340
372
  }
341
373
  }
342
374
 
@@ -344,6 +376,7 @@ class ReactTHEOplayerContext private constructor(
344
376
  * The host activity is resumed.
345
377
  */
346
378
  fun onHostResume() {
379
+ isHostPaused = false
347
380
  mediaSessionConnector?.setActive(true)
348
381
  playerView.onResume()
349
382
  audioFocusManager?.retrieveAudioFocus()
@@ -7,6 +7,7 @@ public class THEOplayerRCTMainEventHandler {
7
7
  // MARK: Members
8
8
  private weak var player: THEOplayer?
9
9
  private weak var presentationModeContext: THEOplayerRCTPresentationModeContext?
10
+ private var metadataTracksInfo: [[String:Any]] = []
10
11
 
11
12
  // MARK: Events
12
13
  var onNativePlay: RCTDirectEventBlock?
@@ -67,6 +68,10 @@ public class THEOplayerRCTMainEventHandler {
67
68
  self.attachListeners()
68
69
  }
69
70
 
71
+ func setMetadataTracksInfo(metadataTracksInfo: [[String:Any]]) {
72
+ self.metadataTracksInfo = metadataTracksInfo
73
+ }
74
+
70
75
  // MARK: - attach/dettach main player Listeners
71
76
  private func attachListeners() {
72
77
  guard let player = self.player else {
@@ -268,8 +273,9 @@ public class THEOplayerRCTMainEventHandler {
268
273
  self.loadedMetadataListener = player.addEventListener(type: PlayerEventTypes.LOADED_META_DATA) { [weak self, weak player] event in
269
274
  if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received LOADED_META_DATA event from THEOplayer") }
270
275
  if let wplayer = player,
276
+ let welf = self,
271
277
  let forwardedLoadedMetadataEvent = self?.onNativeLoadedMetadata {
272
- let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: wplayer)
278
+ let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: wplayer, metadataTracksInfo: welf.metadataTracksInfo)
273
279
  print(metadata)
274
280
  forwardedLoadedMetadataEvent(metadata)
275
281
  }
@@ -57,10 +57,15 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
57
57
  @objc(setSource:src:)
58
58
  func setSource(_ node: NSNumber, src: NSDictionary) -> Void {
59
59
  DispatchQueue.main.async {
60
- if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView,
61
- let srcDescription = THEOplayerRCTSourceDescriptionBuilder.buildSourceDescription(src) {
62
- if let player = theView.player {
63
- self.setNewSourceDescription(player: player, srcDescription: srcDescription)
60
+ if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView {
61
+ let (sourceDescription, metadataTrackDescriptions) = THEOplayerRCTSourceDescriptionBuilder.buildSourceDescription(src)
62
+ if let srcDescription = sourceDescription {
63
+ if let player = theView.player {
64
+ self.setNewSourceDescription(player: player, srcDescription: srcDescription)
65
+ theView.processMetadataTracks(metadataTrackDescriptions: metadataTrackDescriptions)
66
+ }
67
+ } else {
68
+ if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Failed to update THEOplayer source.") }
64
69
  }
65
70
  } else {
66
71
  if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Failed to update THEOplayer source.") }
@@ -0,0 +1,113 @@
1
+ // THEOplayerRCTMetadataAggregator.swift
2
+
3
+ import Foundation
4
+ import THEOplayerSDK
5
+
6
+ struct Cue {
7
+ var startTime: Int64
8
+ var endTime: Int64
9
+ var cueContent: String
10
+ }
11
+
12
+ class THEOplayerRCTSideloadedMetadataTrackHandler {
13
+
14
+ class func parseVtt(_ urlString: String, completion: @escaping ([Cue]?) -> Void) {
15
+ guard let url = URL(string: urlString) else {
16
+ completion(nil)
17
+ return
18
+ }
19
+
20
+ let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
21
+ guard let data = data, error == nil else {
22
+ completion(nil)
23
+ return
24
+ }
25
+
26
+ if let vttString = String(data: data, encoding: .utf8) {
27
+ let cues = THEOplayerRCTSideloadedMetadataTrackHandler.parseVTTString(vttString)
28
+ completion(cues)
29
+ } else {
30
+ completion(nil)
31
+ }
32
+ }
33
+
34
+ task.resume()
35
+ }
36
+
37
+ private class func parseVTTString(_ vttString: String) -> [Cue] {
38
+ var cues: [Cue] = []
39
+ var currentCue: Cue?
40
+
41
+ let separator = THEOplayerRCTSideloadedMetadataTrackHandler.separatorSequence(vttString)
42
+ let lines = vttString.components(separatedBy: separator)
43
+ for line in lines {
44
+ if line.isEmpty { // process unprocessed cue to list
45
+ if let cue = currentCue {
46
+ cues.append(cue)
47
+ currentCue = nil
48
+ }
49
+ } else if currentCue == nil {
50
+ let timeComponents = line.components(separatedBy: " --> ") // start new cue with timestamps
51
+ if timeComponents.count == 2 {
52
+ currentCue = Cue(startTime: Self.parseTimeToMsecs(timeComponents[0]) ?? 0,
53
+ endTime: Self.parseTimeToMsecs(timeComponents[1]) ?? 0,
54
+ cueContent: "")
55
+ }
56
+ } else {
57
+ // Append to the content
58
+ if currentCue!.cueContent.isEmpty {
59
+ currentCue!.cueContent = "\(line)" // set cue content
60
+ } else {
61
+ currentCue!.cueContent += "\(separator)\(line)" // append cue content
62
+ }
63
+ }
64
+ }
65
+
66
+ return cues
67
+ }
68
+
69
+ private class func separatorSequence(_ dataString: String) -> String {
70
+ return dataString.contains("\r\n") ? "\r\n" : "\n"
71
+ }
72
+
73
+ private class func parseTimeToMsecs(_ timeString: String) -> Int64? {
74
+ var tString = timeString
75
+ let formatter = DateFormatter()
76
+ formatter.dateFormat = "mm:ss.SSS"
77
+ if formatter.date(from: timeString) != nil {
78
+ tString = "00:\(tString)"
79
+ }
80
+ formatter.dateFormat = "HH:mm:ss.SSS"
81
+ if formatter.date(from: tString) != nil {
82
+ return Self.totalMilliSeconds(fromTimeIntervalString: tString)
83
+ }
84
+ return nil
85
+ }
86
+
87
+ private class func totalMilliSeconds(fromTimeIntervalString timeIntervalString: String) -> Int64? {
88
+ let mainComponents = timeIntervalString.components(separatedBy: ".")
89
+ // Ensure we have msecs
90
+ guard mainComponents.count == 2 else {
91
+ return nil
92
+ }
93
+
94
+ let components = mainComponents[0].components(separatedBy: ":")
95
+
96
+ // Ensure we have at least minutes, seconds, and hours
97
+ guard components.count == 3 else {
98
+ return nil
99
+ }
100
+
101
+ // Parse hours, minutes, and seconds
102
+ if let hours = Int64(components[0]), let minutes = Int64(components[1]), let seconds = Int64(components[2]) {
103
+ let totalSeconds = hours * 3600 + minutes * 60 + seconds
104
+
105
+ // If there are milliseconds, add them
106
+ if let milliseconds = Int64(mainComponents[1]) {
107
+ return (totalSeconds * 1000) + milliseconds
108
+ }
109
+ }
110
+
111
+ return nil
112
+ }
113
+ }
@@ -59,12 +59,12 @@ class THEOplayerRCTSourceDescriptionBuilder {
59
59
 
60
60
  /**
61
61
  Builds a THEOplayer SourceDescription that can be passed as a source for the THEOplayer.
62
- - returns: a THEOplayer TypedSource. In case of SSAI we support GoogleDAITypedSource with GoogleDAIVodConfiguration or GoogleDAILiveConfiguration
62
+ - returns: a THEOplayer TypedSource and an array containing possible sideloaded metadataTracks. In case of SSAI we support GoogleDAITypedSource with GoogleDAIVodConfiguration or GoogleDAILiveConfiguration
63
63
  */
64
- static func buildSourceDescription(_ sourceData: NSDictionary) -> SourceDescription? {
64
+ static func buildSourceDescription(_ sourceData: NSDictionary) -> (SourceDescription?, [TextTrackDescription]?) {
65
65
  // 1. Extract "sources"
66
66
  guard let sourcesData = sourceData[SD_PROP_SOURCES] else {
67
- return nil
67
+ return (nil, nil)
68
68
  }
69
69
 
70
70
  var typedSources: [TypedSource] = []
@@ -77,7 +77,7 @@ class THEOplayerRCTSourceDescriptionBuilder {
77
77
  if DEBUG_SOURCE_DESCRIPTION_BUIDER {
78
78
  PrintUtils.printLog(logText: "[NATIVE] Could not create THEOplayer TypedSource from sourceData array")
79
79
  }
80
- return nil
80
+ return (nil, nil)
81
81
  }
82
82
  }
83
83
  }
@@ -89,7 +89,7 @@ class THEOplayerRCTSourceDescriptionBuilder {
89
89
  if DEBUG_SOURCE_DESCRIPTION_BUIDER {
90
90
  PrintUtils.printLog(logText: "[NATIVE] Could not create THEOplayer TypedSource from sourceData")
91
91
  }
92
- return nil
92
+ return (nil, nil)
93
93
  }
94
94
  }
95
95
 
@@ -98,16 +98,22 @@ class THEOplayerRCTSourceDescriptionBuilder {
98
98
 
99
99
  // 3. extract 'textTracks'
100
100
  var textTrackDescriptions: [TextTrackDescription]?
101
+ var metadataTrackDescriptions: [TextTrackDescription]?
101
102
  if let textTracksDataArray = sourceData[SD_PROP_TEXTTRACKS] as? [[String:Any]] {
102
103
  textTrackDescriptions = []
104
+ metadataTrackDescriptions = []
103
105
  for textTracksData in textTracksDataArray {
104
106
  if let textTrackDescription = THEOplayerRCTSourceDescriptionBuilder.buildTextTrackDescriptions(textTracksData) {
105
- textTrackDescriptions?.append(textTrackDescription)
107
+ if textTrackDescription.kind == .metadata {
108
+ metadataTrackDescriptions?.append(textTrackDescription)
109
+ } else {
110
+ textTrackDescriptions?.append(textTrackDescription)
111
+ }
106
112
  } else {
107
113
  if DEBUG_SOURCE_DESCRIPTION_BUIDER {
108
114
  PrintUtils.printLog(logText: "[NATIVE] Could not create THEOplayer TextTrackDescription from textTrackData array")
109
115
  }
110
- return nil
116
+ return (nil, nil)
111
117
  }
112
118
  }
113
119
 
@@ -122,12 +128,14 @@ class THEOplayerRCTSourceDescriptionBuilder {
122
128
  metadataDescription = THEOplayerRCTSourceDescriptionBuilder.buildMetaDataDescription(metadataData)
123
129
  }
124
130
 
125
- // 6. construct and return SourceDescription
126
- return SourceDescription(sources: typedSources,
131
+ // 6. construct the SourceDescription
132
+ let sourceDescription = SourceDescription(sources: typedSources,
127
133
  textTracks: textTrackDescriptions,
128
134
  ads: adsDescriptions,
129
135
  poster: poster,
130
136
  metadata: metadataDescription)
137
+
138
+ return (sourceDescription, metadataTrackDescriptions)
131
139
  }
132
140
 
133
141
  // MARK: Private build methods
@@ -37,6 +37,15 @@ class THEOplayerRCTTextTrackEventHandler {
37
37
  self.attachListeners()
38
38
  }
39
39
 
40
+ func triggerAddMetadataTrackEvent(metadataTrackInfo: [String:Any]) {
41
+ if let addTrackEvent = self.onNativeTextTrackListEvent {
42
+ addTrackEvent([
43
+ "track" : metadataTrackInfo,
44
+ "type" : TrackListEventType.ADD_TRACK.rawValue
45
+ ])
46
+ }
47
+ }
48
+
40
49
  // MARK: - attach/dettach textTrackList Listeners
41
50
  private func attachListeners() {
42
51
  guard let player = self.player else {
@@ -28,12 +28,12 @@ let PROP_SRC: String = "src"
28
28
 
29
29
  class THEOplayerRCTTrackMetadataAggregator {
30
30
 
31
- class func aggregateTrackMetadata(player: THEOplayer) -> [String:Any] {
31
+ class func aggregateTrackMetadata(player: THEOplayer, metadataTracksInfo: [[String:Any]]) -> [String:Any] {
32
32
  let textTracks: TextTrackList = player.textTracks
33
33
  let audioTracks: AudioTrackList = player.audioTracks
34
34
  let videoTracks: VideoTrackList = player.videoTracks
35
35
  return [
36
- EVENT_PROP_TEXT_TRACKS : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackListInfo(textTracks: textTracks),
36
+ EVENT_PROP_TEXT_TRACKS : THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackListInfo(textTracks: textTracks, metadataTracks: metadataTracksInfo),
37
37
  EVENT_PROP_AUDIO_TRACKS : THEOplayerRCTTrackMetadataAggregator.aggregatedAudioTrackListInfo(audioTracks: audioTracks),
38
38
  EVENT_PROP_VIDEO_TRACKS : THEOplayerRCTTrackMetadataAggregator.aggregatedVideoTrackListInfo(videoTracks: videoTracks),
39
39
  EVENT_PROP_SELECTED_TEXT_TRACK: THEOplayerRCTTrackMetadataAggregator.selectedTextTrack(textTracks: textTracks),
@@ -44,16 +44,16 @@ class THEOplayerRCTTrackMetadataAggregator {
44
44
  }
45
45
 
46
46
  // MARK: TEXTTRACKS
47
- class func aggregatedTextTrackListInfo(textTracks: TextTrackList) -> [[String:Any]] {
48
- var textTrackEntries:[[String:Any]] = []
47
+ class func aggregatedTextTrackListInfo(textTracks: TextTrackList, metadataTracks: [[String:Any]]) -> [[String:Any]] {
48
+ var trackEntries:[[String:Any]] = metadataTracks
49
49
  guard textTracks.count > 0 else {
50
- return textTrackEntries
50
+ return trackEntries
51
51
  }
52
52
  for i in 0...textTracks.count-1 {
53
53
  let textTrack: TextTrack = textTracks.get(i)
54
- textTrackEntries.append(THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack))
54
+ trackEntries.append(THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack))
55
55
  }
56
- return textTrackEntries
56
+ return trackEntries
57
57
  }
58
58
 
59
59
  class func aggregatedTextTrackInfo(textTrack: TextTrack) -> [String:Any] {
@@ -183,4 +183,47 @@ class THEOplayerRCTTrackMetadataAggregator {
183
183
  }
184
184
  return 0
185
185
  }
186
+
187
+ class func aggregatedMetadataTrackInfo(metadataTrackDescriptions: [TextTrackDescription], completed: (([[String:Any]]) -> Void)? ) {
188
+ var trackIndex = 0
189
+ var tracksInfo: [[String:Any]] = []
190
+ for trackDescription in metadataTrackDescriptions {
191
+ guard trackDescription.kind == .metadata, trackDescription.format == .WebVTT else { continue }
192
+
193
+ let urlString = trackDescription.src.absoluteString
194
+ THEOplayerRCTSideloadedMetadataTrackHandler.parseVtt(urlString) { cueArray in
195
+ if let cues = cueArray {
196
+ var track: [String:Any] = [:]
197
+ let trackUid = 1000 + trackIndex
198
+ track[PROP_ID] = UUID().uuidString
199
+ track[PROP_UID] = trackUid
200
+ track[PROP_KIND] = trackDescription.kind?._rawValue
201
+ track[PROP_LANGUAGE] = trackDescription.srclang
202
+ track[PROP_MODE] = "hidden"
203
+ track[PROP_LABEL] = trackDescription.label ?? "no label"
204
+ track[PROP_TYPE] = "webvtt"
205
+ track[PROP_SRC] = trackDescription.src.absoluteString
206
+ var cueList: [[String:Any]] = []
207
+ var cueIndex = 0
208
+ for c in cues {
209
+ //print("[CUE] \(c.startTime) --> \(c.endTime): \(c.cueContent)")
210
+ var cue: [String:Any] = [:]
211
+ cue[PROP_ID] = cueIndex
212
+ cue[PROP_UID] = (trackUid * 1000000) + cueIndex
213
+ cue[PROP_STARTTIME] = c.startTime
214
+ cue[PROP_ENDTIME] = c.endTime
215
+ cue[PROP_CUE_CONTENT] = c.cueContent
216
+ cueList.append(cue)
217
+ cueIndex += 1
218
+ }
219
+ track[PROP_CUES] = cueList
220
+ tracksInfo.append(track)
221
+ if tracksInfo.count == metadataTrackDescriptions.count {
222
+ completed?(tracksInfo)
223
+ }
224
+ }
225
+ }
226
+ trackIndex += 1
227
+ }
228
+ }
186
229
  }
@@ -150,6 +150,17 @@ public class THEOplayerRCTView: UIView {
150
150
  if DEBUG_THEOPLAYER_INTERACTION { PrintUtils.printLog(logText: "[NATIVE] THEOplayer instance destroyed.") }
151
151
  }
152
152
 
153
+ func processMetadataTracks(metadataTrackDescriptions: [TextTrackDescription]?) {
154
+ if let trackDescriptions = metadataTrackDescriptions {
155
+ THEOplayerRCTTrackMetadataAggregator.aggregatedMetadataTrackInfo(metadataTrackDescriptions: trackDescriptions) { tracksInfo in
156
+ self.mainEventHandler.setMetadataTracksInfo(metadataTracksInfo: tracksInfo)
157
+ for trackInfo in tracksInfo {
158
+ self.textTrackEventHandler.triggerAddMetadataTrackEvent(metadataTrackInfo: trackInfo)
159
+ }
160
+ }
161
+ }
162
+ }
163
+
153
164
  // MARK: - Property bridging (config)
154
165
 
155
166
  @objc(setConfig:)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-theoplayer",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "A THEOplayer video component for react-native.",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -47,7 +47,7 @@ Pod::Spec.new do |s|
47
47
  end
48
48
  if theofeatures.include?("SIDELOADED_TEXTTRACKS")
49
49
  puts "Adding THEOplayer-Connector-SideloadedSubtitle"
50
- s.dependency "THEOplayer-Connector-SideloadedSubtitle", "~> 5.11"
50
+ s.dependency "THEOplayer-Connector-SideloadedSubtitle", "6.0"
51
51
  end
52
52
  end
53
53