react-native-theoplayer 2.13.0 → 2.14.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,16 @@ 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.14.0] - 23-09-25
9
+
10
+ ### Fixed
11
+
12
+ - Fixed an issue on Android where the MediaButtonReceiver would crash the app when it did not find a registered MediaBrowserService instance.
13
+
14
+ ### Added
15
+
16
+ - Added support for side-loaded metadata tracks on iOS.
17
+
8
18
  ## [2.13.0] - 23-09-15
9
19
 
10
20
  ### 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)
@@ -115,8 +115,20 @@ class ReactTHEOplayerContext private constructor(
115
115
  }
116
116
 
117
117
  private fun setPlaybackServiceEnabled(enabled: Boolean) {
118
+ // Toggle the MediaPlaybackService.
119
+ toggleComponent(enabled, ComponentName(reactContext.applicationContext, MediaPlaybackService::class.java))
120
+
121
+ // Also toggle any registered MediaButtonReceiver broadcast receiver.
122
+ // It will crash the app if it remains active and tries to find our disabled MediaBrowserService instance.
123
+ toggleComponent(enabled, ComponentName(reactContext.applicationContext, androidx.media.session.MediaButtonReceiver::class.java))
124
+ }
125
+
126
+ /**
127
+ * Enable or disable a receiver component.
128
+ */
129
+ private fun toggleComponent(enabled: Boolean, componentName: ComponentName) {
118
130
  reactContext.applicationContext.packageManager.setComponentEnabledSetting(
119
- ComponentName(reactContext.applicationContext, MediaPlaybackService::class.java),
131
+ componentName,
120
132
  if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
121
133
  else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
122
134
  PackageManager.DONT_KILL_APP
@@ -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.14.0",
4
4
  "description": "A THEOplayer video component for react-native.",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",