react-native-tpstreams 0.2.19 → 0.2.20
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/TPStreamsRNPlayerView.podspec +8 -2
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/tpstreams/TPStreamsDownloadsModule.kt +143 -0
- package/android/src/main/java/com/tpstreams/TPStreamsRNPackage.kt +1 -0
- package/ios/TPStreamsDownloadsModule.mm +32 -0
- package/ios/TPStreamsDownloadsModule.swift +92 -0
- package/ios/TPStreamsModule.swift +17 -12
- package/ios/TPStreamsRNPlayerView.swift +242 -0
- package/ios/TPStreamsRNPlayerViewManager.m +38 -0
- package/ios/TPStreamsRNPlayerViewManager.swift +109 -0
- package/lib/module/TPStreamsPlayer.js +2 -2
- package/lib/module/TPStreamsPlayer.js.map +1 -1
- package/lib/module/index.js +6 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/spec/NativeTPStreams.d.ts +7 -0
- package/lib/typescript/spec/NativeTPStreams.d.ts.map +1 -0
- package/lib/typescript/spec/NativeTPStreamsDownloads.d.ts +24 -0
- package/lib/typescript/spec/NativeTPStreamsDownloads.d.ts.map +1 -0
- package/lib/typescript/spec/TPStreamsPlayerViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/TPStreamsPlayer.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/TPStreamsPlayer.tsx +6 -3
- package/src/index.tsx +7 -2
- package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/TPStreamsRNPlayerViewManagerDelegate.java +0 -78
- package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/TPStreamsRNPlayerViewManagerInterface.java +0 -31
- package/android/app/build/generated/source/codegen/jni/CMakeLists.txt +0 -36
- package/android/app/build/generated/source/codegen/jni/TPStreamsPlayerViewSpec-generated.cpp +0 -22
- package/android/app/build/generated/source/codegen/jni/TPStreamsPlayerViewSpec.h +0 -24
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/ComponentDescriptors.cpp +0 -22
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/ComponentDescriptors.h +0 -24
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/EventEmitters.cpp +0 -98
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/EventEmitters.h +0 -75
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/Props.cpp +0 -30
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/Props.h +0 -32
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/ShadowNodes.cpp +0 -17
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/ShadowNodes.h +0 -32
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/States.cpp +0 -16
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/States.h +0 -29
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/TPStreamsPlayerViewSpecJSI-generated.cpp +0 -17
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/TPStreamsPlayerViewSpecJSI.h +0 -19
- package/ios/TPStreamsRNPlayerView.h +0 -14
- package/ios/TPStreamsRNPlayerView.mm +0 -52
- package/ios/TPStreamsRNPlayerViewManager.mm +0 -18
- package/lib/module/TPStreamsPlayerViewNativeComponent.ts +0 -82
- package/lib/typescript/src/TPStreamsPlayerViewNativeComponent.d.ts.map +0 -1
- package/src/TPStreamsPlayerViewNativeComponent.ts +0 -82
- /package/ios/{TPStreamsModule.mm → TPStreamsModule.m} +0 -0
- /package/lib/typescript/{src → spec}/TPStreamsPlayerViewNativeComponent.d.ts +0 -0
|
@@ -9,14 +9,20 @@ Pod::Spec.new do |s|
|
|
|
9
9
|
s.homepage = package["homepage"]
|
|
10
10
|
s.license = package["license"]
|
|
11
11
|
s.authors = package["author"]
|
|
12
|
+
s.swift_version = '5.0'
|
|
12
13
|
|
|
13
14
|
s.platforms = { :ios => min_ios_version_supported }
|
|
14
15
|
s.source = { :git => "https://github.com/testpress/react-native-tpstreams.git", :tag => "#{s.version}" }
|
|
15
16
|
|
|
16
17
|
s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
|
|
17
18
|
s.private_header_files = "ios/**/*.h"
|
|
18
|
-
|
|
19
|
-
s.
|
|
19
|
+
|
|
20
|
+
s.pod_target_xcconfig = {
|
|
21
|
+
'DEFINES_MODULE' => 'YES',
|
|
22
|
+
'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES'
|
|
23
|
+
}
|
|
24
|
+
s.dependency "TPStreamsSDK"
|
|
25
|
+
|
|
20
26
|
|
|
21
27
|
# Ensure the module is not built as a framework to avoid bridging header conflicts
|
|
22
28
|
s.static_framework = true
|
package/android/build.gradle
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
package com.tpstreams
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.Arguments
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
8
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
9
|
+
import com.tpstreams.NativeTPStreamsDownloadsSpec
|
|
10
|
+
import com.tpstreams.player.download.DownloadClient
|
|
11
|
+
import com.tpstreams.player.download.DownloadItem
|
|
12
|
+
import org.json.JSONObject
|
|
13
|
+
import com.facebook.react.bridge.WritableMap
|
|
14
|
+
|
|
15
|
+
class TPStreamsDownloadsModule(private val reactContext: ReactApplicationContext) :
|
|
16
|
+
NativeTPStreamsDownloadsSpec(reactContext),
|
|
17
|
+
DownloadClient.Listener {
|
|
18
|
+
|
|
19
|
+
private val downloadClient: DownloadClient by lazy {
|
|
20
|
+
DownloadClient.getInstance(reactContext)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private var isListening = false
|
|
24
|
+
private val TAG = "TPStreamsDownloadsModule"
|
|
25
|
+
|
|
26
|
+
override fun getAll(promise: Promise) {
|
|
27
|
+
try {
|
|
28
|
+
val downloadItems = downloadClient.getAllDownloadItems()
|
|
29
|
+
val result = Arguments.createArray()
|
|
30
|
+
|
|
31
|
+
for (item in downloadItems) {
|
|
32
|
+
result.pushMap(convertDownloadItemToMap(item))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
promise.resolve(result)
|
|
36
|
+
} catch (e: Exception) {
|
|
37
|
+
Log.e(TAG, "Error getting all download items: ${e.message}", e)
|
|
38
|
+
promise.reject("DOWNLOAD_ITEMS_ERROR", e.message, e)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override fun get(videoId: String, promise: Promise) {
|
|
43
|
+
try {
|
|
44
|
+
val download = downloadClient.getDownload(videoId)
|
|
45
|
+
download?.let {
|
|
46
|
+
val item = downloadClient.createDownloadItem(it)
|
|
47
|
+
promise.resolve(convertDownloadItemToMap(item))
|
|
48
|
+
} ?: promise.resolve(null)
|
|
49
|
+
} catch (e: Exception) {
|
|
50
|
+
Log.e(TAG, "Error getting download item: ${e.message}", e)
|
|
51
|
+
promise.reject("DOWNLOAD_ITEM_ERROR", e.message, e)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override fun pause(videoId: String, promise: Promise) {
|
|
56
|
+
try {
|
|
57
|
+
downloadClient.pauseDownload(videoId)
|
|
58
|
+
promise.resolve(null)
|
|
59
|
+
} catch (e: Exception) {
|
|
60
|
+
Log.e(TAG, "Error pausing download: ${e.message}", e)
|
|
61
|
+
promise.reject("DOWNLOAD_PAUSE_ERROR", e.message, e)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun resume(videoId: String, promise: Promise) {
|
|
66
|
+
try {
|
|
67
|
+
downloadClient.resumeDownload(videoId)
|
|
68
|
+
promise.resolve(null)
|
|
69
|
+
} catch (e: Exception) {
|
|
70
|
+
Log.e(TAG, "Error resuming download: ${e.message}", e)
|
|
71
|
+
promise.reject("DOWNLOAD_RESUME_ERROR", e.message, e)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun remove(videoId: String, promise: Promise) {
|
|
76
|
+
try {
|
|
77
|
+
downloadClient.removeDownload(videoId)
|
|
78
|
+
promise.resolve(null)
|
|
79
|
+
} catch (e: Exception) {
|
|
80
|
+
Log.e(TAG, "Error removing download: ${e.message}", e)
|
|
81
|
+
promise.reject("DOWNLOAD_REMOVE_ERROR", e.message, e)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override fun startProgressUpdates() {
|
|
86
|
+
if (!isListening) {
|
|
87
|
+
downloadClient.addListener(this)
|
|
88
|
+
isListening = true
|
|
89
|
+
Log.d(TAG, "Started listening for download progress updates")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override fun stopProgressUpdates() {
|
|
94
|
+
if (isListening) {
|
|
95
|
+
downloadClient.removeListener(this)
|
|
96
|
+
isListening = false
|
|
97
|
+
Log.d(TAG, "Stopped listening for download progress updates")
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun addListener(eventName: String) {
|
|
102
|
+
// Required by NativeEventEmitter, but no-op
|
|
103
|
+
// Actual listener registration happens in startProgressUpdates
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override fun removeListeners(count: Double) {
|
|
107
|
+
// Required by NativeEventEmitter, but no-op
|
|
108
|
+
// Actual listener removal happens in stopProgressUpdates
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override fun onDownloadsChanged() {
|
|
112
|
+
try {
|
|
113
|
+
val currentDownloads = downloadClient.getAllDownloadItems()
|
|
114
|
+
|
|
115
|
+
val result = Arguments.createArray()
|
|
116
|
+
for (item in currentDownloads) {
|
|
117
|
+
result.pushMap(convertDownloadItemToMap(item))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
emitEvent("onDownloadProgressChanged", result)
|
|
121
|
+
} catch (e: Exception) {
|
|
122
|
+
Log.e(TAG, "Error in onDownloadsChanged: ${e.message}", e)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private fun convertDownloadItemToMap(item: DownloadItem): WritableMap {
|
|
127
|
+
val map = Arguments.createMap()
|
|
128
|
+
map.putString("videoId", item.assetId)
|
|
129
|
+
map.putString("title", item.title)
|
|
130
|
+
item.thumbnailUrl?.let { map.putString("thumbnailUrl", it) }
|
|
131
|
+
map.putDouble("totalBytes", item.totalBytes.toDouble())
|
|
132
|
+
map.putDouble("downloadedBytes", item.downloadedBytes.toDouble())
|
|
133
|
+
map.putDouble("progressPercentage", item.progressPercentage.toDouble())
|
|
134
|
+
map.putString("state", downloadClient.getDownloadStatus(item.assetId))
|
|
135
|
+
return map
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private fun emitEvent(eventName: String, data: Any) {
|
|
139
|
+
reactContext
|
|
140
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
141
|
+
.emit(eventName, data)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -17,6 +17,7 @@ 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))
|
|
20
21
|
return modules
|
|
21
22
|
}
|
|
22
23
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
3
|
+
|
|
4
|
+
@interface RCT_EXTERN_MODULE(TPStreamsDownloads, RCTEventEmitter)
|
|
5
|
+
|
|
6
|
+
RCT_EXTERN_METHOD(getAll:(RCTPromiseResolveBlock)resolve
|
|
7
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
8
|
+
|
|
9
|
+
RCT_EXTERN_METHOD(get:(NSString *)videoId
|
|
10
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
11
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
12
|
+
|
|
13
|
+
RCT_EXTERN_METHOD(pause:(NSString *)videoId
|
|
14
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
15
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
16
|
+
|
|
17
|
+
RCT_EXTERN_METHOD(resume:(NSString *)videoId
|
|
18
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
19
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
20
|
+
|
|
21
|
+
RCT_EXTERN_METHOD(remove:(NSString *)videoId
|
|
22
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
23
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
24
|
+
|
|
25
|
+
// Required for RCTEventEmitter
|
|
26
|
+
RCT_EXTERN_METHOD(supportedEvents)
|
|
27
|
+
|
|
28
|
+
// Required by NativeEventEmitter
|
|
29
|
+
RCT_EXTERN_METHOD(addListener:(NSString *)eventName)
|
|
30
|
+
RCT_EXTERN_METHOD(removeListeners:(double)count)
|
|
31
|
+
|
|
32
|
+
@end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
|
|
4
|
+
@objc(TPStreamsDownloads)
|
|
5
|
+
class TPStreamsDownloadsModule: NSObject, RCTEventEmitterProtocol {
|
|
6
|
+
|
|
7
|
+
private var hasListeners = false
|
|
8
|
+
|
|
9
|
+
@objc
|
|
10
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@objc
|
|
15
|
+
func supportedEvents() -> [String] {
|
|
16
|
+
return [
|
|
17
|
+
"onDownloadProgressChanged"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@objc
|
|
22
|
+
func startObserving() {
|
|
23
|
+
hasListeners = true
|
|
24
|
+
// TODO: Add observer for download progress changes
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@objc
|
|
28
|
+
func stopObserving() {
|
|
29
|
+
hasListeners = false
|
|
30
|
+
// TODO: Remove observer
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@objc
|
|
34
|
+
func sendEvent(withName name: String, body: Any?) {
|
|
35
|
+
// This method is required by RCTEventEmitterProtocol
|
|
36
|
+
// It should send events to React Native when hasListeners is true
|
|
37
|
+
if hasListeners {
|
|
38
|
+
// TODO: Implement actual event sending logic
|
|
39
|
+
// For now, this satisfies the protocol requirement
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// MARK: - Native Methods (TODO: Implement with TPDownloadClient)
|
|
44
|
+
|
|
45
|
+
@objc
|
|
46
|
+
func getAll(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
47
|
+
// TODO: Get all downloads from TPDownloadClient.shared.getAllDownloads()
|
|
48
|
+
resolve([])
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@objc
|
|
52
|
+
func get(_ videoId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
53
|
+
// TODO: Get download by videoId from TPDownloadClient.shared.getDownload(videoId:)
|
|
54
|
+
resolve(nil)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@objc
|
|
58
|
+
func pause(_ videoId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
59
|
+
// TODO: Pause download using TPDownloadClient.shared.pauseDownload(videoId:)
|
|
60
|
+
resolve(nil)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@objc
|
|
64
|
+
func resume(_ videoId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
65
|
+
// TODO: Resume download using TPDownloadClient.shared.resumeDownload(videoId:)
|
|
66
|
+
resolve(nil)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@objc
|
|
70
|
+
func remove(_ videoId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
71
|
+
// TODO: Remove download using TPDownloadClient.shared.removeDownload(videoId:)
|
|
72
|
+
resolve(nil)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@objc
|
|
76
|
+
func addListener(_ eventName: String) {
|
|
77
|
+
// Required for RCTEventEmitter
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@objc
|
|
81
|
+
func removeListeners(_ count: NSNumber) {
|
|
82
|
+
// Required for RCTEventEmitter
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Protocol to avoid direct dependency on RCTEventEmitter
|
|
87
|
+
@objc protocol RCTEventEmitterProtocol {
|
|
88
|
+
func supportedEvents() -> [String]
|
|
89
|
+
func startObserving()
|
|
90
|
+
func stopObserving()
|
|
91
|
+
func sendEvent(withName name: String, body: Any?)
|
|
92
|
+
}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import React
|
|
3
|
+
import TPStreamsSDK
|
|
3
4
|
|
|
4
5
|
@objc(TPStreams)
|
|
5
6
|
class TPStreamsModule: NSObject {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
|
|
8
|
+
private var isInitialized = false
|
|
9
|
+
|
|
10
|
+
@objc func initialize(_ organizationId: NSString) {
|
|
11
|
+
if !isInitialized {
|
|
12
|
+
print("Initializing TPStreamsSDK with org code: \(organizationId)")
|
|
13
|
+
TPStreamsSDK.initialize(withOrgCode: organizationId as String)
|
|
14
|
+
isInitialized = true
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@objc
|
|
19
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import TPStreamsSDK
|
|
3
|
+
import CoreMedia
|
|
4
|
+
import React
|
|
5
|
+
import AVFoundation
|
|
6
|
+
|
|
7
|
+
@objc(TPStreamsRNPlayerView)
|
|
8
|
+
class TPStreamsRNPlayerView: UIView {
|
|
9
|
+
|
|
10
|
+
private var player: TPAVPlayer?
|
|
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
|
+
private var playerStatusObserver: NSKeyValueObservation?
|
|
23
|
+
|
|
24
|
+
@objc var onCurrentPosition: RCTDirectEventBlock?
|
|
25
|
+
@objc var onDuration: RCTDirectEventBlock?
|
|
26
|
+
@objc var onIsPlaying: RCTDirectEventBlock?
|
|
27
|
+
@objc var onPlaybackSpeed: RCTDirectEventBlock?
|
|
28
|
+
@objc var onPlayerStateChanged: RCTDirectEventBlock?
|
|
29
|
+
@objc var onIsPlayingChanged: RCTDirectEventBlock?
|
|
30
|
+
@objc var onPlaybackSpeedChanged: RCTDirectEventBlock?
|
|
31
|
+
@objc var onIsLoadingChanged: RCTDirectEventBlock?
|
|
32
|
+
@objc var onError: RCTDirectEventBlock?
|
|
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
|
+
|
|
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
|
+
override init(frame: CGRect) {
|
|
94
|
+
super.init(frame: frame)
|
|
95
|
+
backgroundColor = .black
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
required init?(coder: NSCoder) {
|
|
99
|
+
super.init(coder: coder)
|
|
100
|
+
backgroundColor = .black
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override func layoutSubviews() {
|
|
104
|
+
super.layoutSubviews()
|
|
105
|
+
playerViewController?.view.frame = bounds
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private func schedulePlayerSetup() {
|
|
109
|
+
guard videoId.length > 0, accessToken.length > 0 else { return }
|
|
110
|
+
|
|
111
|
+
if setupScheduled { return }
|
|
112
|
+
setupScheduled = true
|
|
113
|
+
|
|
114
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
115
|
+
self.setupPlayer()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private func setupPlayer() {
|
|
120
|
+
playerViewController?.view.removeFromSuperview()
|
|
121
|
+
playerViewController?.removeFromParent()
|
|
122
|
+
playerViewController = nil
|
|
123
|
+
player = nil
|
|
124
|
+
playerStatusObserver?.invalidate()
|
|
125
|
+
|
|
126
|
+
player = TPAVPlayer(assetID: videoId as String, accessToken: accessToken as String) { error in
|
|
127
|
+
if let error = error {
|
|
128
|
+
print("Online setup error: \(error.localizedDescription)")
|
|
129
|
+
let nsError = error as NSError
|
|
130
|
+
self.onError?([
|
|
131
|
+
"message": "Player initialization failed",
|
|
132
|
+
"code": nsError.code,
|
|
133
|
+
"details": error.localizedDescription
|
|
134
|
+
])
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if startAt > 0 {
|
|
139
|
+
playerStatusObserver = player?.observe(\.status, options: [.new]) { [weak self] player, _ in
|
|
140
|
+
guard let self = self, player.status == .readyToPlay else { return }
|
|
141
|
+
self.seekTo(position: self.startAt*1000.0)
|
|
142
|
+
self.playerStatusObserver?.invalidate()
|
|
143
|
+
self.playerStatusObserver = nil
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let configBuilder = TPStreamPlayerConfigurationBuilder()
|
|
148
|
+
.setPreferredForwardDuration(15)
|
|
149
|
+
.setPreferredRewindDuration(5)
|
|
150
|
+
.setprogressBarThumbColor(.systemBlue)
|
|
151
|
+
.setwatchedProgressTrackColor(.systemBlue)
|
|
152
|
+
|
|
153
|
+
if enableDownload {
|
|
154
|
+
configBuilder.showDownloadOption()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let config = configBuilder.build()
|
|
158
|
+
|
|
159
|
+
let vc = TPStreamPlayerViewController()
|
|
160
|
+
vc.player = player
|
|
161
|
+
vc.config = config
|
|
162
|
+
|
|
163
|
+
if let parentVC = self.reactViewController() {
|
|
164
|
+
parentVC.addChild(vc)
|
|
165
|
+
addSubview(vc.view)
|
|
166
|
+
vc.view.frame = bounds
|
|
167
|
+
vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
168
|
+
vc.view.isHidden = false
|
|
169
|
+
bringSubviewToFront(vc.view)
|
|
170
|
+
vc.didMove(toParent: parentVC)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if shouldAutoPlay {
|
|
174
|
+
player?.play()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
playerViewController = vc
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@objc func seekTo(position: Double) {
|
|
181
|
+
guard position >= 0, let player = player else { return }
|
|
182
|
+
|
|
183
|
+
let seekTime = CMTime(value: CMTimeValue(position), timescale: 1000)
|
|
184
|
+
player.seek(to: seekTime, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@objc func play() {
|
|
188
|
+
player?.play()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@objc func pause() {
|
|
192
|
+
player?.pause()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@objc func setPlaybackSpeed(_ speed: Float) {
|
|
196
|
+
player?.rate = speed
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@objc func getCurrentPosition() {
|
|
200
|
+
guard let player = player else {
|
|
201
|
+
onCurrentPosition?(["position": 0])
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let currentTime = player.currentTime()
|
|
206
|
+
let positionMs = CMTimeGetSeconds(currentTime) * 1000
|
|
207
|
+
onCurrentPosition?(["position": positionMs])
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@objc func getDuration() {
|
|
211
|
+
guard let player = player, let currentItem = player.currentItem else {
|
|
212
|
+
onDuration?(["duration": 0])
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let durationMs = CMTimeGetSeconds(currentItem.duration) * 1000
|
|
217
|
+
onDuration?(["duration": durationMs])
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@objc func isPlaying() {
|
|
221
|
+
guard let player = player else {
|
|
222
|
+
onIsPlaying?(["isPlaying": false])
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let isPlaying = player.timeControlStatus == .playing
|
|
227
|
+
onIsPlaying?(["isPlaying": isPlaying])
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@objc func getPlaybackSpeed() {
|
|
231
|
+
let speed = player?.rate ?? 1.0
|
|
232
|
+
onPlaybackSpeed?(["speed": speed])
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@objc func setNewAccessToken(_ newToken: String) {
|
|
236
|
+
print("TPStreamsRNPlayerView: setNewAccessToken called with token: \(newToken)")
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
deinit {
|
|
240
|
+
playerStatusObserver?.invalidate()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#import <React/RCTViewManager.h>
|
|
2
|
+
#import <React/RCTBridgeModule.h>
|
|
3
|
+
|
|
4
|
+
@interface RCT_EXTERN_MODULE(TPStreamsRNPlayerViewManager, RCTViewManager)
|
|
5
|
+
|
|
6
|
+
RCT_EXPORT_VIEW_PROPERTY(videoId, NSString)
|
|
7
|
+
RCT_EXPORT_VIEW_PROPERTY(accessToken, NSString)
|
|
8
|
+
RCT_EXPORT_VIEW_PROPERTY(shouldAutoPlay, BOOL)
|
|
9
|
+
RCT_EXPORT_VIEW_PROPERTY(startAt, double)
|
|
10
|
+
RCT_EXPORT_VIEW_PROPERTY(enableDownload, BOOL)
|
|
11
|
+
RCT_EXPORT_VIEW_PROPERTY(offlineLicenseExpireTime, double)
|
|
12
|
+
RCT_EXPORT_VIEW_PROPERTY(showDefaultCaptions, BOOL)
|
|
13
|
+
RCT_EXPORT_VIEW_PROPERTY(downloadMetadata, NSString)
|
|
14
|
+
|
|
15
|
+
// Event props
|
|
16
|
+
RCT_EXPORT_VIEW_PROPERTY(onCurrentPosition, RCTDirectEventBlock)
|
|
17
|
+
RCT_EXPORT_VIEW_PROPERTY(onDuration, RCTDirectEventBlock)
|
|
18
|
+
RCT_EXPORT_VIEW_PROPERTY(onIsPlaying, RCTDirectEventBlock)
|
|
19
|
+
RCT_EXPORT_VIEW_PROPERTY(onPlaybackSpeed, RCTDirectEventBlock)
|
|
20
|
+
|
|
21
|
+
RCT_EXPORT_VIEW_PROPERTY(onPlayerStateChanged, RCTDirectEventBlock)
|
|
22
|
+
RCT_EXPORT_VIEW_PROPERTY(onIsPlayingChanged, RCTDirectEventBlock)
|
|
23
|
+
RCT_EXPORT_VIEW_PROPERTY(onPlaybackSpeedChanged, RCTDirectEventBlock)
|
|
24
|
+
RCT_EXPORT_VIEW_PROPERTY(onIsLoadingChanged, RCTDirectEventBlock)
|
|
25
|
+
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
|
|
26
|
+
RCT_EXPORT_VIEW_PROPERTY(onAccessTokenExpired, RCTDirectEventBlock)
|
|
27
|
+
|
|
28
|
+
// Player commands
|
|
29
|
+
RCT_EXTERN_METHOD(play:(nonnull NSNumber *)node)
|
|
30
|
+
RCT_EXTERN_METHOD(pause:(nonnull NSNumber *)node)
|
|
31
|
+
RCT_EXTERN_METHOD(seekTo:(nonnull NSNumber *)node position:(nonnull NSNumber *)position)
|
|
32
|
+
RCT_EXTERN_METHOD(setPlaybackSpeed:(nonnull NSNumber *)node speed:(nonnull NSNumber *)speed)
|
|
33
|
+
RCT_EXTERN_METHOD(getCurrentPosition:(nonnull NSNumber *)node)
|
|
34
|
+
RCT_EXTERN_METHOD(getDuration:(nonnull NSNumber *)node)
|
|
35
|
+
RCT_EXTERN_METHOD(isPlaying:(nonnull NSNumber *)node)
|
|
36
|
+
RCT_EXTERN_METHOD(getPlaybackSpeed:(nonnull NSNumber *)node)
|
|
37
|
+
|
|
38
|
+
@end
|