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 +10 -0
- package/README.md +25 -15
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +13 -1
- package/ios/THEOplayerRCTMainEventHandler.swift +7 -1
- package/ios/THEOplayerRCTPlayerAPI.swift +9 -4
- package/ios/THEOplayerRCTSideloadedMetadataTrackHandler.swift +113 -0
- package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +17 -9
- package/ios/THEOplayerRCTTextTrackEventHandler.swift +9 -0
- package/ios/THEOplayerRCTTrackMetadataAggregator.swift +50 -7
- package/ios/THEOplayerRCTView.swift +11 -0
- package/package.json +1 -1
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. [
|
|
13
|
-
3. [
|
|
14
|
-
4. [
|
|
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
|
|
59
|
-
|
|
60
|
-
| [`@theoplayer/react-native-analytics-adobe`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Adobe analytics connector
|
|
61
|
-
| [`@theoplayer/react-native-analytics-agama`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Agama analytics connector
|
|
62
|
-
| [`@theoplayer/react-native-analytics-comscore`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Comscore analytics connector
|
|
63
|
-
| [`@theoplayer/react-native-analytics-conviva`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Conviva analytics connector
|
|
64
|
-
| [`@theoplayer/react-native-analytics-nielsen`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Nielsen analytics connector
|
|
65
|
-
| [`@theoplayer/react-native-analytics-youbora`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Youbora analytics connector
|
|
66
|
-
| [`@theoplayer/react-native-drm`](https://github.com/THEOplayer/react-native-theoplayer-drm) | Content protection (DRM) connectors
|
|
67
|
-
| [`@theoplayer/react-native-ui`](https://github.com/THEOplayer/react-native-theoplayer-ui) | React Native user interface
|
|
68
|
-
| [`@theoplayer/react-native-connector-template`](https://github.com/THEOplayer/react-native-theoplayer-connector-template) | A template for
|
|
69
|
+
| Package name | Purpose | Registry |
|
|
70
|
+
|---------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
71
|
+
| [`@theoplayer/react-native-analytics-adobe`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Adobe analytics connector | [](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 | [](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 | [](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 | [](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 | [](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 | [](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 | [](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 | [](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. | [](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
|
-
|
|
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
|
-
|
|
62
|
-
if let
|
|
63
|
-
|
|
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
|
-
|
|
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
|
|
126
|
-
|
|
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
|
|
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
|
|
50
|
+
return trackEntries
|
|
51
51
|
}
|
|
52
52
|
for i in 0...textTracks.count-1 {
|
|
53
53
|
let textTrack: TextTrack = textTracks.get(i)
|
|
54
|
-
|
|
54
|
+
trackEntries.append(THEOplayerRCTTrackMetadataAggregator.aggregatedTextTrackInfo(textTrack: textTrack))
|
|
55
55
|
}
|
|
56
|
-
return
|
|
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:)
|