react-native-tpstreams 0.2.26 → 1.0.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.
@@ -1,7 +1,48 @@
1
1
  #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
2
3
 
3
- @interface RCT_EXTERN_MODULE(TPStreamsDownload, NSObject)
4
+ @interface RCT_EXTERN_MODULE(TPStreamsDownload, RCTEventEmitter)
4
5
 
5
- // Add download-related methods here if needed
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: NSObject {
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
- static func requiresMainQueueSetup() -> Bool {
9
- return false
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
- // Download functionality will be implemented in future commits
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
- TPStreamsSDK.initialize(withOrgCode: organizationId as String)
14
- isInitialized = true
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 false
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 videoId.length > 0, accessToken.length > 0 else { return }
110
-
111
- if setupScheduled { return }
56
+ guard !setupScheduled else { return }
57
+
112
58
  setupScheduled = true
113
-
114
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
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
- 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
- }
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
- 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
- }
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
- 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
- }
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
- playerViewController = vc
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
- player?.play()
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 player = player, let currentItem = player.currentItem else {
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
- guard let player = player else {
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
- let speed = player?.rate ?? 1.0
232
- onPlaybackSpeed?(["speed": speed])
213
+ onPlaybackSpeed?(["speed": player?.rate ?? 1.0])
233
214
  }
234
215
 
235
216
  @objc func setNewAccessToken(_ newToken: String) {
236
- print("TPStreamsRNPlayerView: setNewAccessToken called with token: \(newToken)")
217
+ print("New access token set: \(newToken)")
218
+ // TODO: Reinitialize player with new token if needed
237
219
  }
238
-
220
+
239
221
  deinit {
240
- playerStatusObserver?.invalidate()
222
+ removeObservers()
241
223
  }
242
224
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-tpstreams",
3
- "version": "0.2.26",
3
+ "version": "1.0.0",
4
4
  "description": "Video component for TPStreams",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",