@bluebillywig/react-native-bb-player 8.44.0 → 8.45.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.
Files changed (54) hide show
  1. package/README.md +80 -59
  2. package/android/build.gradle +2 -1
  3. package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerModule.kt +146 -28
  4. package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerView.kt +74 -165
  5. package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerViewManager.kt +0 -6
  6. package/android/src/paper/java/com/bluebillywig/bbplayer/NativeBBPlayerModuleSpec.java +19 -8
  7. package/ios/BBPlayerModule.mm +17 -8
  8. package/ios/BBPlayerModule.swift +138 -26
  9. package/ios/BBPlayerView.swift +41 -140
  10. package/ios/BBPlayerViewManager.m +0 -2
  11. package/lib/commonjs/BBModalPlayer.js +21 -0
  12. package/lib/commonjs/BBModalPlayer.js.map +1 -0
  13. package/lib/commonjs/BBOutstreamView.js +0 -1
  14. package/lib/commonjs/BBOutstreamView.js.map +1 -1
  15. package/lib/commonjs/BBPlayerView.js +0 -1
  16. package/lib/commonjs/BBPlayerView.js.map +1 -1
  17. package/lib/commonjs/NativeCommands.js +24 -24
  18. package/lib/commonjs/NativeCommands.js.map +1 -1
  19. package/lib/commonjs/index.js +9 -1
  20. package/lib/commonjs/index.js.map +1 -1
  21. package/lib/commonjs/specs/NativeBBPlayerModule.js.map +1 -1
  22. package/lib/module/BBModalPlayer.js +17 -0
  23. package/lib/module/BBModalPlayer.js.map +1 -0
  24. package/lib/module/BBOutstreamView.js +0 -1
  25. package/lib/module/BBOutstreamView.js.map +1 -1
  26. package/lib/module/BBPlayerView.js +0 -1
  27. package/lib/module/BBPlayerView.js.map +1 -1
  28. package/lib/module/NativeCommands.js +24 -24
  29. package/lib/module/NativeCommands.js.map +1 -1
  30. package/lib/module/index.js +1 -0
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/specs/NativeBBPlayerModule.js.map +1 -1
  33. package/lib/typescript/src/BBModalPlayer.d.ts +13 -0
  34. package/lib/typescript/src/BBModalPlayer.d.ts.map +1 -0
  35. package/lib/typescript/src/BBOutstreamView.d.ts.map +1 -1
  36. package/lib/typescript/src/BBPlayer.types.d.ts +24 -20
  37. package/lib/typescript/src/BBPlayer.types.d.ts.map +1 -1
  38. package/lib/typescript/src/BBPlayerView.d.ts +0 -2
  39. package/lib/typescript/src/BBPlayerView.d.ts.map +1 -1
  40. package/lib/typescript/src/NativeCommands.d.ts +8 -9
  41. package/lib/typescript/src/NativeCommands.d.ts.map +1 -1
  42. package/lib/typescript/src/index.d.ts +2 -0
  43. package/lib/typescript/src/index.d.ts.map +1 -1
  44. package/lib/typescript/src/specs/NativeBBPlayerModule.d.ts +13 -8
  45. package/lib/typescript/src/specs/NativeBBPlayerModule.d.ts.map +1 -1
  46. package/package.json +8 -11
  47. package/src/BBModalPlayer.ts +32 -0
  48. package/src/BBOutstreamView.tsx +0 -1
  49. package/src/BBPlayer.types.ts +31 -17
  50. package/src/BBPlayerView.tsx +0 -12
  51. package/src/NativeCommands.ts +37 -26
  52. package/src/index.ts +2 -0
  53. package/src/specs/NativeBBPlayerModule.ts +25 -8
  54. package/android/proguard-rules.pro +0 -59
@@ -1,5 +1,6 @@
1
1
  import Foundation
2
2
  import React
3
+ import BBNativePlayerKit
3
4
 
4
5
  /**
5
6
  * Global registry for BBPlayerView instances.
@@ -39,21 +40,44 @@ class BBPlayerViewRegistry: NSObject {
39
40
 
40
41
  /**
41
42
  * Native Module for BBPlayer commands.
43
+ * Extends RCTEventEmitter to support module-level events (modal player).
42
44
  * This module looks up BBPlayerView instances by their React tag and dispatches commands to them.
43
45
  */
44
46
  @objc(BBPlayerModule)
45
- class BBPlayerModule: NSObject {
47
+ class BBPlayerModule: RCTEventEmitter {
46
48
 
47
- @objc var bridge: RCTBridge?
49
+ private var modalPlayerView: BBNativePlayerView?
50
+ private var modalDelegate: ModalPlayerDelegate?
51
+ private var hasListeners = false
48
52
 
49
- @objc static func requiresMainQueueSetup() -> Bool {
53
+ @objc override static func requiresMainQueueSetup() -> Bool {
50
54
  return true
51
55
  }
52
56
 
53
- @objc static func moduleName() -> String {
57
+ @objc override static func moduleName() -> String! {
54
58
  return "BBPlayerModule"
55
59
  }
56
60
 
61
+ override func supportedEvents() -> [String]! {
62
+ return [
63
+ "modalPlayerDismissed",
64
+ "modalPlayerPlay",
65
+ "modalPlayerPause",
66
+ "modalPlayerEnded",
67
+ "modalPlayerError",
68
+ "modalPlayerApiReady",
69
+ "modalPlayerCanPlay",
70
+ ]
71
+ }
72
+
73
+ override func startObserving() {
74
+ hasListeners = true
75
+ }
76
+
77
+ override func stopObserving() {
78
+ hasListeners = false
79
+ }
80
+
57
81
  // MARK: - Helper to get view by tag
58
82
 
59
83
  private func getView(_ reactTag: NSNumber) -> BBPlayerView? {
@@ -159,50 +183,50 @@ class BBPlayerModule: NSObject {
159
183
  }
160
184
  }
161
185
 
162
- @objc func loadWithClipId(_ viewTag: NSNumber, clipId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
186
+ @objc func loadWithClipId(_ viewTag: NSNumber, clipId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
163
187
  DispatchQueue.main.async {
164
- self.getView(viewTag)?.loadWithClipId(clipId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
188
+ self.getView(viewTag)?.loadWithClipId(clipId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
165
189
  }
166
190
  }
167
191
 
168
- @objc func loadWithClipListId(_ viewTag: NSNumber, clipListId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
192
+ @objc func loadWithClipListId(_ viewTag: NSNumber, clipListId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
169
193
  DispatchQueue.main.async {
170
- self.getView(viewTag)?.loadWithClipListId(clipListId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
194
+ self.getView(viewTag)?.loadWithClipListId(clipListId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
171
195
  }
172
196
  }
173
197
 
174
- @objc func loadWithProjectId(_ viewTag: NSNumber, projectId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
198
+ @objc func loadWithProjectId(_ viewTag: NSNumber, projectId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
175
199
  DispatchQueue.main.async {
176
- self.getView(viewTag)?.loadWithProjectId(projectId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
200
+ self.getView(viewTag)?.loadWithProjectId(projectId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
177
201
  }
178
202
  }
179
203
 
180
- @objc func loadWithClipJson(_ viewTag: NSNumber, clipJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
204
+ @objc func loadWithClipJson(_ viewTag: NSNumber, clipJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
181
205
  DispatchQueue.main.async {
182
- self.getView(viewTag)?.loadWithClipJson(clipJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
206
+ self.getView(viewTag)?.loadWithClipJson(clipJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
183
207
  }
184
208
  }
185
209
 
186
- @objc func loadWithClipListJson(_ viewTag: NSNumber, clipListJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
210
+ @objc func loadWithClipListJson(_ viewTag: NSNumber, clipListJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
187
211
  DispatchQueue.main.async {
188
- self.getView(viewTag)?.loadWithClipListJson(clipListJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
212
+ self.getView(viewTag)?.loadWithClipListJson(clipListJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
189
213
  }
190
214
  }
191
215
 
192
- @objc func loadWithProjectJson(_ viewTag: NSNumber, projectJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
216
+ @objc func loadWithProjectJson(_ viewTag: NSNumber, projectJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
193
217
  DispatchQueue.main.async {
194
- self.getView(viewTag)?.loadWithProjectJson(projectJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
218
+ self.getView(viewTag)?.loadWithProjectJson(projectJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
195
219
  }
196
220
  }
197
221
 
198
- @objc func loadWithJsonUrl(_ viewTag: NSNumber, jsonUrl: String?, autoPlay: Bool) {
199
- NSLog("BBPlayerModule.loadWithJsonUrl called - viewTag: %@, jsonUrl: %@, autoPlay: %d", viewTag, jsonUrl ?? "nil", autoPlay)
222
+ @objc func loadWithJsonUrl(_ viewTag: NSNumber, jsonUrl: String?, autoPlay: Bool, contextJson: String?) {
223
+ NSLog("BBPlayerModule.loadWithJsonUrl called - viewTag: %@, jsonUrl: %@, autoPlay: %d, context: %@", viewTag, jsonUrl ?? "nil", autoPlay, contextJson ?? "nil")
200
224
  DispatchQueue.main.async {
201
225
  let view = self.getView(viewTag)
202
226
  NSLog("BBPlayerModule.loadWithJsonUrl - view found: %@", view != nil ? "YES" : "NO")
203
227
  if let view = view, let url = jsonUrl {
204
228
  NSLog("BBPlayerModule.loadWithJsonUrl - calling view.loadWithJsonUrl with url: %@", url)
205
- view.loadWithJsonUrl(url, autoPlay: autoPlay)
229
+ view.loadWithJsonUrl(url, autoPlay: autoPlay, contextJson: contextJson)
206
230
  } else {
207
231
  NSLog("BBPlayerModule.loadWithJsonUrl - FAILED: view=%@, url=%@", view != nil ? "found" : "nil", jsonUrl ?? "nil")
208
232
  }
@@ -218,13 +242,6 @@ class BBPlayerModule: NSObject {
218
242
  }
219
243
  }
220
244
 
221
- @objc func getCurrentTime(_ viewTag: NSNumber, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
222
- DispatchQueue.main.async {
223
- let currentTime = self.getView(viewTag)?.currentTime()
224
- resolver(currentTime)
225
- }
226
- }
227
-
228
245
  @objc func getMuted(_ viewTag: NSNumber, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
229
246
  DispatchQueue.main.async {
230
247
  let muted = self.getView(viewTag)?.muted()
@@ -280,4 +297,99 @@ class BBPlayerModule: NSObject {
280
297
  resolver(playoutData)
281
298
  }
282
299
  }
300
+
301
+ // MARK: - Modal Player API
302
+
303
+ @objc func presentModalPlayer(_ jsonUrl: String, optionsJson: String?) {
304
+ DispatchQueue.main.async {
305
+ guard let rootVC = RCTPresentedViewController() else {
306
+ NSLog("BBPlayerModule: No root view controller found")
307
+ return
308
+ }
309
+
310
+ // Parse options from JSON string
311
+ var options: [String: Any]? = nil
312
+ if let json = optionsJson, let data = json.data(using: .utf8) {
313
+ options = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
314
+ }
315
+
316
+ // Create modal player via native SDK
317
+ let playerView = BBNativePlayer.createModalPlayerView(
318
+ uiViewContoller: rootVC,
319
+ jsonUrl: jsonUrl,
320
+ options: options
321
+ )
322
+
323
+ // Set up delegate for event forwarding
324
+ let delegate = ModalPlayerDelegate(module: self)
325
+ playerView.delegate = delegate
326
+
327
+ self.modalPlayerView = playerView
328
+ self.modalDelegate = delegate
329
+
330
+ NSLog("BBPlayerModule: Modal player presented with URL: %@", jsonUrl)
331
+ }
332
+ }
333
+
334
+ @objc func dismissModalPlayer() {
335
+ DispatchQueue.main.async {
336
+ self.modalPlayerView?.player.closeModalPlayer()
337
+ self.modalPlayerView = nil
338
+ self.modalDelegate = nil
339
+ }
340
+ }
341
+
342
+ @objc override func addListener(_ eventName: String) {
343
+ // Required for RCTEventEmitter
344
+ }
345
+
346
+ @objc override func removeListeners(_ count: Double) {
347
+ // Required for RCTEventEmitter
348
+ }
349
+
350
+ private func emitEvent(_ name: String, body: Any? = nil) {
351
+ if hasListeners {
352
+ sendEvent(withName: name, body: body)
353
+ }
354
+ }
355
+
356
+ // MARK: - Modal Player Delegate
357
+
358
+ private class ModalPlayerDelegate: NSObject, BBNativePlayerViewDelegate {
359
+ weak var module: BBPlayerModule?
360
+
361
+ init(module: BBPlayerModule) {
362
+ self.module = module
363
+ }
364
+
365
+ func bbNativePlayerView(didTriggerPlay playerView: BBNativePlayerView) {
366
+ module?.emitEvent("modalPlayerPlay")
367
+ }
368
+
369
+ func bbNativePlayerView(didTriggerPause playerView: BBNativePlayerView) {
370
+ module?.emitEvent("modalPlayerPause")
371
+ }
372
+
373
+ func bbNativePlayerView(didTriggerEnded playerView: BBNativePlayerView) {
374
+ module?.emitEvent("modalPlayerEnded")
375
+ }
376
+
377
+ func bbNativePlayerView(playerView: BBNativePlayerView, didFailWithError error: String?) {
378
+ module?.emitEvent("modalPlayerError", body: ["error": error ?? "Unknown error"])
379
+ }
380
+
381
+ func bbNativePlayerView(didTriggerApiReady playerView: BBNativePlayerView) {
382
+ module?.emitEvent("modalPlayerApiReady")
383
+ }
384
+
385
+ func bbNativePlayerView(didTriggerCanPlay playerView: BBNativePlayerView) {
386
+ module?.emitEvent("modalPlayerCanPlay")
387
+ }
388
+
389
+ func bbNativePlayerView(didCloseModalPlayer playerView: BBNativePlayerView) {
390
+ module?.emitEvent("modalPlayerDismissed")
391
+ module?.modalPlayerView = nil
392
+ module?.modalDelegate = nil
393
+ }
394
+ }
283
395
  }
@@ -42,16 +42,9 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
42
42
  private var playerView: BBNativePlayerView?
43
43
  private var hasSetup: Bool = false
44
44
 
45
- // Timer for periodic time updates (opt-in for performance)
46
- private var timeUpdateTimer: Timer?
47
45
  private var isPlaying: Bool = false
48
46
  private var currentDuration: Double = 0.0
49
- private var lastKnownTime: Double = 0.0
50
- private var playbackStartTimestamp: CFTimeInterval = 0
51
- private var lastEmittedTime: Double = 0.0
52
47
  private var isInFullscreen: Bool = false
53
- private var backgroundObserver: NSObjectProtocol?
54
- private var foregroundObserver: NSObjectProtocol?
55
48
  // Independent Google Cast button for showing the cast picker
56
49
  private var independentCastButton: GCKUICastButton?
57
50
  // Store parent view controller reference for SDK
@@ -71,18 +64,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
71
64
  }
72
65
  }
73
66
 
74
- @objc var enableTimeUpdates: Bool = false {
75
- didSet {
76
- log("Time updates \(enableTimeUpdates ? "enabled" : "disabled")")
77
-
78
- if !enableTimeUpdates && timeUpdateTimer != nil {
79
- stopTimeUpdates()
80
- } else if enableTimeUpdates && isPlaying && timeUpdateTimer == nil {
81
- startTimeUpdates()
82
- }
83
- }
84
- }
85
-
86
67
  // MARK: - Event handlers (RCTDirectEventBlock)
87
68
 
88
69
  @objc var onDidFailWithError: RCTDirectEventBlock?
@@ -123,7 +104,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
123
104
  @objc var onDidTriggerViewFinished: RCTDirectEventBlock?
124
105
  @objc var onDidTriggerViewStarted: RCTDirectEventBlock?
125
106
  @objc var onDidTriggerVolumeChange: RCTDirectEventBlock?
126
- @objc var onDidTriggerTimeUpdate: RCTDirectEventBlock?
127
107
  @objc var onDidTriggerApiReady: RCTDirectEventBlock?
128
108
 
129
109
  override var intrinsicContentSize: CGSize {
@@ -153,8 +133,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
153
133
  BBPlayerViewRegistry.shared.register(self, tag: tag.intValue)
154
134
  }
155
135
 
156
- setupAppLifecycleObservers()
157
-
158
136
  // Find the parent view controller from the responder chain
159
137
  var responder = self.next
160
138
  while responder != nil {
@@ -180,91 +158,19 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
180
158
  BBPlayerViewRegistry.shared.unregister(tag: tag.intValue)
181
159
  }
182
160
 
183
- if !isInFullscreen {
184
- stopTimeUpdates()
185
- }
186
- }
187
- }
188
-
189
- // Start periodic time updates (1x per second, only if enabled)
190
- private func startTimeUpdates() {
191
- guard enableTimeUpdates, timeUpdateTimer == nil else { return }
192
-
193
- timeUpdateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
194
- guard let self = self, self.isPlaying else { return }
195
-
196
- let elapsedSeconds = CACurrentMediaTime() - self.playbackStartTimestamp
197
- let estimatedTime = self.lastKnownTime + elapsedSeconds
198
- let currentTime = min(estimatedTime, self.currentDuration)
199
-
200
- if self.currentDuration > 0 && abs(currentTime - self.lastEmittedTime) >= 0.5 {
201
- self.lastEmittedTime = currentTime
202
- self.onDidTriggerTimeUpdate?([
203
- "currentTime": currentTime,
204
- "duration": self.currentDuration
205
- ])
206
- }
161
+ // No cleanup needed when not in fullscreen
207
162
  }
208
163
  }
209
164
 
210
- private func stopTimeUpdates() {
211
- timeUpdateTimer?.invalidate()
212
- timeUpdateTimer = nil
213
- }
214
-
215
165
  deinit {
216
166
  // Unregister from view registry
217
167
  if let tag = self.reactTag {
218
168
  BBPlayerViewRegistry.shared.unregister(tag: tag.intValue)
219
169
  }
220
- stopTimeUpdates()
221
- removeAppLifecycleObservers()
222
170
  independentCastButton?.removeFromSuperview()
223
171
  independentCastButton = nil
224
172
  }
225
173
 
226
- // MARK: - App Lifecycle Management
227
-
228
- private func setupAppLifecycleObservers() {
229
- guard backgroundObserver == nil else { return }
230
-
231
- backgroundObserver = NotificationCenter.default.addObserver(
232
- forName: UIApplication.didEnterBackgroundNotification,
233
- object: nil,
234
- queue: .main
235
- ) { [weak self] _ in
236
- guard let self = self else { return }
237
- if self.isPlaying {
238
- self.lastKnownTime = self.calculateCurrentTime()
239
- }
240
- self.stopTimeUpdates()
241
- log("Timer paused - app entered background", level: .debug)
242
- }
243
-
244
- foregroundObserver = NotificationCenter.default.addObserver(
245
- forName: UIApplication.willEnterForegroundNotification,
246
- object: nil,
247
- queue: .main
248
- ) { [weak self] _ in
249
- guard let self = self else { return }
250
- if self.isPlaying && self.enableTimeUpdates {
251
- self.playbackStartTimestamp = CACurrentMediaTime()
252
- self.startTimeUpdates()
253
- log("Timer resumed - app entered foreground", level: .debug)
254
- }
255
- }
256
- }
257
-
258
- private func removeAppLifecycleObservers() {
259
- if let observer = backgroundObserver {
260
- NotificationCenter.default.removeObserver(observer)
261
- backgroundObserver = nil
262
- }
263
- if let observer = foregroundObserver {
264
- NotificationCenter.default.removeObserver(observer)
265
- foregroundObserver = nil
266
- }
267
- }
268
174
 
269
175
  // MARK: - Player Setup (Simplified - no intermediate view controller)
270
176
 
@@ -409,7 +315,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
409
315
 
410
316
  func bbNativePlayerView(didTriggerEnded playerView: BBNativePlayerView) {
411
317
  isPlaying = false
412
- stopTimeUpdates()
413
318
  onDidTriggerEnded?([:])
414
319
  }
415
320
 
@@ -451,7 +356,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
451
356
 
452
357
  func bbNativePlayerView(didTriggerPause playerView: BBNativePlayerView) {
453
358
  isPlaying = false
454
- stopTimeUpdates()
455
359
  onDidTriggerPause?([:])
456
360
  }
457
361
 
@@ -465,10 +369,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
465
369
 
466
370
  func bbNativePlayerView(didTriggerPlaying playerView: BBNativePlayerView) {
467
371
  isPlaying = true
468
- playbackStartTimestamp = CACurrentMediaTime()
469
- lastEmittedTime = 0.0
470
- lastKnownTime = calculateCurrentTime()
471
- startTimeUpdates()
472
372
  onDidTriggerPlaying?([:])
473
373
  }
474
374
 
@@ -500,9 +400,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
500
400
  }
501
401
 
502
402
  func bbNativePlayerView(playerView: BBNativePlayerView, didTriggerSeeked seekOffset: Double) {
503
- lastKnownTime = seekOffset
504
- playbackStartTimestamp = CACurrentMediaTime()
505
- lastEmittedTime = 0.0
506
403
  onDidTriggerSeeked?(["payload": seekOffset as Any])
507
404
  }
508
405
 
@@ -557,18 +454,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
557
454
  addSubview(castButton)
558
455
  }
559
456
 
560
- // MARK: - Private Helper Methods
561
-
562
- private func calculateCurrentTime() -> Double {
563
- if isPlaying && playbackStartTimestamp > 0 {
564
- let elapsedSeconds = CACurrentMediaTime() - playbackStartTimestamp
565
- let estimatedTime = lastKnownTime + elapsedSeconds
566
- return min(estimatedTime, currentDuration)
567
- } else {
568
- return lastKnownTime
569
- }
570
- }
571
-
572
457
  // MARK: - Public API Methods
573
458
 
574
459
  func adMediaHeight() -> Int? {
@@ -587,10 +472,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
587
472
  return nil
588
473
  }
589
474
 
590
- func currentTime() -> Double? {
591
- return calculateCurrentTime()
592
- }
593
-
594
475
  func duration() -> Double? {
595
476
  return playerView?.player.duration
596
477
  }
@@ -691,9 +572,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
691
572
  }
692
573
 
693
574
  func seekRelative(_ offsetInSeconds: Double) {
694
- let currentTime = calculateCurrentTime()
695
- let newPosition = max(0, min(currentDuration, currentTime + offsetInSeconds))
696
- playerView?.player.seek(offsetInSeconds: newPosition as NSNumber)
575
+ playerView?.player.seekRelative(offsetInSeconds: offsetInSeconds as NSNumber)
697
576
  }
698
577
 
699
578
  func setMuted(_ muted: Bool) {
@@ -704,28 +583,48 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
704
583
  playerView?.setApiProperty(property: .volume, value: Float(volume))
705
584
  }
706
585
 
707
- func loadWithClipId(_ clipId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
708
- playerView?.player.loadWithClipId(clipId: clipId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?)
586
+ // Helper to parse context JSON into a dictionary for the native SDK
587
+ private func parseContext(_ contextJson: String?) -> [String: Any]? {
588
+ guard let jsonString = contextJson, !jsonString.isEmpty else { return nil }
589
+ guard let data = jsonString.data(using: .utf8) else { return nil }
590
+ do {
591
+ if let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
592
+ return dict
593
+ }
594
+ } catch {
595
+ log("Failed to parse context JSON: \(error)", level: .warning)
596
+ }
597
+ return nil
709
598
  }
710
599
 
711
- func loadWithClipListId(_ clipListId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
712
- playerView?.player.loadWithClipListId(clipListId: clipListId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?)
600
+ func loadWithClipId(_ clipId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
601
+ let context = parseContext(contextJson)
602
+ playerView?.player.loadWithClipId(clipId: clipId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
713
603
  }
714
604
 
715
- func loadWithProjectId(_ projectId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
716
- playerView?.player.loadWithProjectId(projectId: projectId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?)
605
+ func loadWithClipListId(_ clipListId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
606
+ let context = parseContext(contextJson)
607
+ playerView?.player.loadWithClipListId(clipListId: clipListId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
717
608
  }
718
609
 
719
- func loadWithClipJson(_ clipJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
720
- playerView?.player.loadWithClipJson(clipJson: clipJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?)
610
+ func loadWithProjectId(_ projectId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
611
+ let context = parseContext(contextJson)
612
+ playerView?.player.loadWithProjectId(projectId: projectId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
721
613
  }
722
614
 
723
- func loadWithClipListJson(_ clipListJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
724
- playerView?.player.loadWithClipListJson(clipListJson: clipListJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?)
615
+ func loadWithClipJson(_ clipJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
616
+ let context = parseContext(contextJson)
617
+ playerView?.player.loadWithClipJson(clipJson: clipJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
725
618
  }
726
619
 
727
- func loadWithProjectJson(_ projectJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
728
- playerView?.player.loadWithProjectJson(projectJson: projectJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?)
620
+ func loadWithClipListJson(_ clipListJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
621
+ let context = parseContext(contextJson)
622
+ playerView?.player.loadWithClipListJson(clipListJson: clipListJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
623
+ }
624
+
625
+ func loadWithProjectJson(_ projectJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
626
+ let context = parseContext(contextJson)
627
+ playerView?.player.loadWithProjectJson(projectJson: projectJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
729
628
  }
730
629
 
731
630
  /**
@@ -735,8 +634,8 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
735
634
  *
736
635
  * Note: Shorts URLs (/sh/{id}.json) are NOT supported here - use BBShortsView instead.
737
636
  */
738
- func loadWithJsonUrl(_ url: String, autoPlay: Bool) {
739
- NSLog("BBPlayerView.loadWithJsonUrl called - url: %@, autoPlay: %d", url, autoPlay)
637
+ func loadWithJsonUrl(_ url: String, autoPlay: Bool, contextJson: String? = nil) {
638
+ NSLog("BBPlayerView.loadWithJsonUrl called - url: %@, autoPlay: %d, context: %@", url, autoPlay, contextJson ?? "nil")
740
639
  guard playerView != nil else {
741
640
  NSLog("BBPlayerView.loadWithJsonUrl ERROR - playerView not initialized")
742
641
  return
@@ -754,6 +653,8 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
754
653
  let projectIdPattern = "/pj/([0-9]+)\\.json|/project/([0-9]+)"
755
654
  let shortsIdPattern = "/sh/([0-9]+)\\.json"
756
655
 
656
+ let context = parseContext(contextJson)
657
+
757
658
  if let shortsMatch = url.range(of: shortsIdPattern, options: .regularExpression) {
758
659
  // Shorts require a separate BBShortsView component
759
660
  log("ERROR - Shorts URLs are not supported in BBPlayerView. Use BBShortsView instead.", level: .error)
@@ -765,7 +666,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
765
666
  // Extract the cliplist ID
766
667
  if let clipListId = extractIdFromUrl(url, pattern: clipListIdPattern) {
767
668
  NSLog("BBPlayerView.loadWithJsonUrl - Loading ClipList by ID: %@", clipListId)
768
- playerView?.player.loadWithClipListId(clipListId: clipListId, initiator: "external", autoPlay: autoPlay, seekTo: nil)
669
+ playerView?.player.loadWithClipListId(clipListId: clipListId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
769
670
  } else {
770
671
  NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract cliplist ID from URL: %@", url)
771
672
  }
@@ -776,7 +677,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
776
677
  // Extract the project ID
777
678
  if let projectId = extractIdFromUrl(url, pattern: projectIdPattern) {
778
679
  NSLog("BBPlayerView.loadWithJsonUrl - Loading Project by ID: %@", projectId)
779
- playerView?.player.loadWithProjectId(projectId: projectId, initiator: "external", autoPlay: autoPlay, seekTo: nil)
680
+ playerView?.player.loadWithProjectId(projectId: projectId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
780
681
  } else {
781
682
  NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract project ID from URL: %@", url)
782
683
  }
@@ -787,7 +688,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
787
688
  // Extract the clip ID
788
689
  if let clipId = extractIdFromUrl(url, pattern: clipIdPattern) {
789
690
  NSLog("BBPlayerView.loadWithJsonUrl - Loading Clip by ID: %@", clipId)
790
- playerView?.player.loadWithClipId(clipId: clipId, initiator: "external", autoPlay: autoPlay, seekTo: nil)
691
+ playerView?.player.loadWithClipId(clipId: clipId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
791
692
  } else {
792
693
  NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract clip ID from URL: %@", url)
793
694
  }
@@ -7,7 +7,6 @@
7
7
  // Props
8
8
  RCT_EXPORT_VIEW_PROPERTY(jsonUrl, NSString)
9
9
  RCT_EXPORT_VIEW_PROPERTY(options, NSDictionary)
10
- RCT_EXPORT_VIEW_PROPERTY(enableTimeUpdates, BOOL)
11
10
 
12
11
  // Event handlers
13
12
  RCT_EXPORT_VIEW_PROPERTY(onDidFailWithError, RCTDirectEventBlock)
@@ -48,7 +47,6 @@ RCT_EXPORT_VIEW_PROPERTY(onDidTriggerStateChange, RCTDirectEventBlock)
48
47
  RCT_EXPORT_VIEW_PROPERTY(onDidTriggerViewFinished, RCTDirectEventBlock)
49
48
  RCT_EXPORT_VIEW_PROPERTY(onDidTriggerViewStarted, RCTDirectEventBlock)
50
49
  RCT_EXPORT_VIEW_PROPERTY(onDidTriggerVolumeChange, RCTDirectEventBlock)
51
- RCT_EXPORT_VIEW_PROPERTY(onDidTriggerTimeUpdate, RCTDirectEventBlock)
52
50
  RCT_EXPORT_VIEW_PROPERTY(onDidTriggerApiReady, RCTDirectEventBlock)
53
51
 
54
52
  // Commands (methods callable from JS)
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BBModalPlayer = void 0;
7
+ var _reactNative = require("react-native");
8
+ const BBPlayerModule = _reactNative.NativeModules.BBPlayerModule;
9
+ const eventEmitter = new _reactNative.NativeEventEmitter(BBPlayerModule);
10
+ const BBModalPlayer = exports.BBModalPlayer = {
11
+ present(jsonUrl, options) {
12
+ BBPlayerModule?.presentModalPlayer(jsonUrl, options ? JSON.stringify(options) : null);
13
+ },
14
+ dismiss() {
15
+ BBPlayerModule?.dismissModalPlayer();
16
+ },
17
+ addEventListener(event, callback) {
18
+ return eventEmitter.addListener(event, callback);
19
+ }
20
+ };
21
+ //# sourceMappingURL=BBModalPlayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","BBPlayerModule","NativeModules","eventEmitter","NativeEventEmitter","BBModalPlayer","exports","present","jsonUrl","options","presentModalPlayer","JSON","stringify","dismiss","dismissModalPlayer","addEventListener","event","callback","addListener"],"sourceRoot":"../../src","sources":["BBModalPlayer.ts"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA,MAAMC,cAAc,GAAGC,0BAAa,CAACD,cAAc;AACnD,MAAME,YAAY,GAAG,IAAIC,+BAAkB,CAACH,cAAc,CAAC;AAUpD,MAAMI,aAAa,GAAAC,OAAA,CAAAD,aAAA,GAAG;EAC3BE,OAAOA,CAACC,OAAe,EAAEC,OAA4B,EAAE;IACrDR,cAAc,EAAES,kBAAkB,CAChCF,OAAO,EACPC,OAAO,GAAGE,IAAI,CAACC,SAAS,CAACH,OAAO,CAAC,GAAG,IACtC,CAAC;EACH,CAAC;EAEDI,OAAOA,CAAA,EAAG;IACRZ,cAAc,EAAEa,kBAAkB,CAAC,CAAC;EACtC,CAAC;EAEDC,gBAAgBA,CACdC,KAAa,EACbC,QAAkC,EAClC;IACA,OAAOd,YAAY,CAACe,WAAW,CAACF,KAAK,EAAEC,QAAQ,CAAC;EAClD;AACF,CAAC","ignoreList":[]}
@@ -171,7 +171,6 @@ const BBOutstreamView = /*#__PURE__*/(0, _react.forwardRef)(({
171
171
  loadWithProjectJson: (...args) => playerRef.current?.loadWithProjectJson(...args),
172
172
  loadWithJsonUrl: (...args) => playerRef.current?.loadWithJsonUrl(...args),
173
173
  getDuration: () => playerRef.current?.getDuration() ?? Promise.resolve(null),
174
- getCurrentTime: () => playerRef.current?.getCurrentTime() ?? Promise.resolve(null),
175
174
  getMuted: () => playerRef.current?.getMuted() ?? Promise.resolve(null),
176
175
  getVolume: () => playerRef.current?.getVolume() ?? Promise.resolve(null),
177
176
  getPhase: () => playerRef.current?.getPhase() ?? Promise.resolve(null),