react-native-tpstreams 0.2.18 → 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.
Files changed (52) hide show
  1. package/TPStreamsRNPlayerView.podspec +8 -2
  2. package/android/build.gradle +1 -1
  3. package/android/gradle.properties +1 -1
  4. package/android/src/main/java/com/tpstreams/TPStreamsDownloadsModule.kt +143 -0
  5. package/android/src/main/java/com/tpstreams/TPStreamsRNPackage.kt +1 -0
  6. package/android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt +16 -1
  7. package/ios/TPStreamsDownloadsModule.mm +32 -0
  8. package/ios/TPStreamsDownloadsModule.swift +92 -0
  9. package/ios/TPStreamsModule.swift +17 -12
  10. package/ios/TPStreamsRNPlayerView.swift +242 -0
  11. package/ios/TPStreamsRNPlayerViewManager.m +38 -0
  12. package/ios/TPStreamsRNPlayerViewManager.swift +109 -0
  13. package/lib/module/TPStreamsPlayer.js +2 -2
  14. package/lib/module/TPStreamsPlayer.js.map +1 -1
  15. package/lib/module/index.js +6 -2
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/typescript/spec/NativeTPStreams.d.ts +7 -0
  18. package/lib/typescript/spec/NativeTPStreams.d.ts.map +1 -0
  19. package/lib/typescript/spec/NativeTPStreamsDownloads.d.ts +24 -0
  20. package/lib/typescript/spec/NativeTPStreamsDownloads.d.ts.map +1 -0
  21. package/lib/typescript/spec/TPStreamsPlayerViewNativeComponent.d.ts.map +1 -0
  22. package/lib/typescript/src/TPStreamsPlayer.d.ts.map +1 -1
  23. package/lib/typescript/src/index.d.ts +4 -2
  24. package/lib/typescript/src/index.d.ts.map +1 -1
  25. package/package.json +3 -3
  26. package/src/TPStreamsPlayer.tsx +6 -3
  27. package/src/index.tsx +7 -2
  28. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/TPStreamsRNPlayerViewManagerDelegate.java +0 -78
  29. package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/TPStreamsRNPlayerViewManagerInterface.java +0 -31
  30. package/android/app/build/generated/source/codegen/jni/CMakeLists.txt +0 -36
  31. package/android/app/build/generated/source/codegen/jni/TPStreamsPlayerViewSpec-generated.cpp +0 -22
  32. package/android/app/build/generated/source/codegen/jni/TPStreamsPlayerViewSpec.h +0 -24
  33. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/ComponentDescriptors.cpp +0 -22
  34. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/ComponentDescriptors.h +0 -24
  35. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/EventEmitters.cpp +0 -98
  36. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/EventEmitters.h +0 -75
  37. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/Props.cpp +0 -30
  38. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/Props.h +0 -32
  39. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/ShadowNodes.cpp +0 -17
  40. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/ShadowNodes.h +0 -32
  41. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/States.cpp +0 -16
  42. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/States.h +0 -29
  43. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/TPStreamsPlayerViewSpecJSI-generated.cpp +0 -17
  44. package/android/app/build/generated/source/codegen/jni/react/renderer/components/TPStreamsPlayerViewSpec/TPStreamsPlayerViewSpecJSI.h +0 -19
  45. package/ios/TPStreamsRNPlayerView.h +0 -14
  46. package/ios/TPStreamsRNPlayerView.mm +0 -52
  47. package/ios/TPStreamsRNPlayerViewManager.mm +0 -18
  48. package/lib/module/TPStreamsPlayerViewNativeComponent.ts +0 -82
  49. package/lib/typescript/src/TPStreamsPlayerViewNativeComponent.d.ts.map +0 -1
  50. package/src/TPStreamsPlayerViewNativeComponent.ts +0 -82
  51. /package/ios/{TPStreamsModule.mm → TPStreamsModule.m} +0 -0
  52. /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.swift_version = '5.0'
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
@@ -86,7 +86,7 @@ dependencies {
86
86
  }
87
87
 
88
88
  react {
89
- jsRootDir = file("../src/")
89
+ jsRootDir = file("../spec/")
90
90
  libraryName = "TpstreamsView"
91
91
  codegenJavaPackageName = "com.tpstreams"
92
92
  }
@@ -3,4 +3,4 @@ Tpstreams_minSdkVersion=24
3
3
  Tpstreams_targetSdkVersion=34
4
4
  Tpstreams_compileSdkVersion=35
5
5
  Tpstreams_ndkVersion=27.1.12297006
6
- Tpstreams_tpstreamsAndroidPlayerVersion=1.0.15
6
+ Tpstreams_tpstreamsAndroidPlayerVersion=1.0.16
@@ -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
  }
@@ -11,6 +11,7 @@ import com.tpstreams.player.TPStreamsPlayerView
11
11
  import androidx.media3.common.Player
12
12
  import androidx.media3.common.PlaybackParameters
13
13
  import androidx.media3.common.PlaybackException
14
+ import android.media.MediaCodec
14
15
 
15
16
  class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context) {
16
17
  private val playerView: TPStreamsPlayerView = TPStreamsPlayerView(context)
@@ -19,6 +20,8 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)
19
20
 
20
21
  companion object {
21
22
  private const val DEFAULT_OFFLINE_LICENSE_EXPIRE_TIME = 15L * 24L * 60L * 60L // 15 days in seconds
23
+ private const val ERROR_CODE_PLAYER_CREATION_FAILED = 1001
24
+ private const val ERROR_CODE_DRM_LICENSE_EXPIRED = 5001
22
25
  }
23
26
 
24
27
  private var videoId: String? = null
@@ -141,7 +144,7 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)
141
144
  emitEvent("onIsLoadingChanged", mapOf("isLoading" to false))
142
145
  } catch (e: Exception) {
143
146
  Log.e("TPStreamsRN", "Error creating player", e)
144
- sendErrorEvent("Error creating player", 1001, e.message)
147
+ sendErrorEvent("Error creating player", ERROR_CODE_PLAYER_CREATION_FAILED, e.message)
145
148
  }
146
149
  }
147
150
 
@@ -165,11 +168,23 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)
165
168
 
166
169
  override fun onPlayerError(error: PlaybackException) {
167
170
  Log.e("TPStreamsRN", "Player error", error)
171
+ if (isDrmLicenseExpiredError(error)) {
172
+ sendErrorEvent("Playback error", ERROR_CODE_DRM_LICENSE_EXPIRED, "Offline DRM license expired")
173
+ return
174
+ }
168
175
  sendErrorEvent("Playback error", error.errorCode, error.message)
169
176
  }
170
177
  }
171
178
  }
172
179
 
180
+ private fun isDrmLicenseExpiredError(error: PlaybackException): Boolean {
181
+ val cause = error.cause
182
+ return error.errorCode == PlaybackException.ERROR_CODE_DRM_LICENSE_EXPIRED ||
183
+ error.errorCode == PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION ||
184
+ error.errorCode == PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR ||
185
+ cause is MediaCodec.CryptoException
186
+ }
187
+
173
188
  // Player control methods
174
189
  fun play() {
175
190
  player?.play()
@@ -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
- @objc
8
- static func requiresMainQueueSetup() -> Bool {
9
- return false
10
- }
11
-
12
- @objc
13
- func initialize(_ organizationId: String) {
14
- // Basic initialization - will be implemented in future commits
15
- print("TPStreams initialized with organization ID: \(organizationId)")
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