@bluebillywig/react-native-bb-player 8.44.0 → 8.45.4
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 +4 -3
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerModule.kt +174 -28
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerView.kt +70 -167
- package/android/src/main/java/com/bluebillywig/bbplayer/BBPlayerViewManager.kt +9 -7
- package/android/src/paper/java/com/bluebillywig/bbplayer/NativeBBPlayerModuleSpec.java +19 -8
- package/ios/BBPlayerModule.mm +17 -8
- package/ios/BBPlayerModule.swift +192 -26
- package/ios/BBPlayerView.swift +55 -140
- package/ios/BBPlayerViewManager.m +2 -2
- package/ios/BBPlayerViewManager.swift +12 -0
- package/lib/commonjs/BBModalPlayer.js +21 -0
- package/lib/commonjs/BBModalPlayer.js.map +1 -0
- package/lib/commonjs/BBOutstreamView.js +2 -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 +32 -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 +2 -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 +32 -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 +28 -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 +10 -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 +9 -10
- package/src/BBModalPlayer.ts +33 -0
- package/src/BBOutstreamView.tsx +2 -1
- package/src/BBPlayer.types.ts +35 -17
- package/src/BBPlayerView.tsx +0 -12
- package/src/NativeCommands.ts +45 -26
- package/src/index.ts +2 -0
- package/src/specs/NativeBBPlayerModule.ts +23 -8
- package/android/proguard-rules.pro +0 -59
package/ios/BBPlayerModule.swift
CHANGED
|
@@ -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,45 @@ 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:
|
|
47
|
+
class BBPlayerModule: RCTEventEmitter {
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
private var modalPlayerView: BBNativePlayerView?
|
|
50
|
+
private var modalDelegate: ModalPlayerDelegate?
|
|
51
|
+
private var pendingModalLoad: (() -> Void)?
|
|
52
|
+
private var hasListeners = false
|
|
48
53
|
|
|
49
|
-
@objc static func requiresMainQueueSetup() -> Bool {
|
|
54
|
+
@objc override static func requiresMainQueueSetup() -> Bool {
|
|
50
55
|
return true
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
@objc static func moduleName() -> String {
|
|
58
|
+
@objc override static func moduleName() -> String! {
|
|
54
59
|
return "BBPlayerModule"
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
override func supportedEvents() -> [String]! {
|
|
63
|
+
return [
|
|
64
|
+
"modalPlayerDismissed",
|
|
65
|
+
"modalPlayerPlay",
|
|
66
|
+
"modalPlayerPause",
|
|
67
|
+
"modalPlayerEnded",
|
|
68
|
+
"modalPlayerError",
|
|
69
|
+
"modalPlayerApiReady",
|
|
70
|
+
"modalPlayerCanPlay",
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override func startObserving() {
|
|
75
|
+
hasListeners = true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
override func stopObserving() {
|
|
79
|
+
hasListeners = false
|
|
80
|
+
}
|
|
81
|
+
|
|
57
82
|
// MARK: - Helper to get view by tag
|
|
58
83
|
|
|
59
84
|
private func getView(_ reactTag: NSNumber) -> BBPlayerView? {
|
|
@@ -159,50 +184,50 @@ class BBPlayerModule: NSObject {
|
|
|
159
184
|
}
|
|
160
185
|
}
|
|
161
186
|
|
|
162
|
-
@objc func loadWithClipId(_ viewTag: NSNumber, clipId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
|
|
187
|
+
@objc func loadWithClipId(_ viewTag: NSNumber, clipId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
|
|
163
188
|
DispatchQueue.main.async {
|
|
164
|
-
self.getView(viewTag)?.loadWithClipId(clipId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
|
|
189
|
+
self.getView(viewTag)?.loadWithClipId(clipId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
|
|
165
190
|
}
|
|
166
191
|
}
|
|
167
192
|
|
|
168
|
-
@objc func loadWithClipListId(_ viewTag: NSNumber, clipListId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
|
|
193
|
+
@objc func loadWithClipListId(_ viewTag: NSNumber, clipListId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
|
|
169
194
|
DispatchQueue.main.async {
|
|
170
|
-
self.getView(viewTag)?.loadWithClipListId(clipListId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
|
|
195
|
+
self.getView(viewTag)?.loadWithClipListId(clipListId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
|
|
171
196
|
}
|
|
172
197
|
}
|
|
173
198
|
|
|
174
|
-
@objc func loadWithProjectId(_ viewTag: NSNumber, projectId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
|
|
199
|
+
@objc func loadWithProjectId(_ viewTag: NSNumber, projectId: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
|
|
175
200
|
DispatchQueue.main.async {
|
|
176
|
-
self.getView(viewTag)?.loadWithProjectId(projectId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
|
|
201
|
+
self.getView(viewTag)?.loadWithProjectId(projectId ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
|
|
177
202
|
}
|
|
178
203
|
}
|
|
179
204
|
|
|
180
|
-
@objc func loadWithClipJson(_ viewTag: NSNumber, clipJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
|
|
205
|
+
@objc func loadWithClipJson(_ viewTag: NSNumber, clipJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
|
|
181
206
|
DispatchQueue.main.async {
|
|
182
|
-
self.getView(viewTag)?.loadWithClipJson(clipJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
|
|
207
|
+
self.getView(viewTag)?.loadWithClipJson(clipJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
|
|
183
208
|
}
|
|
184
209
|
}
|
|
185
210
|
|
|
186
|
-
@objc func loadWithClipListJson(_ viewTag: NSNumber, clipListJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
|
|
211
|
+
@objc func loadWithClipListJson(_ viewTag: NSNumber, clipListJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
|
|
187
212
|
DispatchQueue.main.async {
|
|
188
|
-
self.getView(viewTag)?.loadWithClipListJson(clipListJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
|
|
213
|
+
self.getView(viewTag)?.loadWithClipListJson(clipListJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
|
|
189
214
|
}
|
|
190
215
|
}
|
|
191
216
|
|
|
192
|
-
@objc func loadWithProjectJson(_ viewTag: NSNumber, projectJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?) {
|
|
217
|
+
@objc func loadWithProjectJson(_ viewTag: NSNumber, projectJson: String?, initiator: String?, autoPlay: Bool, seekTo: NSNumber?, contextJson: String?) {
|
|
193
218
|
DispatchQueue.main.async {
|
|
194
|
-
self.getView(viewTag)?.loadWithProjectJson(projectJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue)
|
|
219
|
+
self.getView(viewTag)?.loadWithProjectJson(projectJson ?? "", initiator: initiator, autoPlay: autoPlay, seekTo: seekTo?.doubleValue, contextJson: contextJson)
|
|
195
220
|
}
|
|
196
221
|
}
|
|
197
222
|
|
|
198
|
-
@objc func loadWithJsonUrl(_ viewTag: NSNumber, jsonUrl: String?, autoPlay: Bool) {
|
|
199
|
-
NSLog("BBPlayerModule.loadWithJsonUrl called - viewTag: %@, jsonUrl: %@, autoPlay: %d", viewTag, jsonUrl ?? "nil", autoPlay)
|
|
223
|
+
@objc func loadWithJsonUrl(_ viewTag: NSNumber, jsonUrl: String?, autoPlay: Bool, contextJson: String?) {
|
|
224
|
+
NSLog("BBPlayerModule.loadWithJsonUrl called - viewTag: %@, jsonUrl: %@, autoPlay: %d, context: %@", viewTag, jsonUrl ?? "nil", autoPlay, contextJson ?? "nil")
|
|
200
225
|
DispatchQueue.main.async {
|
|
201
226
|
let view = self.getView(viewTag)
|
|
202
227
|
NSLog("BBPlayerModule.loadWithJsonUrl - view found: %@", view != nil ? "YES" : "NO")
|
|
203
228
|
if let view = view, let url = jsonUrl {
|
|
204
229
|
NSLog("BBPlayerModule.loadWithJsonUrl - calling view.loadWithJsonUrl with url: %@", url)
|
|
205
|
-
view.loadWithJsonUrl(url, autoPlay: autoPlay)
|
|
230
|
+
view.loadWithJsonUrl(url, autoPlay: autoPlay, contextJson: contextJson)
|
|
206
231
|
} else {
|
|
207
232
|
NSLog("BBPlayerModule.loadWithJsonUrl - FAILED: view=%@, url=%@", view != nil ? "found" : "nil", jsonUrl ?? "nil")
|
|
208
233
|
}
|
|
@@ -218,13 +243,6 @@ class BBPlayerModule: NSObject {
|
|
|
218
243
|
}
|
|
219
244
|
}
|
|
220
245
|
|
|
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
246
|
@objc func getMuted(_ viewTag: NSNumber, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
|
|
229
247
|
DispatchQueue.main.async {
|
|
230
248
|
let muted = self.getView(viewTag)?.muted()
|
|
@@ -280,4 +298,152 @@ class BBPlayerModule: NSObject {
|
|
|
280
298
|
resolver(playoutData)
|
|
281
299
|
}
|
|
282
300
|
}
|
|
301
|
+
|
|
302
|
+
// MARK: - Modal Player API
|
|
303
|
+
|
|
304
|
+
@objc func presentModalPlayer(_ jsonUrl: String, optionsJson: String?, contextJson: String?) {
|
|
305
|
+
DispatchQueue.main.async {
|
|
306
|
+
guard let rootVC = RCTPresentedViewController() else {
|
|
307
|
+
NSLog("BBPlayerModule: No root view controller found")
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Parse options from JSON string
|
|
312
|
+
var options: [String: Any]? = nil
|
|
313
|
+
if let json = optionsJson, let data = json.data(using: .utf8) {
|
|
314
|
+
options = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Parse context for playlist auto-advance
|
|
318
|
+
var context: [String: Any]? = nil
|
|
319
|
+
if let ctxJson = contextJson, let data = ctxJson.data(using: .utf8) {
|
|
320
|
+
context = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Create modal player view
|
|
324
|
+
let playerView = BBNativePlayerView(frame: rootVC.view.frame)
|
|
325
|
+
playerView.showBackArrow = options?["showBackArrow"] as? Bool ?? false
|
|
326
|
+
|
|
327
|
+
var loadOptions: [String: Any] = options ?? [:]
|
|
328
|
+
if loadOptions["autoPlay"] == nil {
|
|
329
|
+
loadOptions["autoPlay"] = true
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// When context has a cliplist (contextCollectionId), load clip by ID
|
|
333
|
+
// with cliplist context. ProgramController will swap to loading the
|
|
334
|
+
// cliplist and find the clip by ID (matching web standardplayer pattern).
|
|
335
|
+
// Deferred until apiReady to avoid racing with initial jsonUrl load.
|
|
336
|
+
let collectionId = context?["contextCollectionId"] as? String
|
|
337
|
+
let clipId = context?["contextEntityId"] as? String
|
|
338
|
+
|
|
339
|
+
// Set up player with options (playout config, JWT, etc.)
|
|
340
|
+
playerView.setupWithJsonUrl(jsonUrl: jsonUrl, options: loadOptions)
|
|
341
|
+
playerView.presentModal(uiViewContoller: rootVC, animated: true)
|
|
342
|
+
|
|
343
|
+
if let collectionId = collectionId, let clipId = clipId {
|
|
344
|
+
let clipContext: [String: Any] = [
|
|
345
|
+
"contextCollectionType": context?["contextCollectionType"] as? String ?? "MediaClipList",
|
|
346
|
+
"contextCollectionId": collectionId,
|
|
347
|
+
]
|
|
348
|
+
self.pendingModalLoad = {
|
|
349
|
+
playerView.player.loadWithClipId(clipId: clipId, initiator: "external", autoPlay: true, seekTo: nil, context: clipContext)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Set up delegate for event forwarding
|
|
354
|
+
let delegate = ModalPlayerDelegate(module: self)
|
|
355
|
+
playerView.delegate = delegate
|
|
356
|
+
|
|
357
|
+
self.modalPlayerView = playerView
|
|
358
|
+
self.modalDelegate = delegate
|
|
359
|
+
|
|
360
|
+
NSLog("BBPlayerModule: Modal player presented with URL: %@, context: %@", jsonUrl, contextJson ?? "nil")
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private func extractIdFromUrl(_ url: String, pattern: String) -> String? {
|
|
365
|
+
do {
|
|
366
|
+
let regex = try NSRegularExpression(pattern: pattern, options: [])
|
|
367
|
+
let range = NSRange(url.startIndex..., in: url)
|
|
368
|
+
if let match = regex.firstMatch(in: url, options: [], range: range) {
|
|
369
|
+
for i in 1..<match.numberOfRanges {
|
|
370
|
+
if let groupRange = Range(match.range(at: i), in: url) {
|
|
371
|
+
let extracted = String(url[groupRange])
|
|
372
|
+
if !extracted.isEmpty {
|
|
373
|
+
return extracted
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
NSLog("BBPlayerModule: Regex error: %@", error.localizedDescription)
|
|
380
|
+
}
|
|
381
|
+
return nil
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
@objc func dismissModalPlayer() {
|
|
385
|
+
DispatchQueue.main.async {
|
|
386
|
+
self.modalPlayerView?.player.closeModalPlayer()
|
|
387
|
+
self.modalPlayerView = nil
|
|
388
|
+
self.modalDelegate = nil
|
|
389
|
+
self.pendingModalLoad = nil
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@objc override func addListener(_ eventName: String) {
|
|
394
|
+
// Required for RCTEventEmitter
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@objc override func removeListeners(_ count: Double) {
|
|
398
|
+
// Required for RCTEventEmitter
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private func emitEvent(_ name: String, body: Any? = nil) {
|
|
402
|
+
if hasListeners {
|
|
403
|
+
sendEvent(withName: name, body: body)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// MARK: - Modal Player Delegate
|
|
408
|
+
|
|
409
|
+
private class ModalPlayerDelegate: NSObject, BBNativePlayerViewDelegate {
|
|
410
|
+
weak var module: BBPlayerModule?
|
|
411
|
+
|
|
412
|
+
init(module: BBPlayerModule) {
|
|
413
|
+
self.module = module
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
func bbNativePlayerView(didTriggerPlay playerView: BBNativePlayerView) {
|
|
417
|
+
module?.emitEvent("modalPlayerPlay")
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
func bbNativePlayerView(didTriggerPause playerView: BBNativePlayerView) {
|
|
421
|
+
module?.emitEvent("modalPlayerPause")
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
func bbNativePlayerView(didTriggerEnded playerView: BBNativePlayerView) {
|
|
425
|
+
module?.emitEvent("modalPlayerEnded")
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
func bbNativePlayerView(playerView: BBNativePlayerView, didFailWithError error: String?) {
|
|
429
|
+
module?.emitEvent("modalPlayerError", body: ["error": error ?? "Unknown error"])
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
func bbNativePlayerView(didTriggerApiReady playerView: BBNativePlayerView) {
|
|
433
|
+
module?.pendingModalLoad?()
|
|
434
|
+
module?.pendingModalLoad = nil
|
|
435
|
+
module?.emitEvent("modalPlayerApiReady")
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
func bbNativePlayerView(didTriggerCanPlay playerView: BBNativePlayerView) {
|
|
439
|
+
module?.emitEvent("modalPlayerCanPlay")
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
func bbNativePlayerView(didCloseModalPlayer playerView: BBNativePlayerView) {
|
|
443
|
+
module?.emitEvent("modalPlayerDismissed")
|
|
444
|
+
module?.modalPlayerView = nil
|
|
445
|
+
module?.modalDelegate = nil
|
|
446
|
+
module?.pendingModalLoad = nil
|
|
447
|
+
}
|
|
448
|
+
}
|
|
283
449
|
}
|
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 {
|
|
@@ -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
|
-
|
|
184
|
-
stopTimeUpdates()
|
|
185
|
-
}
|
|
161
|
+
// No cleanup needed when not in fullscreen
|
|
186
162
|
}
|
|
187
163
|
}
|
|
188
164
|
|
|
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
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
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
|
}
|
|
@@ -639,6 +520,20 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
639
520
|
playerView?.player.expand()
|
|
640
521
|
}
|
|
641
522
|
|
|
523
|
+
func presentModal() {
|
|
524
|
+
guard let parentVC = parentViewController else {
|
|
525
|
+
log("Cannot present modal - no parent view controller", level: .warning)
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
isInFullscreen = true
|
|
529
|
+
playerView?.presentModal(uiViewContoller: parentVC, animated: true)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
func closeModal() {
|
|
533
|
+
playerView?.player.closeModalPlayer()
|
|
534
|
+
isInFullscreen = false
|
|
535
|
+
}
|
|
536
|
+
|
|
642
537
|
func enterFullscreen() {
|
|
643
538
|
enterFullscreenWithLandscapeForce(forceLandscape: false)
|
|
644
539
|
}
|
|
@@ -691,9 +586,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
691
586
|
}
|
|
692
587
|
|
|
693
588
|
func seekRelative(_ offsetInSeconds: Double) {
|
|
694
|
-
|
|
695
|
-
let newPosition = max(0, min(currentDuration, currentTime + offsetInSeconds))
|
|
696
|
-
playerView?.player.seek(offsetInSeconds: newPosition as NSNumber)
|
|
589
|
+
playerView?.player.seekRelative(offsetInSeconds: offsetInSeconds as NSNumber)
|
|
697
590
|
}
|
|
698
591
|
|
|
699
592
|
func setMuted(_ muted: Bool) {
|
|
@@ -704,28 +597,48 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
704
597
|
playerView?.setApiProperty(property: .volume, value: Float(volume))
|
|
705
598
|
}
|
|
706
599
|
|
|
707
|
-
|
|
708
|
-
|
|
600
|
+
// Helper to parse context JSON into a dictionary for the native SDK
|
|
601
|
+
private func parseContext(_ contextJson: String?) -> [String: Any]? {
|
|
602
|
+
guard let jsonString = contextJson, !jsonString.isEmpty else { return nil }
|
|
603
|
+
guard let data = jsonString.data(using: .utf8) else { return nil }
|
|
604
|
+
do {
|
|
605
|
+
if let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
606
|
+
return dict
|
|
607
|
+
}
|
|
608
|
+
} catch {
|
|
609
|
+
log("Failed to parse context JSON: \(error)", level: .warning)
|
|
610
|
+
}
|
|
611
|
+
return nil
|
|
709
612
|
}
|
|
710
613
|
|
|
711
|
-
func
|
|
712
|
-
|
|
614
|
+
func loadWithClipId(_ clipId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
|
|
615
|
+
let context = parseContext(contextJson)
|
|
616
|
+
playerView?.player.loadWithClipId(clipId: clipId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
|
|
713
617
|
}
|
|
714
618
|
|
|
715
|
-
func
|
|
716
|
-
|
|
619
|
+
func loadWithClipListId(_ clipListId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
|
|
620
|
+
let context = parseContext(contextJson)
|
|
621
|
+
playerView?.player.loadWithClipListId(clipListId: clipListId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
|
|
717
622
|
}
|
|
718
623
|
|
|
719
|
-
func
|
|
720
|
-
|
|
624
|
+
func loadWithProjectId(_ projectId: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
|
|
625
|
+
let context = parseContext(contextJson)
|
|
626
|
+
playerView?.player.loadWithProjectId(projectId: projectId, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
|
|
721
627
|
}
|
|
722
628
|
|
|
723
|
-
func
|
|
724
|
-
|
|
629
|
+
func loadWithClipJson(_ clipJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
|
|
630
|
+
let context = parseContext(contextJson)
|
|
631
|
+
playerView?.player.loadWithClipJson(clipJson: clipJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
|
|
725
632
|
}
|
|
726
633
|
|
|
727
|
-
func
|
|
728
|
-
|
|
634
|
+
func loadWithClipListJson(_ clipListJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
|
|
635
|
+
let context = parseContext(contextJson)
|
|
636
|
+
playerView?.player.loadWithClipListJson(clipListJson: clipListJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
func loadWithProjectJson(_ projectJson: String, initiator: String?, autoPlay: Bool?, seekTo: Double?, contextJson: String? = nil) {
|
|
640
|
+
let context = parseContext(contextJson)
|
|
641
|
+
playerView?.player.loadWithProjectJson(projectJson: projectJson, initiator: initiator, autoPlay: autoPlay, seekTo: seekTo as NSNumber?, context: context)
|
|
729
642
|
}
|
|
730
643
|
|
|
731
644
|
/**
|
|
@@ -735,8 +648,8 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
735
648
|
*
|
|
736
649
|
* Note: Shorts URLs (/sh/{id}.json) are NOT supported here - use BBShortsView instead.
|
|
737
650
|
*/
|
|
738
|
-
func loadWithJsonUrl(_ url: String, autoPlay: Bool) {
|
|
739
|
-
NSLog("BBPlayerView.loadWithJsonUrl called - url: %@, autoPlay: %d", url, autoPlay)
|
|
651
|
+
func loadWithJsonUrl(_ url: String, autoPlay: Bool, contextJson: String? = nil) {
|
|
652
|
+
NSLog("BBPlayerView.loadWithJsonUrl called - url: %@, autoPlay: %d, context: %@", url, autoPlay, contextJson ?? "nil")
|
|
740
653
|
guard playerView != nil else {
|
|
741
654
|
NSLog("BBPlayerView.loadWithJsonUrl ERROR - playerView not initialized")
|
|
742
655
|
return
|
|
@@ -754,6 +667,8 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
754
667
|
let projectIdPattern = "/pj/([0-9]+)\\.json|/project/([0-9]+)"
|
|
755
668
|
let shortsIdPattern = "/sh/([0-9]+)\\.json"
|
|
756
669
|
|
|
670
|
+
let context = parseContext(contextJson)
|
|
671
|
+
|
|
757
672
|
if let shortsMatch = url.range(of: shortsIdPattern, options: .regularExpression) {
|
|
758
673
|
// Shorts require a separate BBShortsView component
|
|
759
674
|
log("ERROR - Shorts URLs are not supported in BBPlayerView. Use BBShortsView instead.", level: .error)
|
|
@@ -765,7 +680,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
765
680
|
// Extract the cliplist ID
|
|
766
681
|
if let clipListId = extractIdFromUrl(url, pattern: clipListIdPattern) {
|
|
767
682
|
NSLog("BBPlayerView.loadWithJsonUrl - Loading ClipList by ID: %@", clipListId)
|
|
768
|
-
playerView?.player.loadWithClipListId(clipListId: clipListId, initiator: "external", autoPlay: autoPlay, seekTo: nil)
|
|
683
|
+
playerView?.player.loadWithClipListId(clipListId: clipListId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
|
|
769
684
|
} else {
|
|
770
685
|
NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract cliplist ID from URL: %@", url)
|
|
771
686
|
}
|
|
@@ -776,7 +691,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
776
691
|
// Extract the project ID
|
|
777
692
|
if let projectId = extractIdFromUrl(url, pattern: projectIdPattern) {
|
|
778
693
|
NSLog("BBPlayerView.loadWithJsonUrl - Loading Project by ID: %@", projectId)
|
|
779
|
-
playerView?.player.loadWithProjectId(projectId: projectId, initiator: "external", autoPlay: autoPlay, seekTo: nil)
|
|
694
|
+
playerView?.player.loadWithProjectId(projectId: projectId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
|
|
780
695
|
} else {
|
|
781
696
|
NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract project ID from URL: %@", url)
|
|
782
697
|
}
|
|
@@ -787,7 +702,7 @@ class BBPlayerView: UIView, BBNativePlayerViewDelegate {
|
|
|
787
702
|
// Extract the clip ID
|
|
788
703
|
if let clipId = extractIdFromUrl(url, pattern: clipIdPattern) {
|
|
789
704
|
NSLog("BBPlayerView.loadWithJsonUrl - Loading Clip by ID: %@", clipId)
|
|
790
|
-
playerView?.player.loadWithClipId(clipId: clipId, initiator: "external", autoPlay: autoPlay, seekTo: nil)
|
|
705
|
+
playerView?.player.loadWithClipId(clipId: clipId, initiator: "external", autoPlay: autoPlay, seekTo: nil, context: context)
|
|
791
706
|
} else {
|
|
792
707
|
NSLog("BBPlayerView.loadWithJsonUrl ERROR - Failed to extract clip ID from URL: %@", url)
|
|
793
708
|
}
|
|
@@ -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)
|
|
@@ -58,6 +56,8 @@ RCT_EXTERN_METHOD(seek:(nonnull NSNumber *)reactTag offsetInSeconds:(nonnull NSN
|
|
|
58
56
|
RCT_EXTERN_METHOD(seekRelative:(nonnull NSNumber *)reactTag offsetInSeconds:(nonnull NSNumber *)offsetInSeconds)
|
|
59
57
|
RCT_EXTERN_METHOD(setMuted:(nonnull NSNumber *)reactTag muted:(BOOL)muted)
|
|
60
58
|
RCT_EXTERN_METHOD(setVolume:(nonnull NSNumber *)reactTag volume:(nonnull NSNumber *)volume)
|
|
59
|
+
RCT_EXTERN_METHOD(presentModal:(nonnull NSNumber *)reactTag)
|
|
60
|
+
RCT_EXTERN_METHOD(closeModal:(nonnull NSNumber *)reactTag)
|
|
61
61
|
RCT_EXTERN_METHOD(enterFullscreen:(nonnull NSNumber *)reactTag)
|
|
62
62
|
RCT_EXTERN_METHOD(enterFullscreenLandscape:(nonnull NSNumber *)reactTag)
|
|
63
63
|
RCT_EXTERN_METHOD(exitFullscreen:(nonnull NSNumber *)reactTag)
|
|
@@ -62,6 +62,18 @@ class BBPlayerViewManager: RCTViewManager {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
@objc func presentModal(_ reactTag: NSNumber) {
|
|
66
|
+
DispatchQueue.main.async {
|
|
67
|
+
self.getView(reactTag)?.presentModal()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@objc func closeModal(_ reactTag: NSNumber) {
|
|
72
|
+
DispatchQueue.main.async {
|
|
73
|
+
self.getView(reactTag)?.closeModal()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
@objc func enterFullscreen(_ reactTag: NSNumber) {
|
|
66
78
|
DispatchQueue.main.async {
|
|
67
79
|
self.getView(reactTag)?.enterFullscreen()
|