react-native-tpstreams 0.2.25 → 0.2.27
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/android/build.gradle +1 -1
- package/android/src/main/java/com/tpstreams/TPStreamsRNPackage.kt +0 -1
- package/ios/TPStreamsDownloadModule.mm +43 -2
- package/ios/TPStreamsDownloadModule.swift +257 -5
- package/ios/TPStreamsModule.swift +5 -3
- package/ios/TPStreamsRNPlayerView.swift +121 -139
- package/lib/module/TPStreamsPlayer.js +1 -1
- package/lib/module/TPStreamsPlayer.js.map +1 -1
- package/lib/module/index.js +2 -8
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/TPStreamsPlayerViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/TPStreamsPlayer.tsx +2 -2
- package/src/TPStreamsPlayerViewNativeComponent.ts +82 -0
- package/src/index.tsx +2 -7
- package/android/src/main/java/com/tpstreams/TPStreamsDownloadsModule.kt +0 -143
- package/ios/TPStreamsDownloadsModule.mm +0 -32
- package/ios/TPStreamsDownloadsModule.swift +0 -92
- package/lib/typescript/spec/NativeTPStreams.d.ts +0 -7
- package/lib/typescript/spec/NativeTPStreams.d.ts.map +0 -1
- package/lib/typescript/spec/NativeTPStreamsDownloads.d.ts +0 -24
- package/lib/typescript/spec/NativeTPStreamsDownloads.d.ts.map +0 -1
- package/lib/typescript/spec/TPStreamsPlayerViewNativeComponent.d.ts.map +0 -1
- package/spec/NativeTPStreams.ts +0 -8
- package/spec/NativeTPStreamsDownloads.ts +0 -32
- /package/{spec → lib/module}/TPStreamsPlayerViewNativeComponent.ts +0 -0
- /package/lib/typescript/{spec → src}/TPStreamsPlayerViewNativeComponent.d.ts +0 -0
package/android/build.gradle
CHANGED
|
@@ -17,7 +17,6 @@ class TPStreamsRNPackage : ReactPackage {
|
|
|
17
17
|
val modules: MutableList<NativeModule> = ArrayList()
|
|
18
18
|
modules.add(TPStreamsRNModule(reactContext))
|
|
19
19
|
modules.add(TPStreamsDownloadModule(reactContext))
|
|
20
|
-
modules.add(TPStreamsDownloadsModule(reactContext))
|
|
21
20
|
return modules
|
|
22
21
|
}
|
|
23
22
|
}
|
|
@@ -1,7 +1,48 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
2
3
|
|
|
3
|
-
@interface RCT_EXTERN_MODULE(TPStreamsDownload,
|
|
4
|
+
@interface RCT_EXTERN_MODULE(TPStreamsDownload, RCTEventEmitter)
|
|
4
5
|
|
|
5
|
-
//
|
|
6
|
+
// Download Progress Listener Methods
|
|
7
|
+
RCT_EXTERN_METHOD(addDownloadProgressListener:(RCTPromiseResolveBlock)resolve
|
|
8
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
9
|
+
|
|
10
|
+
RCT_EXTERN_METHOD(removeDownloadProgressListener:(RCTPromiseResolveBlock)resolve
|
|
11
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
12
|
+
|
|
13
|
+
// Download Control Methods
|
|
14
|
+
RCT_EXTERN_METHOD(pauseDownload:(NSString *)videoId
|
|
15
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
16
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
17
|
+
|
|
18
|
+
RCT_EXTERN_METHOD(resumeDownload:(NSString *)videoId
|
|
19
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
20
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
21
|
+
|
|
22
|
+
RCT_EXTERN_METHOD(removeDownload:(NSString *)videoId
|
|
23
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
24
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
25
|
+
|
|
26
|
+
// Download Status Methods
|
|
27
|
+
RCT_EXTERN_METHOD(isDownloaded:(NSString *)videoId
|
|
28
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
29
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
30
|
+
|
|
31
|
+
RCT_EXTERN_METHOD(isDownloading:(NSString *)videoId
|
|
32
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
33
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
34
|
+
|
|
35
|
+
RCT_EXTERN_METHOD(isPaused:(NSString *)videoId
|
|
36
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
37
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
38
|
+
|
|
39
|
+
RCT_EXTERN_METHOD(getDownloadStatus:(NSString *)videoId
|
|
40
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
41
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
42
|
+
|
|
43
|
+
RCT_EXTERN_METHOD(getAllDownloads:(RCTPromiseResolveBlock)resolve
|
|
44
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
45
|
+
|
|
46
|
+
RCT_EXTERN_METHOD(supportedEvents)
|
|
6
47
|
|
|
7
48
|
@end
|
|
@@ -1,13 +1,265 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import React
|
|
3
|
+
import TPStreamsSDK
|
|
4
|
+
|
|
5
|
+
private enum PlayerConstants {
|
|
6
|
+
static let statusNotDownloaded = "NotDownloaded"
|
|
7
|
+
static let statusDownloading = "Downloading"
|
|
8
|
+
static let statusPaused = "Paused"
|
|
9
|
+
static let statusCompleted = "Completed"
|
|
10
|
+
static let statusFailed = "Failed"
|
|
11
|
+
static let statusUnknown = "Unknown"
|
|
12
|
+
}
|
|
3
13
|
|
|
4
14
|
@objc(TPStreamsDownload)
|
|
5
|
-
class TPStreamsDownloadModule:
|
|
15
|
+
class TPStreamsDownloadModule: RCTEventEmitter, TPStreamsDownloadDelegate {
|
|
16
|
+
|
|
17
|
+
private let downloadManager = TPStreamsDownloadManager.shared
|
|
18
|
+
private var isListening = false
|
|
19
|
+
|
|
20
|
+
override init() {
|
|
21
|
+
super.init()
|
|
22
|
+
downloadManager.setTPStreamsDownloadDelegate(tpStreamsDownloadDelegate: self)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@objc
|
|
26
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@objc
|
|
31
|
+
func addDownloadProgressListener(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
32
|
+
isListening = true
|
|
33
|
+
resolve(nil)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@objc
|
|
37
|
+
func removeDownloadProgressListener(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
38
|
+
isListening = false
|
|
39
|
+
resolve(nil)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func onProgressChange(assetId: String, percentage: Double) {
|
|
43
|
+
if isListening {
|
|
44
|
+
notifyDownloadsChange()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func onStateChange(status: Status, offlineAsset: OfflineAsset) {
|
|
49
|
+
if isListening {
|
|
50
|
+
notifyDownloadsChange()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func onDelete(assetId: String) {
|
|
55
|
+
if isListening {
|
|
56
|
+
notifyDownloadsChange()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func onStart(offlineAsset: OfflineAsset) {
|
|
61
|
+
if isListening {
|
|
62
|
+
notifyDownloadsChange()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func onComplete(offlineAsset: OfflineAsset) {
|
|
67
|
+
if isListening {
|
|
68
|
+
notifyDownloadsChange()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func onPause(offlineAsset: OfflineAsset) {
|
|
73
|
+
if isListening {
|
|
74
|
+
notifyDownloadsChange()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func onResume(offlineAsset: OfflineAsset) {
|
|
79
|
+
if isListening {
|
|
80
|
+
notifyDownloadsChange()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func onCanceled(assetId: String) {
|
|
85
|
+
if isListening {
|
|
86
|
+
notifyDownloadsChange()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private func notifyDownloadsChange() {
|
|
91
|
+
DispatchQueue.main.async { [weak self] in
|
|
92
|
+
guard let self = self else { return }
|
|
93
|
+
let downloadAssets = self.getAllDownloadItems()
|
|
94
|
+
self.sendEvent(withName: "onDownloadProgressChanged", body: downloadAssets)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private func getAllDownloadItems() -> [[String: Any]] {
|
|
99
|
+
let offlineAssets = downloadManager.getAllOfflineAssets()
|
|
100
|
+
return offlineAssets.map { mapOfflineAssetToDict($0) }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private func mapOfflineAssetToDict(_ asset: OfflineAsset) -> [String: Any] {
|
|
104
|
+
var item: [String: Any] = [:]
|
|
105
|
+
item["videoId"] = asset.assetId
|
|
106
|
+
item["title"] = asset.title
|
|
107
|
+
item["totalBytes"] = asset.size
|
|
108
|
+
item["downloadedBytes"] = calculateDownloadedBytes(size: asset.size, progress: asset.percentageCompleted)
|
|
109
|
+
item["progressPercentage"] = asset.percentageCompleted
|
|
110
|
+
item["state"] = mapDownloadStatus(Status(rawValue: asset.status))
|
|
111
|
+
item["metadata"] = "{}"
|
|
112
|
+
|
|
113
|
+
return item
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private func calculateDownloadedBytes(size: Any?, progress: Any?) -> Int64 {
|
|
117
|
+
guard let totalBytes = size as? Double,
|
|
118
|
+
let progressPercentage = progress as? Double else {
|
|
119
|
+
return 0
|
|
120
|
+
}
|
|
121
|
+
return Int64((progressPercentage / 100.0) * totalBytes)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@objc
|
|
125
|
+
func pauseDownload(_ videoId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
126
|
+
DispatchQueue.main.async { [weak self] in
|
|
127
|
+
guard let self = self else {
|
|
128
|
+
reject("ERROR", "Module deallocated", nil)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
self.downloadManager.pauseDownload(videoId)
|
|
132
|
+
resolve(nil)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@objc
|
|
137
|
+
func resumeDownload(_ videoId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
138
|
+
DispatchQueue.main.async { [weak self] in
|
|
139
|
+
guard let self = self else {
|
|
140
|
+
reject("ERROR", "Module deallocated", nil)
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
self.downloadManager.resumeDownload(videoId)
|
|
144
|
+
resolve(nil)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@objc
|
|
149
|
+
func removeDownload(_ videoId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
150
|
+
DispatchQueue.main.async { [weak self] in
|
|
151
|
+
guard let self = self else {
|
|
152
|
+
reject("ERROR", "Module deallocated", nil)
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
if self.downloadManager.isAssetDownloaded(assetID: videoId) {
|
|
156
|
+
self.downloadManager.deleteDownload(videoId)
|
|
157
|
+
} else {
|
|
158
|
+
self.downloadManager.cancelDownload(videoId)
|
|
159
|
+
}
|
|
160
|
+
resolve(nil)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@objc
|
|
165
|
+
func isDownloaded(_ videoId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
166
|
+
DispatchQueue.main.async { [weak self] in
|
|
167
|
+
resolve(self?.downloadManager.isAssetDownloaded(assetID: videoId) ?? false)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@objc
|
|
172
|
+
func isDownloading(_ videoId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
173
|
+
DispatchQueue.main.async { [weak self] in
|
|
174
|
+
guard let self = self else {
|
|
175
|
+
reject("ERROR", "Module deallocated", nil)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
let offlineAssets = self.downloadManager.getAllOfflineAssets()
|
|
179
|
+
let isDownloading = offlineAssets.contains { asset in
|
|
180
|
+
asset.assetId == videoId && Status(rawValue: asset.status) == .inProgress
|
|
181
|
+
}
|
|
182
|
+
resolve(isDownloading)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
6
185
|
|
|
7
186
|
@objc
|
|
8
|
-
|
|
9
|
-
|
|
187
|
+
func isPaused(_ videoId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
188
|
+
DispatchQueue.main.async { [weak self] in
|
|
189
|
+
guard let self = self else {
|
|
190
|
+
reject("ERROR", "Module deallocated", nil)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
let offlineAssets = self.downloadManager.getAllOfflineAssets()
|
|
194
|
+
let isPaused = offlineAssets.contains { asset in
|
|
195
|
+
asset.assetId == videoId && Status(rawValue: asset.status) == .paused
|
|
196
|
+
}
|
|
197
|
+
resolve(isPaused)
|
|
198
|
+
}
|
|
10
199
|
}
|
|
11
200
|
|
|
12
|
-
|
|
13
|
-
|
|
201
|
+
@objc
|
|
202
|
+
func getDownloadStatus(_ videoId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
203
|
+
DispatchQueue.main.async { [weak self] in
|
|
204
|
+
guard let self = self else {
|
|
205
|
+
reject("ERROR", "Module deallocated", nil)
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
let offlineAssets = self.downloadManager.getAllOfflineAssets()
|
|
209
|
+
|
|
210
|
+
if let asset = offlineAssets.first(where: { $0.assetId == videoId }) {
|
|
211
|
+
resolve(mapDownloadStatus(Status(rawValue: asset.status)))
|
|
212
|
+
} else {
|
|
213
|
+
resolve(PlayerConstants.statusNotDownloaded)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@objc
|
|
219
|
+
func getAllDownloads(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
220
|
+
DispatchQueue.main.async { [weak self] in
|
|
221
|
+
guard let self = self else {
|
|
222
|
+
reject("ERROR", "Module deallocated", nil)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
let downloadItems = self.getAllDownloadItems()
|
|
226
|
+
resolve(downloadItems)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private func mapDownloadStatus(_ status: Status?) -> String {
|
|
231
|
+
guard let status = status else { return PlayerConstants.statusUnknown }
|
|
232
|
+
|
|
233
|
+
switch status {
|
|
234
|
+
case .inProgress:
|
|
235
|
+
return PlayerConstants.statusDownloading
|
|
236
|
+
case .paused:
|
|
237
|
+
return PlayerConstants.statusPaused
|
|
238
|
+
case .finished:
|
|
239
|
+
return PlayerConstants.statusCompleted
|
|
240
|
+
case .failed:
|
|
241
|
+
return PlayerConstants.statusFailed
|
|
242
|
+
default:
|
|
243
|
+
return PlayerConstants.statusNotDownloaded
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@objc
|
|
248
|
+
override func supportedEvents() -> [String] {
|
|
249
|
+
return ["onDownloadProgressChanged"]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@objc
|
|
253
|
+
override func addListener(_ eventName: String) {
|
|
254
|
+
super.addListener(eventName)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@objc
|
|
258
|
+
override func removeListeners(_ count: Double) {
|
|
259
|
+
super.removeListeners(count)
|
|
260
|
+
|
|
261
|
+
if count >= 1 && isListening {
|
|
262
|
+
isListening = false
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -10,13 +10,15 @@ class TPStreamsModule: NSObject {
|
|
|
10
10
|
@objc func initialize(_ organizationId: NSString) {
|
|
11
11
|
if !isInitialized {
|
|
12
12
|
print("Initializing TPStreamsSDK with org code: \(organizationId)")
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
DispatchQueue.main.async {
|
|
14
|
+
TPStreamsSDK.initialize(withOrgCode: organizationId as String)
|
|
15
|
+
self.isInitialized = true
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
@objc
|
|
19
21
|
static func requiresMainQueueSetup() -> Bool {
|
|
20
|
-
return
|
|
22
|
+
return true
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -9,17 +9,17 @@ class TPStreamsRNPlayerView: UIView {
|
|
|
9
9
|
|
|
10
10
|
private var player: TPAVPlayer?
|
|
11
11
|
private var playerViewController: TPStreamPlayerViewController?
|
|
12
|
-
|
|
13
|
-
private var _videoId: NSString = ""
|
|
14
|
-
private var _accessToken: NSString = ""
|
|
15
|
-
private var _shouldAutoPlay: Bool = true
|
|
16
|
-
private var _startAt: Double = 0
|
|
17
|
-
private var _enableDownload: Bool = false
|
|
18
|
-
private var _offlineLicenseExpireTime: Double = 0
|
|
19
|
-
private var _showDefaultCaptions: Bool = false
|
|
20
|
-
private var _downloadMetadata: String?
|
|
21
|
-
private var setupScheduled = false
|
|
22
12
|
private var playerStatusObserver: NSKeyValueObservation?
|
|
13
|
+
private var setupScheduled = false
|
|
14
|
+
|
|
15
|
+
@objc var videoId: NSString = ""
|
|
16
|
+
@objc var accessToken: NSString = ""
|
|
17
|
+
@objc var shouldAutoPlay: Bool = true
|
|
18
|
+
@objc var startAt: Double = 0
|
|
19
|
+
@objc var enableDownload: Bool = false
|
|
20
|
+
@objc var offlineLicenseExpireTime: Double = 0
|
|
21
|
+
@objc var showDefaultCaptions: Bool = false
|
|
22
|
+
@objc var downloadMetadata: NSString?
|
|
23
23
|
|
|
24
24
|
@objc var onCurrentPosition: RCTDirectEventBlock?
|
|
25
25
|
@objc var onDuration: RCTDirectEventBlock?
|
|
@@ -31,65 +31,7 @@ class TPStreamsRNPlayerView: UIView {
|
|
|
31
31
|
@objc var onIsLoadingChanged: RCTDirectEventBlock?
|
|
32
32
|
@objc var onError: RCTDirectEventBlock?
|
|
33
33
|
@objc var onAccessTokenExpired: RCTDirectEventBlock?
|
|
34
|
-
|
|
35
|
-
@objc var videoId: NSString {
|
|
36
|
-
get { _videoId }
|
|
37
|
-
set {
|
|
38
|
-
_videoId = newValue
|
|
39
|
-
schedulePlayerSetup()
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@objc var accessToken: NSString {
|
|
44
|
-
get { _accessToken }
|
|
45
|
-
set {
|
|
46
|
-
_accessToken = newValue
|
|
47
|
-
schedulePlayerSetup()
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@objc var shouldAutoPlay: Bool {
|
|
52
|
-
get { _shouldAutoPlay }
|
|
53
|
-
set {
|
|
54
|
-
_shouldAutoPlay = newValue
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
@objc var startAt: Double {
|
|
59
|
-
get { _startAt }
|
|
60
|
-
set {
|
|
61
|
-
_startAt = newValue
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
@objc var enableDownload: Bool {
|
|
66
|
-
get { _enableDownload }
|
|
67
|
-
set {
|
|
68
|
-
_enableDownload = newValue
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@objc var offlineLicenseExpireTime: Double {
|
|
73
|
-
get { _offlineLicenseExpireTime }
|
|
74
|
-
set {
|
|
75
|
-
_offlineLicenseExpireTime = newValue
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
34
|
|
|
79
|
-
@objc var showDefaultCaptions: Bool {
|
|
80
|
-
get { _showDefaultCaptions }
|
|
81
|
-
set {
|
|
82
|
-
_showDefaultCaptions = newValue
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
@objc var downloadMetadata: NSString? {
|
|
87
|
-
get { _downloadMetadata as NSString? }
|
|
88
|
-
set {
|
|
89
|
-
_downloadMetadata = newValue as String?
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
35
|
override init(frame: CGRect) {
|
|
94
36
|
super.init(frame: frame)
|
|
95
37
|
backgroundColor = .black
|
|
@@ -104,46 +46,91 @@ class TPStreamsRNPlayerView: UIView {
|
|
|
104
46
|
super.layoutSubviews()
|
|
105
47
|
playerViewController?.view.frame = bounds
|
|
106
48
|
}
|
|
49
|
+
|
|
50
|
+
override func didSetProps(_ changedProps: [String]!) {
|
|
51
|
+
super.didSetProps(changedProps)
|
|
52
|
+
schedulePlayerSetup()
|
|
53
|
+
}
|
|
107
54
|
|
|
108
55
|
private func schedulePlayerSetup() {
|
|
109
|
-
guard
|
|
110
|
-
|
|
111
|
-
if setupScheduled { return }
|
|
56
|
+
guard !setupScheduled else { return }
|
|
57
|
+
|
|
112
58
|
setupScheduled = true
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
self.setupPlayer()
|
|
59
|
+
DispatchQueue.main.async { [weak self] in
|
|
60
|
+
self?.setupPlayer()
|
|
116
61
|
}
|
|
117
62
|
}
|
|
118
|
-
|
|
63
|
+
|
|
119
64
|
private func setupPlayer() {
|
|
65
|
+
cleanupPlayer()
|
|
66
|
+
|
|
67
|
+
player = TPStreamsDownloadManager.shared.isAssetDownloaded(assetID: videoId as String)
|
|
68
|
+
? createOfflinePlayer()
|
|
69
|
+
: createOnlinePlayer()
|
|
70
|
+
|
|
71
|
+
guard player != nil else {
|
|
72
|
+
print("Failed to create player - invalid videoId or accessToken")
|
|
73
|
+
setupScheduled = false
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
configurePlayerView()
|
|
78
|
+
observePlayerChanges()
|
|
79
|
+
setupScheduled = false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private func cleanupPlayer() {
|
|
83
|
+
removeObservers()
|
|
120
84
|
playerViewController?.view.removeFromSuperview()
|
|
121
85
|
playerViewController?.removeFromParent()
|
|
122
86
|
playerViewController = nil
|
|
123
87
|
player = nil
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private func removeObservers() {
|
|
124
91
|
playerStatusObserver?.invalidate()
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"message": "Player initialization failed",
|
|
132
|
-
"code": nsError.code,
|
|
133
|
-
"details": error.localizedDescription
|
|
134
|
-
])
|
|
135
|
-
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private func createOfflinePlayer() -> TPAVPlayer? {
|
|
95
|
+
guard videoId.length > 0 else { return nil }
|
|
96
|
+
return TPAVPlayer(offlineAssetId: videoId as String) { [weak self] error in
|
|
97
|
+
self?.handlePlayerError(error, context: "Offline setup")
|
|
136
98
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
self.playerStatusObserver = nil
|
|
144
|
-
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private func createOnlinePlayer() -> TPAVPlayer? {
|
|
102
|
+
guard videoId.length > 0, accessToken.length > 0 else { return nil }
|
|
103
|
+
return TPAVPlayer(assetID: videoId as String, accessToken: accessToken as String) { [weak self] error in
|
|
104
|
+
self?.handlePlayerError(error, context: "Online setup")
|
|
145
105
|
}
|
|
146
|
-
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private func handlePlayerError(_ error: Error?, context: String) {
|
|
109
|
+
guard let error = error else { return }
|
|
110
|
+
let nsError = error as NSError
|
|
111
|
+
print("\(context) error: \(error.localizedDescription)")
|
|
112
|
+
onError?([
|
|
113
|
+
"message": "Player initialization failed",
|
|
114
|
+
"code": nsError.code,
|
|
115
|
+
"details": error.localizedDescription
|
|
116
|
+
])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private func configurePlayerView() {
|
|
120
|
+
guard let player = player else { return }
|
|
121
|
+
|
|
122
|
+
let configBuilder = createPlayerConfigBuilder()
|
|
123
|
+
let playerVC = TPStreamPlayerViewController()
|
|
124
|
+
playerVC.player = player
|
|
125
|
+
playerVC.config = configBuilder.build()
|
|
126
|
+
|
|
127
|
+
attachPlayerViewController(playerVC)
|
|
128
|
+
|
|
129
|
+
if shouldAutoPlay { player.play() }
|
|
130
|
+
playerViewController = playerVC
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private func createPlayerConfigBuilder() -> TPStreamPlayerConfigurationBuilder {
|
|
147
134
|
let configBuilder = TPStreamPlayerConfigurationBuilder()
|
|
148
135
|
.setPreferredForwardDuration(15)
|
|
149
136
|
.setPreferredRewindDuration(5)
|
|
@@ -154,27 +141,34 @@ class TPStreamsRNPlayerView: UIView {
|
|
|
154
141
|
configBuilder.showDownloadOption()
|
|
155
142
|
}
|
|
156
143
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if shouldAutoPlay {
|
|
174
|
-
player?.play()
|
|
175
|
-
}
|
|
144
|
+
return configBuilder
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private func attachPlayerViewController(_ playerVC: TPStreamPlayerViewController) {
|
|
148
|
+
guard let parentVC = reactViewController() else { return }
|
|
149
|
+
|
|
150
|
+
parentVC.addChild(playerVC)
|
|
151
|
+
addSubview(playerVC.view)
|
|
152
|
+
playerVC.view.frame = bounds
|
|
153
|
+
playerVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
154
|
+
playerVC.view.isHidden = false
|
|
155
|
+
bringSubviewToFront(playerVC.view)
|
|
156
|
+
playerVC.didMove(toParent: parentVC)
|
|
157
|
+
}
|
|
176
158
|
|
|
177
|
-
|
|
159
|
+
private func observePlayerChanges() {
|
|
160
|
+
setupSeekObserver()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private func setupSeekObserver() {
|
|
164
|
+
guard let player = player, startAt > 0 else { return }
|
|
165
|
+
|
|
166
|
+
playerStatusObserver = player.observe(\.status, options: [.new]) { [weak self] player, _ in
|
|
167
|
+
guard let self = self, player.status == .readyToPlay else { return }
|
|
168
|
+
self.seekTo(position: self.startAt * 1000.0)
|
|
169
|
+
self.playerStatusObserver?.invalidate()
|
|
170
|
+
self.playerStatusObserver = nil
|
|
171
|
+
}
|
|
178
172
|
}
|
|
179
173
|
|
|
180
174
|
@objc func seekTo(position: Double) {
|
|
@@ -184,13 +178,9 @@ class TPStreamsRNPlayerView: UIView {
|
|
|
184
178
|
player.seek(to: seekTime, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
185
179
|
}
|
|
186
180
|
|
|
187
|
-
@objc func play() {
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
@objc func pause() {
|
|
192
|
-
player?.pause()
|
|
193
|
-
}
|
|
181
|
+
@objc func play() { player?.play() }
|
|
182
|
+
|
|
183
|
+
@objc func pause() { player?.pause() }
|
|
194
184
|
|
|
195
185
|
@objc func setPlaybackSpeed(_ speed: Float) {
|
|
196
186
|
player?.rate = speed
|
|
@@ -201,42 +191,34 @@ class TPStreamsRNPlayerView: UIView {
|
|
|
201
191
|
onCurrentPosition?(["position": 0])
|
|
202
192
|
return
|
|
203
193
|
}
|
|
204
|
-
|
|
205
|
-
let currentTime = player.currentTime()
|
|
206
|
-
let positionMs = CMTimeGetSeconds(currentTime) * 1000
|
|
194
|
+
let positionMs = CMTimeGetSeconds(player.currentTime()) * 1000
|
|
207
195
|
onCurrentPosition?(["position": positionMs])
|
|
208
196
|
}
|
|
209
197
|
|
|
210
198
|
@objc func getDuration() {
|
|
211
|
-
guard let
|
|
199
|
+
guard let duration = player?.currentItem?.duration else {
|
|
212
200
|
onDuration?(["duration": 0])
|
|
213
201
|
return
|
|
214
202
|
}
|
|
215
|
-
|
|
216
|
-
let durationMs = CMTimeGetSeconds(currentItem.duration) * 1000
|
|
203
|
+
let durationMs = CMTimeGetSeconds(duration) * 1000
|
|
217
204
|
onDuration?(["duration": durationMs])
|
|
218
205
|
}
|
|
219
206
|
|
|
220
207
|
@objc func isPlaying() {
|
|
221
|
-
|
|
222
|
-
onIsPlaying?(["isPlaying": false])
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
let isPlaying = player.timeControlStatus == .playing
|
|
208
|
+
let isPlaying = player?.timeControlStatus == .playing
|
|
227
209
|
onIsPlaying?(["isPlaying": isPlaying])
|
|
228
210
|
}
|
|
229
211
|
|
|
230
212
|
@objc func getPlaybackSpeed() {
|
|
231
|
-
|
|
232
|
-
onPlaybackSpeed?(["speed": speed])
|
|
213
|
+
onPlaybackSpeed?(["speed": player?.rate ?? 1.0])
|
|
233
214
|
}
|
|
234
215
|
|
|
235
216
|
@objc func setNewAccessToken(_ newToken: String) {
|
|
236
|
-
print("
|
|
217
|
+
print("New access token set: \(newToken)")
|
|
218
|
+
// TODO: Reinitialize player with new token if needed
|
|
237
219
|
}
|
|
238
|
-
|
|
220
|
+
|
|
239
221
|
deinit {
|
|
240
|
-
|
|
222
|
+
removeObservers()
|
|
241
223
|
}
|
|
242
224
|
}
|