@bluebillywig/react-native-bb-player 8.42.15 → 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.
- package/README.md +80 -59
- package/android/build.gradle +2 -1
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerModule.kt +146 -28
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerView.kt +74 -165
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerViewManager.kt +0 -6
- package/android/src/paper/java/com/bluebillywig/bbplayer/NativeBBPlayerModuleSpec.java +19 -8
- package/ios/BBPlayerModule.mm +17 -8
- package/ios/BBPlayerModule.swift +193 -31
- package/ios/BBPlayerView.swift +141 -132
- package/ios/BBPlayerViewManager.m +0 -2
- package/ios/BBPlayerViewManager.swift +8 -0
- package/lib/commonjs/BBModalPlayer.js +21 -0
- package/lib/commonjs/BBModalPlayer.js.map +1 -0
- package/lib/commonjs/BBOutstreamView.js +0 -1
- package/lib/commonjs/BBOutstreamView.js.map +1 -1
- package/lib/commonjs/BBPlayerView.js +0 -1
- package/lib/commonjs/BBPlayerView.js.map +1 -1
- package/lib/commonjs/NativeCommands.js +24 -24
- package/lib/commonjs/NativeCommands.js.map +1 -1
- package/lib/commonjs/index.js +9 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/NativeBBPlayerModule.js.map +1 -1
- package/lib/module/BBModalPlayer.js +17 -0
- package/lib/module/BBModalPlayer.js.map +1 -0
- package/lib/module/BBOutstreamView.js +0 -1
- package/lib/module/BBOutstreamView.js.map +1 -1
- package/lib/module/BBPlayerView.js +0 -1
- package/lib/module/BBPlayerView.js.map +1 -1
- package/lib/module/NativeCommands.js +24 -24
- package/lib/module/NativeCommands.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/NativeBBPlayerModule.js.map +1 -1
- package/lib/typescript/src/BBModalPlayer.d.ts +13 -0
- package/lib/typescript/src/BBModalPlayer.d.ts.map +1 -0
- package/lib/typescript/src/BBOutstreamView.d.ts.map +1 -1
- package/lib/typescript/src/BBPlayer.types.d.ts +24 -20
- package/lib/typescript/src/BBPlayer.types.d.ts.map +1 -1
- package/lib/typescript/src/BBPlayerView.d.ts +0 -2
- package/lib/typescript/src/BBPlayerView.d.ts.map +1 -1
- package/lib/typescript/src/NativeCommands.d.ts +8 -9
- package/lib/typescript/src/NativeCommands.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeBBPlayerModule.d.ts +13 -8
- package/lib/typescript/src/specs/NativeBBPlayerModule.d.ts.map +1 -1
- package/package.json +8 -11
- package/src/BBModalPlayer.ts +32 -0
- package/src/BBOutstreamView.tsx +0 -1
- package/src/BBPlayer.types.ts +31 -17
- package/src/BBPlayerView.tsx +0 -12
- package/src/NativeCommands.ts +37 -26
- package/src/index.ts +2 -0
- package/src/specs/NativeBBPlayerModule.ts +25 -8
- package/android/proguard-rules.pro +0 -59
package/ios/BBPlayerModule.swift
CHANGED
|
@@ -1,31 +1,100 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import React
|
|
3
|
+
import BBNativePlayerKit
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Global registry for BBPlayerView instances.
|
|
7
|
+
* This is needed because the bridge.uiManager.view(forReactTag:) doesn't work
|
|
8
|
+
* with the New Architecture (Fabric). Views register themselves when created.
|
|
9
|
+
*/
|
|
10
|
+
class BBPlayerViewRegistry: NSObject {
|
|
11
|
+
static let shared = BBPlayerViewRegistry()
|
|
12
|
+
|
|
13
|
+
private var views: [Int: BBPlayerView] = [:]
|
|
14
|
+
private let lock = NSLock()
|
|
15
|
+
|
|
16
|
+
private override init() {
|
|
17
|
+
super.init()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func register(_ view: BBPlayerView, tag: Int) {
|
|
21
|
+
lock.lock()
|
|
22
|
+
defer { lock.unlock() }
|
|
23
|
+
views[tag] = view
|
|
24
|
+
NSLog("BBPlayerViewRegistry: Registered view with tag %d (total: %d)", tag, views.count)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func unregister(tag: Int) {
|
|
28
|
+
lock.lock()
|
|
29
|
+
defer { lock.unlock() }
|
|
30
|
+
views.removeValue(forKey: tag)
|
|
31
|
+
NSLog("BBPlayerViewRegistry: Unregistered view with tag %d (total: %d)", tag, views.count)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func getView(tag: Int) -> BBPlayerView? {
|
|
35
|
+
lock.lock()
|
|
36
|
+
defer { lock.unlock() }
|
|
37
|
+
return views[tag]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
3
40
|
|
|
4
41
|
/**
|
|
5
42
|
* Native Module for BBPlayer commands.
|
|
43
|
+
* Extends RCTEventEmitter to support module-level events (modal player).
|
|
6
44
|
* This module looks up BBPlayerView instances by their React tag and dispatches commands to them.
|
|
7
45
|
*/
|
|
8
46
|
@objc(BBPlayerModule)
|
|
9
|
-
class BBPlayerModule:
|
|
47
|
+
class BBPlayerModule: RCTEventEmitter {
|
|
10
48
|
|
|
11
|
-
|
|
49
|
+
private var modalPlayerView: BBNativePlayerView?
|
|
50
|
+
private var modalDelegate: ModalPlayerDelegate?
|
|
51
|
+
private var hasListeners = false
|
|
12
52
|
|
|
13
|
-
@objc static func requiresMainQueueSetup() -> Bool {
|
|
53
|
+
@objc override static func requiresMainQueueSetup() -> Bool {
|
|
14
54
|
return true
|
|
15
55
|
}
|
|
16
56
|
|
|
17
|
-
@objc static func moduleName() -> String {
|
|
57
|
+
@objc override static func moduleName() -> String! {
|
|
18
58
|
return "BBPlayerModule"
|
|
19
59
|
}
|
|
20
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
|
+
|
|
21
81
|
// MARK: - Helper to get view by tag
|
|
22
82
|
|
|
23
83
|
private func getView(_ reactTag: NSNumber) -> BBPlayerView? {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
84
|
+
// First try the view registry (works with both old and new architecture)
|
|
85
|
+
if let view = BBPlayerViewRegistry.shared.getView(tag: reactTag.intValue) {
|
|
86
|
+
return view
|
|
27
87
|
}
|
|
28
|
-
|
|
88
|
+
|
|
89
|
+
// Fallback to bridge.uiManager for old architecture
|
|
90
|
+
if let bridge = self.bridge {
|
|
91
|
+
if let view = bridge.uiManager.view(forReactTag: reactTag) as? BBPlayerView {
|
|
92
|
+
return view
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
NSLog("BBPlayerModule: Could not find view with tag %@", reactTag)
|
|
97
|
+
return nil
|
|
29
98
|
}
|
|
30
99
|
|
|
31
100
|
// MARK: - Commands
|
|
@@ -114,47 +183,52 @@ class BBPlayerModule: NSObject {
|
|
|
114
183
|
}
|
|
115
184
|
}
|
|
116
185
|
|
|
117
|
-
@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?) {
|
|
118
187
|
DispatchQueue.main.async {
|
|
119
|
-
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)
|
|
120
189
|
}
|
|
121
190
|
}
|
|
122
191
|
|
|
123
|
-
@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?) {
|
|
124
193
|
DispatchQueue.main.async {
|
|
125
|
-
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)
|
|
126
195
|
}
|
|
127
196
|
}
|
|
128
197
|
|
|
129
|
-
@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?) {
|
|
130
199
|
DispatchQueue.main.async {
|
|
131
|
-
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)
|
|
132
201
|
}
|
|
133
202
|
}
|
|
134
203
|
|
|
135
|
-
@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?) {
|
|
136
205
|
DispatchQueue.main.async {
|
|
137
|
-
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)
|
|
138
207
|
}
|
|
139
208
|
}
|
|
140
209
|
|
|
141
|
-
@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?) {
|
|
142
211
|
DispatchQueue.main.async {
|
|
143
|
-
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)
|
|
144
213
|
}
|
|
145
214
|
}
|
|
146
215
|
|
|
147
|
-
@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?) {
|
|
148
217
|
DispatchQueue.main.async {
|
|
149
|
-
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)
|
|
150
219
|
}
|
|
151
220
|
}
|
|
152
221
|
|
|
153
|
-
@objc func loadWithJsonUrl(_ viewTag: NSNumber, jsonUrl: String?, autoPlay: Bool) {
|
|
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")
|
|
154
224
|
DispatchQueue.main.async {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
225
|
+
let view = self.getView(viewTag)
|
|
226
|
+
NSLog("BBPlayerModule.loadWithJsonUrl - view found: %@", view != nil ? "YES" : "NO")
|
|
227
|
+
if let view = view, let url = jsonUrl {
|
|
228
|
+
NSLog("BBPlayerModule.loadWithJsonUrl - calling view.loadWithJsonUrl with url: %@", url)
|
|
229
|
+
view.loadWithJsonUrl(url, autoPlay: autoPlay, contextJson: contextJson)
|
|
230
|
+
} else {
|
|
231
|
+
NSLog("BBPlayerModule.loadWithJsonUrl - FAILED: view=%@, url=%@", view != nil ? "found" : "nil", jsonUrl ?? "nil")
|
|
158
232
|
}
|
|
159
233
|
}
|
|
160
234
|
}
|
|
@@ -168,13 +242,6 @@ class BBPlayerModule: NSObject {
|
|
|
168
242
|
}
|
|
169
243
|
}
|
|
170
244
|
|
|
171
|
-
@objc func getCurrentTime(_ viewTag: NSNumber, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
|
|
172
|
-
DispatchQueue.main.async {
|
|
173
|
-
let currentTime = self.getView(viewTag)?.currentTime()
|
|
174
|
-
resolver(currentTime)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
245
|
@objc func getMuted(_ viewTag: NSNumber, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
|
|
179
246
|
DispatchQueue.main.async {
|
|
180
247
|
let muted = self.getView(viewTag)?.muted()
|
|
@@ -230,4 +297,99 @@ class BBPlayerModule: NSObject {
|
|
|
230
297
|
resolver(playoutData)
|
|
231
298
|
}
|
|
232
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
|
+
}
|
|
233
395
|
}
|
package/ios/BBPlayerView.swift
CHANGED
|
@@ -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 {
|
|
@@ -148,7 +128,10 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
148
128
|
self.layer.drawsAsynchronously = true
|
|
149
129
|
self.isOpaque = true
|
|
150
130
|
|
|
151
|
-
|
|
131
|
+
// Register with view registry for command dispatch (supports New Architecture)
|
|
132
|
+
if let tag = self.reactTag {
|
|
133
|
+
BBPlayerViewRegistry.shared.register(self, tag: tag.intValue)
|
|
134
|
+
}
|
|
152
135
|
|
|
153
136
|
// Find the parent view controller from the responder chain
|
|
154
137
|
var responder = self.next
|
|
@@ -170,87 +153,24 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
170
153
|
} else {
|
|
171
154
|
log("BBPlayerView.didMoveToWindow - view removed from window, isInFullscreen: \(isInFullscreen)")
|
|
172
155
|
|
|
173
|
-
|
|
174
|
-
|
|
156
|
+
// Unregister from view registry when removed from window (unless in fullscreen)
|
|
157
|
+
if !isInFullscreen, let tag = self.reactTag {
|
|
158
|
+
BBPlayerViewRegistry.shared.unregister(tag: tag.intValue)
|
|
175
159
|
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
160
|
|
|
179
|
-
|
|
180
|
-
private func startTimeUpdates() {
|
|
181
|
-
guard enableTimeUpdates, timeUpdateTimer == nil else { return }
|
|
182
|
-
|
|
183
|
-
timeUpdateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
|
184
|
-
guard let self = self, self.isPlaying else { return }
|
|
185
|
-
|
|
186
|
-
let elapsedSeconds = CACurrentMediaTime() - self.playbackStartTimestamp
|
|
187
|
-
let estimatedTime = self.lastKnownTime + elapsedSeconds
|
|
188
|
-
let currentTime = min(estimatedTime, self.currentDuration)
|
|
189
|
-
|
|
190
|
-
if self.currentDuration > 0 && abs(currentTime - self.lastEmittedTime) >= 0.5 {
|
|
191
|
-
self.lastEmittedTime = currentTime
|
|
192
|
-
self.onDidTriggerTimeUpdate?([
|
|
193
|
-
"currentTime": currentTime,
|
|
194
|
-
"duration": self.currentDuration
|
|
195
|
-
])
|
|
196
|
-
}
|
|
161
|
+
// No cleanup needed when not in fullscreen
|
|
197
162
|
}
|
|
198
163
|
}
|
|
199
164
|
|
|
200
|
-
private func stopTimeUpdates() {
|
|
201
|
-
timeUpdateTimer?.invalidate()
|
|
202
|
-
timeUpdateTimer = nil
|
|
203
|
-
}
|
|
204
|
-
|
|
205
165
|
deinit {
|
|
206
|
-
|
|
207
|
-
|
|
166
|
+
// Unregister from view registry
|
|
167
|
+
if let tag = self.reactTag {
|
|
168
|
+
BBPlayerViewRegistry.shared.unregister(tag: tag.intValue)
|
|
169
|
+
}
|
|
208
170
|
independentCastButton?.removeFromSuperview()
|
|
209
171
|
independentCastButton = nil
|
|
210
172
|
}
|
|
211
173
|
|
|
212
|
-
// MARK: - App Lifecycle Management
|
|
213
|
-
|
|
214
|
-
private func setupAppLifecycleObservers() {
|
|
215
|
-
guard backgroundObserver == nil else { return }
|
|
216
|
-
|
|
217
|
-
backgroundObserver = NotificationCenter.default.addObserver(
|
|
218
|
-
forName: UIApplication.didEnterBackgroundNotification,
|
|
219
|
-
object: nil,
|
|
220
|
-
queue: .main
|
|
221
|
-
) { [weak self] _ in
|
|
222
|
-
guard let self = self else { return }
|
|
223
|
-
if self.isPlaying {
|
|
224
|
-
self.lastKnownTime = self.calculateCurrentTime()
|
|
225
|
-
}
|
|
226
|
-
self.stopTimeUpdates()
|
|
227
|
-
log("Timer paused - app entered background", level: .debug)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
foregroundObserver = NotificationCenter.default.addObserver(
|
|
231
|
-
forName: UIApplication.willEnterForegroundNotification,
|
|
232
|
-
object: nil,
|
|
233
|
-
queue: .main
|
|
234
|
-
) { [weak self] _ in
|
|
235
|
-
guard let self = self else { return }
|
|
236
|
-
if self.isPlaying && self.enableTimeUpdates {
|
|
237
|
-
self.playbackStartTimestamp = CACurrentMediaTime()
|
|
238
|
-
self.startTimeUpdates()
|
|
239
|
-
log("Timer resumed - app entered foreground", level: .debug)
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private func removeAppLifecycleObservers() {
|
|
245
|
-
if let observer = backgroundObserver {
|
|
246
|
-
NotificationCenter.default.removeObserver(observer)
|
|
247
|
-
backgroundObserver = nil
|
|
248
|
-
}
|
|
249
|
-
if let observer = foregroundObserver {
|
|
250
|
-
NotificationCenter.default.removeObserver(observer)
|
|
251
|
-
foregroundObserver = nil
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
174
|
|
|
255
175
|
// MARK: - Player Setup (Simplified - no intermediate view controller)
|
|
256
176
|
|
|
@@ -395,7 +315,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
395
315
|
|
|
396
316
|
func bbNativePlayerView(didTriggerEnded playerView: BBNativePlayerView) {
|
|
397
317
|
isPlaying = false
|
|
398
|
-
stopTimeUpdates()
|
|
399
318
|
onDidTriggerEnded?([:])
|
|
400
319
|
}
|
|
401
320
|
|
|
@@ -437,7 +356,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
437
356
|
|
|
438
357
|
func bbNativePlayerView(didTriggerPause playerView: BBNativePlayerView) {
|
|
439
358
|
isPlaying = false
|
|
440
|
-
stopTimeUpdates()
|
|
441
359
|
onDidTriggerPause?([:])
|
|
442
360
|
}
|
|
443
361
|
|
|
@@ -451,10 +369,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
451
369
|
|
|
452
370
|
func bbNativePlayerView(didTriggerPlaying playerView: BBNativePlayerView) {
|
|
453
371
|
isPlaying = true
|
|
454
|
-
playbackStartTimestamp = CACurrentMediaTime()
|
|
455
|
-
lastEmittedTime = 0.0
|
|
456
|
-
lastKnownTime = calculateCurrentTime()
|
|
457
|
-
startTimeUpdates()
|
|
458
372
|
onDidTriggerPlaying?([:])
|
|
459
373
|
}
|
|
460
374
|
|
|
@@ -486,9 +400,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
486
400
|
}
|
|
487
401
|
|
|
488
402
|
func bbNativePlayerView(playerView: BBNativePlayerView, didTriggerSeeked seekOffset: Double) {
|
|
489
|
-
lastKnownTime = seekOffset
|
|
490
|
-
playbackStartTimestamp = CACurrentMediaTime()
|
|
491
|
-
lastEmittedTime = 0.0
|
|
492
403
|
onDidTriggerSeeked?(["payload": seekOffset as Any])
|
|
493
404
|
}
|
|
494
405
|
|
|
@@ -543,18 +454,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
543
454
|
addSubview(castButton)
|
|
544
455
|
}
|
|
545
456
|
|
|
546
|
-
// MARK: - Private Helper Methods
|
|
547
|
-
|
|
548
|
-
private func calculateCurrentTime() -> Double {
|
|
549
|
-
if isPlaying && playbackStartTimestamp > 0 {
|
|
550
|
-
let elapsedSeconds = CACurrentMediaTime() - playbackStartTimestamp
|
|
551
|
-
let estimatedTime = lastKnownTime + elapsedSeconds
|
|
552
|
-
return min(estimatedTime, currentDuration)
|
|
553
|
-
} else {
|
|
554
|
-
return lastKnownTime
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
457
|
// MARK: - Public API Methods
|
|
559
458
|
|
|
560
459
|
func adMediaHeight() -> Int? {
|
|
@@ -573,10 +472,6 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
573
472
|
return nil
|
|
574
473
|
}
|
|
575
474
|
|
|
576
|
-
func currentTime() -> Double? {
|
|
577
|
-
return calculateCurrentTime()
|
|
578
|
-
}
|
|
579
|
-
|
|
580
475
|
func duration() -> Double? {
|
|
581
476
|
return playerView?.player.duration
|
|
582
477
|
}
|
|
@@ -677,9 +572,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
677
572
|
}
|
|
678
573
|
|
|
679
574
|
func seekRelative(_ offsetInSeconds: Double) {
|
|
680
|
-
|
|
681
|
-
let newPosition = max(0, min(currentDuration, currentTime + offsetInSeconds))
|
|
682
|
-
playerView?.player.seek(offsetInSeconds: newPosition as NSNumber)
|
|
575
|
+
playerView?.player.seekRelative(offsetInSeconds: offsetInSeconds as NSNumber)
|
|
683
576
|
}
|
|
684
577
|
|
|
685
578
|
func setMuted(_ muted: Bool) {
|
|
@@ -690,28 +583,144 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
690
583
|
playerView?.setApiProperty(property: .volume, value: Float(volume))
|
|
691
584
|
}
|
|
692
585
|
|
|
693
|
-
|
|
694
|
-
|
|
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
|
|
598
|
+
}
|
|
599
|
+
|
|
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)
|
|
695
603
|
}
|
|
696
604
|
|
|
697
|
-
func loadWithClipListId(_ clipListId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
|
|
698
|
-
|
|
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)
|
|
699
608
|
}
|
|
700
609
|
|
|
701
|
-
func loadWithProjectId(_ projectId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
|
|
702
|
-
|
|
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)
|
|
703
613
|
}
|
|
704
614
|
|
|
705
|
-
func loadWithClipJson(_ clipJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
|
|
706
|
-
|
|
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)
|
|
707
618
|
}
|
|
708
619
|
|
|
709
|
-
func loadWithClipListJson(_ clipListJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
|
|
710
|
-
|
|
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)
|
|
711
623
|
}
|
|
712
624
|
|
|
713
|
-
func loadWithProjectJson(_ projectJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?) {
|
|
714
|
-
|
|
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)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Load content from a JSON URL into the existing player.
|
|
632
|
+
* Extracts IDs from the URL and uses the native SDK's loadWithXxxId methods.
|
|
633
|
+
* This is more reliable than parsing JSON because the SDK handles all the loading internally.
|
|
634
|
+
*
|
|
635
|
+
* Note: Shorts URLs (/sh/{id}.json) are NOT supported here - use BBShortsView instead.
|
|
636
|
+
*/
|
|
637
|
+
func loadWithJsonUrl(_ url: String, autoPlay: Bool, contextJson: String? = nil) {
|
|
638
|
+
NSLog("BBPlayerView.loadWithJsonUrl called - url: %@, autoPlay: %d, context: %@", url, autoPlay, contextJson ?? "nil")
|
|
639
|
+
guard playerView != nil else {
|
|
640
|
+
NSLog("BBPlayerView.loadWithJsonUrl ERROR - playerView not initialized")
|
|
641
|
+
return
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
NSLog("BBPlayerView.loadWithJsonUrl - playerView exists, parsing URL")
|
|
645
|
+
|
|
646
|
+
// Extract ID from URL patterns like:
|
|
647
|
+
// /c/{id}.json or /mediaclip/{id}.json -> clip ID
|
|
648
|
+
// /l/{id}.json or /mediacliplist/{id}.json -> clip list ID
|
|
649
|
+
// /pj/{id}.json or /project/{id}.json -> project ID
|
|
650
|
+
|
|
651
|
+
let clipIdPattern = "/c/([0-9]+)\\.json|/mediaclip/([0-9]+)"
|
|
652
|
+
let clipListIdPattern = "/l/([0-9]+)\\.json|/mediacliplist/([0-9]+)"
|
|
653
|
+
let projectIdPattern = "/pj/([0-9]+)\\.json|/project/([0-9]+)"
|
|
654
|
+
let shortsIdPattern = "/sh/([0-9]+)\\.json"
|
|
655
|
+
|
|
656
|
+
let context = parseContext(contextJson)
|
|
657
|
+
|
|
658
|
+
if let shortsMatch = url.range(of: shortsIdPattern, options: .regularExpression) {
|
|
659
|
+
// Shorts require a separate BBShortsView component
|
|
660
|
+
log("ERROR - Shorts URLs are not supported in BBPlayerView. Use BBShortsView instead.", level: .error)
|
|
661
|
+
onDidFailWithError?(["payload": "Shorts URLs are not supported in BBPlayerView. Use BBShortsView instead."])
|
|
662
|
+
return
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if let match = url.range(of: clipListIdPattern, options: .regularExpression) {
|
|
666
|
+
// Extract the cliplist ID
|
|
667
|
+
if let clipListId = extractIdFromUrl(url, pattern: clipListIdPattern) {
|
|
668
|
+
NSLog("BBPlayerView.loadWithJsonUrl - Loading ClipList by ID: %@", clipListId)
|
|
669
|
+
playerView?.player.loadWithClipListId(clipListId: clipListId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
|
|
670
|
+
} else {
|
|
671
|
+
NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract cliplist ID from URL: %@", url)
|
|
672
|
+
}
|
|
673
|
+
return
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if let match = url.range(of: projectIdPattern, options: .regularExpression) {
|
|
677
|
+
// Extract the project ID
|
|
678
|
+
if let projectId = extractIdFromUrl(url, pattern: projectIdPattern) {
|
|
679
|
+
NSLog("BBPlayerView.loadWithJsonUrl - Loading Project by ID: %@", projectId)
|
|
680
|
+
playerView?.player.loadWithProjectId(projectId: projectId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
|
|
681
|
+
} else {
|
|
682
|
+
NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract project ID from URL: %@", url)
|
|
683
|
+
}
|
|
684
|
+
return
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if let match = url.range(of: clipIdPattern, options: .regularExpression) {
|
|
688
|
+
// Extract the clip ID
|
|
689
|
+
if let clipId = extractIdFromUrl(url, pattern: clipIdPattern) {
|
|
690
|
+
NSLog("BBPlayerView.loadWithJsonUrl - Loading Clip by ID: %@", clipId)
|
|
691
|
+
playerView?.player.loadWithClipId(clipId: clipId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
|
|
692
|
+
} else {
|
|
693
|
+
NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract clip ID from URL: %@", url)
|
|
694
|
+
}
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
NSLog("BBPlayerView.loadWithJsonUrl ERROR - Unknown URL format, cannot extract ID: %@", url)
|
|
699
|
+
onDidFailWithError?(["payload": "Cannot load content: unsupported URL format"])
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Helper to extract numeric ID from URL using regex pattern with capture groups
|
|
704
|
+
*/
|
|
705
|
+
private func extractIdFromUrl(_ url: String, pattern: String) -> String? {
|
|
706
|
+
do {
|
|
707
|
+
let regex = try NSRegularExpression(pattern: pattern, options: [])
|
|
708
|
+
let range = NSRange(url.startIndex..., in: url)
|
|
709
|
+
if let match = regex.firstMatch(in: url, options: [], range: range) {
|
|
710
|
+
// Try each capture group (pattern has multiple alternatives with |)
|
|
711
|
+
for i in 1..<match.numberOfRanges {
|
|
712
|
+
if let groupRange = Range(match.range(at: i), in: url) {
|
|
713
|
+
let extracted = String(url[groupRange])
|
|
714
|
+
if !extracted.isEmpty {
|
|
715
|
+
return extracted
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
} catch {
|
|
721
|
+
log("ERROR - Regex error: \(error)", level: .error)
|
|
722
|
+
}
|
|
723
|
+
return nil
|
|
715
724
|
}
|
|
716
725
|
|
|
717
726
|
func showCastPicker() {
|