react-native-video-provider 0.1.1

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 (141) hide show
  1. package/AuVideo.podspec +28 -0
  2. package/LICENSE +20 -0
  3. package/README.md +183 -0
  4. package/android/build.gradle +59 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/auvideo/AuVideoModule.kt +259 -0
  7. package/android/src/main/java/com/auvideo/AuVideoPackage.kt +35 -0
  8. package/android/src/main/java/com/auvideo/AuVideoSurfaceView.kt +40 -0
  9. package/android/src/main/java/com/auvideo/AuVideoSurfaceViewManager.kt +38 -0
  10. package/android/src/main/java/com/auvideo/PlayerCore.kt +366 -0
  11. package/android/src/main/java/com/auvideo/SurfaceRegistry.kt +26 -0
  12. package/android/src/main/res/layout/au_video_player_view.xml +10 -0
  13. package/ios/AuVideo.h +10 -0
  14. package/ios/AuVideo.mm +232 -0
  15. package/ios/AuVideoOrientation.swift +59 -0
  16. package/ios/AuVideoPlayerCore.swift +415 -0
  17. package/ios/AuVideoSurfaceRegistry.swift +30 -0
  18. package/ios/AuVideoSurfaceView.h +15 -0
  19. package/ios/AuVideoSurfaceView.mm +63 -0
  20. package/lib/module/AuVideoSurfaceNativeComponent.ts +8 -0
  21. package/lib/module/NativeAuVideo.js +5 -0
  22. package/lib/module/NativeAuVideo.js.map +1 -0
  23. package/lib/module/components/FloatingPlayer.js +119 -0
  24. package/lib/module/components/FloatingPlayer.js.map +1 -0
  25. package/lib/module/components/FullscreenPlayer.js +59 -0
  26. package/lib/module/components/FullscreenPlayer.js.map +1 -0
  27. package/lib/module/components/GestureOverlay.js +56 -0
  28. package/lib/module/components/GestureOverlay.js.map +1 -0
  29. package/lib/module/components/MiniPlayer.js +89 -0
  30. package/lib/module/components/MiniPlayer.js.map +1 -0
  31. package/lib/module/components/VideoControls.js +191 -0
  32. package/lib/module/components/VideoControls.js.map +1 -0
  33. package/lib/module/components/VideoPlayer.js +65 -0
  34. package/lib/module/components/VideoPlayer.js.map +1 -0
  35. package/lib/module/components/VideoSurface.js +35 -0
  36. package/lib/module/components/VideoSurface.js.map +1 -0
  37. package/lib/module/core/VideoManager.js +436 -0
  38. package/lib/module/core/VideoManager.js.map +1 -0
  39. package/lib/module/hooks/useFullscreen.js +19 -0
  40. package/lib/module/hooks/useFullscreen.js.map +1 -0
  41. package/lib/module/hooks/usePiP.js +17 -0
  42. package/lib/module/hooks/usePiP.js.map +1 -0
  43. package/lib/module/hooks/usePlayback.js +21 -0
  44. package/lib/module/hooks/usePlayback.js.map +1 -0
  45. package/lib/module/hooks/useVideo.js +17 -0
  46. package/lib/module/hooks/useVideo.js.map +1 -0
  47. package/lib/module/hooks/useVideoEvents.js +26 -0
  48. package/lib/module/hooks/useVideoEvents.js.map +1 -0
  49. package/lib/module/index.js +19 -0
  50. package/lib/module/index.js.map +1 -0
  51. package/lib/module/package.json +1 -0
  52. package/lib/module/provider/VideoContext.js +14 -0
  53. package/lib/module/provider/VideoContext.js.map +1 -0
  54. package/lib/module/provider/VideoProvider.js +36 -0
  55. package/lib/module/provider/VideoProvider.js.map +1 -0
  56. package/lib/module/state/createVideoStore.js +33 -0
  57. package/lib/module/state/createVideoStore.js.map +1 -0
  58. package/lib/module/types/events.js +4 -0
  59. package/lib/module/types/events.js.map +1 -0
  60. package/lib/module/types/index.js +2 -0
  61. package/lib/module/types/index.js.map +1 -0
  62. package/lib/module/types/video.js +2 -0
  63. package/lib/module/types/video.js.map +1 -0
  64. package/lib/module/utils/Emitter.js +39 -0
  65. package/lib/module/utils/Emitter.js.map +1 -0
  66. package/lib/module/utils/formatTime.js +15 -0
  67. package/lib/module/utils/formatTime.js.map +1 -0
  68. package/lib/typescript/package.json +1 -0
  69. package/lib/typescript/src/AuVideoSurfaceNativeComponent.d.ts +8 -0
  70. package/lib/typescript/src/AuVideoSurfaceNativeComponent.d.ts.map +1 -0
  71. package/lib/typescript/src/NativeAuVideo.d.ts +85 -0
  72. package/lib/typescript/src/NativeAuVideo.d.ts.map +1 -0
  73. package/lib/typescript/src/components/FloatingPlayer.d.ts +12 -0
  74. package/lib/typescript/src/components/FloatingPlayer.d.ts.map +1 -0
  75. package/lib/typescript/src/components/FullscreenPlayer.d.ts +12 -0
  76. package/lib/typescript/src/components/FullscreenPlayer.d.ts.map +1 -0
  77. package/lib/typescript/src/components/GestureOverlay.d.ts +14 -0
  78. package/lib/typescript/src/components/GestureOverlay.d.ts.map +1 -0
  79. package/lib/typescript/src/components/MiniPlayer.d.ts +16 -0
  80. package/lib/typescript/src/components/MiniPlayer.d.ts.map +1 -0
  81. package/lib/typescript/src/components/VideoControls.d.ts +17 -0
  82. package/lib/typescript/src/components/VideoControls.d.ts.map +1 -0
  83. package/lib/typescript/src/components/VideoPlayer.d.ts +27 -0
  84. package/lib/typescript/src/components/VideoPlayer.d.ts.map +1 -0
  85. package/lib/typescript/src/components/VideoSurface.d.ts +18 -0
  86. package/lib/typescript/src/components/VideoSurface.d.ts.map +1 -0
  87. package/lib/typescript/src/core/VideoManager.d.ts +88 -0
  88. package/lib/typescript/src/core/VideoManager.d.ts.map +1 -0
  89. package/lib/typescript/src/hooks/useFullscreen.d.ts +7 -0
  90. package/lib/typescript/src/hooks/useFullscreen.d.ts.map +1 -0
  91. package/lib/typescript/src/hooks/usePiP.d.ts +6 -0
  92. package/lib/typescript/src/hooks/usePiP.d.ts.map +1 -0
  93. package/lib/typescript/src/hooks/usePlayback.d.ts +13 -0
  94. package/lib/typescript/src/hooks/usePlayback.d.ts.map +1 -0
  95. package/lib/typescript/src/hooks/useVideo.d.ts +13 -0
  96. package/lib/typescript/src/hooks/useVideo.d.ts.map +1 -0
  97. package/lib/typescript/src/hooks/useVideoEvents.d.ts +14 -0
  98. package/lib/typescript/src/hooks/useVideoEvents.d.ts.map +1 -0
  99. package/lib/typescript/src/index.d.ts +18 -0
  100. package/lib/typescript/src/index.d.ts.map +1 -0
  101. package/lib/typescript/src/provider/VideoContext.d.ts +8 -0
  102. package/lib/typescript/src/provider/VideoContext.d.ts.map +1 -0
  103. package/lib/typescript/src/provider/VideoProvider.d.ts +18 -0
  104. package/lib/typescript/src/provider/VideoProvider.d.ts.map +1 -0
  105. package/lib/typescript/src/state/createVideoStore.d.ts +6 -0
  106. package/lib/typescript/src/state/createVideoStore.d.ts.map +1 -0
  107. package/lib/typescript/src/types/events.d.ts +46 -0
  108. package/lib/typescript/src/types/events.d.ts.map +1 -0
  109. package/lib/typescript/src/types/index.d.ts +3 -0
  110. package/lib/typescript/src/types/index.d.ts.map +1 -0
  111. package/lib/typescript/src/types/video.d.ts +70 -0
  112. package/lib/typescript/src/types/video.d.ts.map +1 -0
  113. package/lib/typescript/src/utils/Emitter.d.ts +15 -0
  114. package/lib/typescript/src/utils/Emitter.d.ts.map +1 -0
  115. package/lib/typescript/src/utils/formatTime.d.ts +2 -0
  116. package/lib/typescript/src/utils/formatTime.d.ts.map +1 -0
  117. package/package.json +154 -0
  118. package/src/AuVideoSurfaceNativeComponent.ts +8 -0
  119. package/src/NativeAuVideo.ts +106 -0
  120. package/src/components/FloatingPlayer.tsx +130 -0
  121. package/src/components/FullscreenPlayer.tsx +64 -0
  122. package/src/components/GestureOverlay.tsx +76 -0
  123. package/src/components/MiniPlayer.tsx +93 -0
  124. package/src/components/VideoControls.tsx +213 -0
  125. package/src/components/VideoPlayer.tsx +77 -0
  126. package/src/components/VideoSurface.tsx +39 -0
  127. package/src/core/VideoManager.ts +441 -0
  128. package/src/hooks/useFullscreen.ts +14 -0
  129. package/src/hooks/usePiP.ts +13 -0
  130. package/src/hooks/usePlayback.ts +26 -0
  131. package/src/hooks/useVideo.ts +16 -0
  132. package/src/hooks/useVideoEvents.ts +33 -0
  133. package/src/index.ts +54 -0
  134. package/src/provider/VideoContext.ts +12 -0
  135. package/src/provider/VideoProvider.tsx +41 -0
  136. package/src/state/createVideoStore.ts +33 -0
  137. package/src/types/events.ts +22 -0
  138. package/src/types/index.ts +11 -0
  139. package/src/types/video.ts +85 -0
  140. package/src/utils/Emitter.ts +51 -0
  141. package/src/utils/formatTime.ts +12 -0
@@ -0,0 +1,415 @@
1
+ import AVFoundation
2
+ import AVKit
3
+ import Foundation
4
+ import UIKit
5
+
6
+ @objc(AuVideoSourceSpec)
7
+ public final class AuVideoSourceSpec: NSObject {
8
+ @objc public let videoId: String
9
+ @objc public let uri: String
10
+ @objc public let headers: [String: String]
11
+ @objc public let title: String?
12
+ @objc public let artist: String?
13
+ @objc public let artworkUri: String?
14
+ @objc public let startPosition: Double
15
+
16
+ @objc public init(
17
+ videoId: String,
18
+ uri: String,
19
+ headers: [String: String],
20
+ title: String?,
21
+ artist: String?,
22
+ artworkUri: String?,
23
+ startPosition: Double
24
+ ) {
25
+ self.videoId = videoId
26
+ self.uri = uri
27
+ self.headers = headers
28
+ self.title = title
29
+ self.artist = artist
30
+ self.artworkUri = artworkUri
31
+ self.startPosition = startPosition
32
+ }
33
+ }
34
+
35
+ @objc(AuVideoCoreDelegate)
36
+ public protocol AuVideoCoreDelegate: AnyObject {
37
+ func onStatusChange(_ status: String)
38
+ func onLoad(_ videoId: String, duration: Double, width: Double, height: Double)
39
+ func onProgress(_ position: Double, duration: Double, buffered: Double)
40
+ func onSeek(_ position: Double)
41
+ func onEnd()
42
+ func onError(_ code: String, message: String)
43
+ func onAttach(_ surfaceId: String)
44
+ func onDetach(_ surfaceId: String)
45
+ func onPipChange(_ active: Bool)
46
+ }
47
+
48
+ /// UIView whose backing layer is the AVPlayerLayer. Moving this view between
49
+ /// surface containers re-parents rendering without touching playback.
50
+ @objc(AuVideoHostView)
51
+ public final class AuVideoHostView: UIView {
52
+ public override static var layerClass: AnyClass { AVPlayerLayer.self }
53
+ public var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
54
+ }
55
+
56
+ /// The ONE playback engine of the app: a single AVPlayer plus a single host
57
+ /// view, re-parented between registered surfaces. Created once, never owned
58
+ /// by React components; destroyed only by an explicit releasePlayer().
59
+ @objc(AuVideoPlayerCore)
60
+ public final class AuVideoPlayerCore: NSObject {
61
+
62
+ @objc public static let shared = AuVideoPlayerCore()
63
+
64
+ @objc public weak var delegate: AuVideoCoreDelegate?
65
+
66
+ private let player = AVPlayer()
67
+ private let hostView = AuVideoHostView()
68
+
69
+ private var currentVideoId: String?
70
+ private var currentSurfaceId: String?
71
+ /// Surface we want but that hasn't registered (yet, or again).
72
+ private var pendingSurfaceId: String?
73
+
74
+ private var preloaded: [String: AVPlayerItem] = [:]
75
+ private var loadReported = false
76
+ private var repeatEnabled = false
77
+ private var playbackRate: Double = 1
78
+ private var initialized = false
79
+
80
+ private var timeObserver: Any?
81
+ private var timeControlObservation: NSKeyValueObservation?
82
+ private var itemStatusObservation: NSKeyValueObservation?
83
+ private var pipController: AVPictureInPictureController?
84
+
85
+ private override init() {
86
+ super.init()
87
+ }
88
+
89
+ // ------------------------------------------------------------ lifecycle
90
+
91
+ /// Idempotent; main thread.
92
+ @objc public func initialize() {
93
+ if initialized { return }
94
+ initialized = true
95
+
96
+ hostView.playerLayer.player = player
97
+ hostView.playerLayer.videoGravity = .resizeAspect
98
+ player.actionAtItemEnd = .pause
99
+
100
+ try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
101
+ try? AVAudioSession.sharedInstance().setActive(true)
102
+
103
+ let interval = CMTime(seconds: 0.5, preferredTimescale: 600)
104
+ timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) {
105
+ [weak self] _ in
106
+ self?.emitProgress()
107
+ }
108
+
109
+ timeControlObservation = player.observe(\.timeControlStatus, options: [.new]) {
110
+ [weak self] player, _ in
111
+ guard let self else { return }
112
+ switch player.timeControlStatus {
113
+ case .playing:
114
+ self.delegate?.onStatusChange("playing")
115
+ case .waitingToPlayAtSpecifiedRate:
116
+ self.delegate?.onStatusChange("buffering")
117
+ case .paused:
118
+ if player.currentItem != nil {
119
+ self.delegate?.onStatusChange("paused")
120
+ }
121
+ @unknown default:
122
+ break
123
+ }
124
+ }
125
+
126
+ NotificationCenter.default.addObserver(
127
+ self,
128
+ selector: #selector(itemDidPlayToEnd(_:)),
129
+ name: .AVPlayerItemDidPlayToEndTime,
130
+ object: nil
131
+ )
132
+
133
+ if AVPictureInPictureController.isPictureInPictureSupported() {
134
+ pipController = AVPictureInPictureController(playerLayer: hostView.playerLayer)
135
+ pipController?.delegate = self
136
+ }
137
+ }
138
+
139
+ @objc public func releasePlayer() {
140
+ guard initialized else { return }
141
+ initialized = false
142
+ if let observer = timeObserver {
143
+ player.removeTimeObserver(observer)
144
+ timeObserver = nil
145
+ }
146
+ timeControlObservation?.invalidate()
147
+ itemStatusObservation?.invalidate()
148
+ NotificationCenter.default.removeObserver(self)
149
+ detach()
150
+ player.replaceCurrentItem(with: nil)
151
+ pipController = nil
152
+ currentVideoId = nil
153
+ pendingSurfaceId = nil
154
+ preloaded.removeAll()
155
+ delegate?.onStatusChange("idle")
156
+ }
157
+
158
+ // --------------------------------------------------------------- source
159
+
160
+ @objc public func setSource(_ source: AuVideoSourceSpec, autoplay: Bool) {
161
+ initialize()
162
+
163
+ if source.videoId == currentVideoId {
164
+ // Same-video handoff: never reload; at most honor autoplay.
165
+ if autoplay, player.timeControlStatus != .playing {
166
+ play()
167
+ }
168
+ return
169
+ }
170
+
171
+ currentVideoId = source.videoId
172
+ loadReported = false
173
+ delegate?.onStatusChange("loading")
174
+
175
+ let item = preloaded.removeValue(forKey: source.videoId) ?? makeItem(source)
176
+ observeItem(item, videoId: source.videoId)
177
+ player.replaceCurrentItem(with: item)
178
+
179
+ if source.startPosition > 0 {
180
+ let target = CMTime(seconds: source.startPosition, preferredTimescale: 600)
181
+ player.seek(to: target, toleranceBefore: .zero, toleranceAfter: .zero)
182
+ }
183
+ if autoplay {
184
+ player.play()
185
+ }
186
+ }
187
+
188
+ @objc public func preload(_ source: AuVideoSourceSpec) {
189
+ // AVPlayerItem starts loading its asset as soon as it exists, so a
190
+ // later attach starts near-instantly.
191
+ guard preloaded[source.videoId] == nil, source.videoId != currentVideoId else { return }
192
+ preloaded[source.videoId] = makeItem(source)
193
+ }
194
+
195
+ private func makeItem(_ source: AuVideoSourceSpec) -> AVPlayerItem {
196
+ let url = URL(string: source.uri) ?? URL(fileURLWithPath: source.uri)
197
+ var options: [String: Any] = [:]
198
+ if !source.headers.isEmpty {
199
+ options["AVURLAssetHTTPHeaderFieldsKey"] = source.headers
200
+ }
201
+ let asset = AVURLAsset(url: url, options: options)
202
+ return AVPlayerItem(asset: asset)
203
+ }
204
+
205
+ private func observeItem(_ item: AVPlayerItem, videoId: String) {
206
+ itemStatusObservation?.invalidate()
207
+ itemStatusObservation = item.observe(\.status, options: [.new]) {
208
+ [weak self] item, _ in
209
+ guard let self else { return }
210
+ switch item.status {
211
+ case .readyToPlay:
212
+ if !self.loadReported {
213
+ self.loadReported = true
214
+ let size = item.presentationSize
215
+ let duration = item.duration.isNumeric ? item.duration.seconds : 0
216
+ self.delegate?.onStatusChange("ready")
217
+ self.delegate?.onLoad(
218
+ videoId,
219
+ duration: duration,
220
+ width: Double(size.width),
221
+ height: Double(size.height)
222
+ )
223
+ }
224
+ case .failed:
225
+ let error = item.error as NSError?
226
+ self.delegate?.onError(
227
+ String(error?.code ?? -1),
228
+ message: error?.localizedDescription ?? "Playback failed"
229
+ )
230
+ default:
231
+ break
232
+ }
233
+ }
234
+ }
235
+
236
+ // ------------------------------------------------------------- commands
237
+
238
+ @objc public func play() {
239
+ if player.currentItem == nil { return }
240
+ player.play()
241
+ if playbackRate != 1 {
242
+ player.rate = Float(playbackRate)
243
+ }
244
+ }
245
+
246
+ @objc public func pause() {
247
+ player.pause()
248
+ }
249
+
250
+ @objc public func stop() {
251
+ player.pause()
252
+ player.seek(to: .zero)
253
+ delegate?.onStatusChange("idle")
254
+ }
255
+
256
+ @objc public func seek(to position: Double) {
257
+ let target = CMTime(seconds: max(position, 0), preferredTimescale: 600)
258
+ player.seek(to: target, toleranceBefore: .zero, toleranceAfter: .zero) {
259
+ [weak self] _ in
260
+ self?.delegate?.onSeek(position)
261
+ }
262
+ }
263
+
264
+ @objc public func setRate(_ rate: Double) {
265
+ playbackRate = rate
266
+ // Setting rate on a paused player starts playback; mirror ExoPlayer by
267
+ // only applying immediately when already playing (play() re-applies it).
268
+ if player.timeControlStatus == .playing {
269
+ player.rate = Float(rate)
270
+ }
271
+ if #available(iOS 16.0, *) {
272
+ player.defaultRate = Float(rate)
273
+ }
274
+ }
275
+
276
+ @objc public func setVolume(_ volume: Double) {
277
+ player.volume = Float(min(max(volume, 0), 1))
278
+ }
279
+
280
+ @objc public func setMuted(_ muted: Bool) {
281
+ player.isMuted = muted
282
+ }
283
+
284
+ @objc public func setRepeat(_ enabled: Bool) {
285
+ repeatEnabled = enabled
286
+ }
287
+
288
+ @objc public func setResizeMode(_ mode: String) {
289
+ switch mode {
290
+ case "cover":
291
+ hostView.playerLayer.videoGravity = .resizeAspectFill
292
+ case "stretch":
293
+ hostView.playerLayer.videoGravity = .resize
294
+ default:
295
+ hostView.playerLayer.videoGravity = .resizeAspect
296
+ }
297
+ }
298
+
299
+ @objc public func positionSeconds() -> Double {
300
+ let time = player.currentTime()
301
+ return time.isNumeric ? time.seconds : 0
302
+ }
303
+
304
+ // ------------------------------------------------------------- surfaces
305
+
306
+ @objc public func attach(_ surfaceId: String) {
307
+ initialize()
308
+ guard let container = AuVideoSurfaceRegistry.view(for: surfaceId) else {
309
+ // Screen still mounting — attach the moment it registers.
310
+ pendingSurfaceId = surfaceId
311
+ return
312
+ }
313
+ attachTo(container, surfaceId: surfaceId)
314
+ }
315
+
316
+ @objc public func detach() {
317
+ hostView.removeFromSuperview()
318
+ if let surfaceId = currentSurfaceId {
319
+ delegate?.onDetach(surfaceId)
320
+ }
321
+ currentSurfaceId = nil
322
+ pendingSurfaceId = nil
323
+ }
324
+
325
+ @objc public func onSurfaceAvailable(_ surfaceId: String, view: UIView) {
326
+ // Also re-attach when the active surface's view was recreated (e.g.
327
+ // navigating back to a screen Fabric re-materialized).
328
+ if surfaceId == pendingSurfaceId || surfaceId == currentSurfaceId {
329
+ attachTo(view, surfaceId: surfaceId)
330
+ }
331
+ }
332
+
333
+ @objc public func onSurfaceUnavailable(_ surfaceId: String, view: UIView) {
334
+ if currentSurfaceId == surfaceId, hostView.superview === view {
335
+ hostView.removeFromSuperview()
336
+ currentSurfaceId = nil
337
+ // Keep playing hidden (audio); remounting the same surface re-attaches.
338
+ pendingSurfaceId = surfaceId
339
+ delegate?.onDetach(surfaceId)
340
+ }
341
+ }
342
+
343
+ private func attachTo(_ container: UIView, surfaceId: String) {
344
+ if currentSurfaceId == surfaceId, hostView.superview === container {
345
+ pendingSurfaceId = nil
346
+ return
347
+ }
348
+ if let previous = currentSurfaceId, previous != surfaceId {
349
+ delegate?.onDetach(previous)
350
+ }
351
+ hostView.removeFromSuperview()
352
+ hostView.frame = container.bounds
353
+ hostView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
354
+ container.addSubview(hostView)
355
+ currentSurfaceId = surfaceId
356
+ pendingSurfaceId = nil
357
+ delegate?.onAttach(surfaceId)
358
+ }
359
+
360
+ // ------------------------------------------------------------------ pip
361
+
362
+ @objc public func enterPip() -> Bool {
363
+ guard let pip = pipController, pip.isPictureInPicturePossible else {
364
+ return false
365
+ }
366
+ pip.startPictureInPicture()
367
+ return true
368
+ }
369
+
370
+ @objc public func exitPip() {
371
+ pipController?.stopPictureInPicture()
372
+ }
373
+
374
+ // --------------------------------------------------------------- events
375
+
376
+ @objc private func itemDidPlayToEnd(_ notification: Notification) {
377
+ guard let item = notification.object as? AVPlayerItem, item === player.currentItem else {
378
+ return
379
+ }
380
+ if repeatEnabled {
381
+ player.seek(to: .zero)
382
+ player.play()
383
+ return
384
+ }
385
+ emitProgress()
386
+ delegate?.onStatusChange("ended")
387
+ delegate?.onEnd()
388
+ }
389
+
390
+ private func emitProgress() {
391
+ guard let item = player.currentItem else { return }
392
+ let position = positionSeconds()
393
+ let duration = item.duration.isNumeric ? item.duration.seconds : 0
394
+ var buffered: Double = 0
395
+ if let range = item.loadedTimeRanges.first?.timeRangeValue {
396
+ let end = range.start.seconds + range.duration.seconds
397
+ buffered = max(end - position, 0)
398
+ }
399
+ delegate?.onProgress(position, duration: duration, buffered: buffered)
400
+ }
401
+ }
402
+
403
+ extension AuVideoPlayerCore: AVPictureInPictureControllerDelegate {
404
+ public func pictureInPictureControllerDidStartPictureInPicture(
405
+ _ pictureInPictureController: AVPictureInPictureController
406
+ ) {
407
+ delegate?.onPipChange(true)
408
+ }
409
+
410
+ public func pictureInPictureControllerDidStopPictureInPicture(
411
+ _ pictureInPictureController: AVPictureInPictureController
412
+ ) {
413
+ delegate?.onPipChange(false)
414
+ }
415
+ }
@@ -0,0 +1,30 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ /// Maps surface ids to their mounted container views. Weak references only —
5
+ /// an unmounted screen can never be leaked by the registry.
6
+ @objc(AuVideoSurfaceRegistry)
7
+ public final class AuVideoSurfaceRegistry: NSObject {
8
+
9
+ private static let views = NSMapTable<NSString, UIView>.strongToWeakObjects()
10
+
11
+ @objc(registerSurface:view:)
12
+ public static func register(_ surfaceId: String, view: UIView) {
13
+ views.setObject(view, forKey: surfaceId as NSString)
14
+ AuVideoPlayerCore.shared.onSurfaceAvailable(surfaceId, view: view)
15
+ }
16
+
17
+ @objc(unregisterSurface:view:)
18
+ public static func unregister(_ surfaceId: String, view: UIView) {
19
+ let registered = views.object(forKey: surfaceId as NSString)
20
+ if registered == nil || registered === view {
21
+ views.removeObject(forKey: surfaceId as NSString)
22
+ AuVideoPlayerCore.shared.onSurfaceUnavailable(surfaceId, view: view)
23
+ }
24
+ }
25
+
26
+ @objc(viewForSurface:)
27
+ public static func view(for surfaceId: String) -> UIView? {
28
+ return views.object(forKey: surfaceId as NSString)
29
+ }
30
+ }
@@ -0,0 +1,15 @@
1
+ #import <React/RCTViewComponentView.h>
2
+ #import <UIKit/UIKit.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ /**
7
+ * Fabric component view for <VideoSurface>. A dumb mount point: registers
8
+ * itself in the surface registry under its surfaceId prop; the singleton
9
+ * player's host view gets re-parented into it by the core.
10
+ */
11
+ @interface AuVideoSurfaceView : RCTViewComponentView
12
+
13
+ @end
14
+
15
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,63 @@
1
+ #import "AuVideoSurfaceView.h"
2
+
3
+ #import <react/renderer/components/AuVideoSpec/ComponentDescriptors.h>
4
+ #import <react/renderer/components/AuVideoSpec/Props.h>
5
+ #import <react/renderer/components/AuVideoSpec/RCTComponentViewHelpers.h>
6
+
7
+ #import <React/RCTConversions.h>
8
+
9
+ #import <AVKit/AVKit.h>
10
+
11
+ #if __has_include(<AuVideo/AuVideo-Swift.h>)
12
+ #import <AuVideo/AuVideo-Swift.h>
13
+ #else
14
+ #import "AuVideo-Swift.h"
15
+ #endif
16
+
17
+ using namespace facebook::react;
18
+
19
+ @interface AuVideoSurfaceView () <RCTAuVideoSurfaceViewViewProtocol>
20
+ @end
21
+
22
+ @implementation AuVideoSurfaceView {
23
+ NSString *_surfaceId;
24
+ }
25
+
26
+ + (ComponentDescriptorProvider)componentDescriptorProvider
27
+ {
28
+ return concreteComponentDescriptorProvider<AuVideoSurfaceViewComponentDescriptor>();
29
+ }
30
+
31
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
32
+ {
33
+ const auto &newViewProps = *std::static_pointer_cast<AuVideoSurfaceViewProps const>(props);
34
+ NSString *newSurfaceId = RCTNSStringFromStringNilIfEmpty(newViewProps.surfaceId);
35
+
36
+ if (![newSurfaceId isEqualToString:_surfaceId]) {
37
+ if (_surfaceId != nil) {
38
+ [AuVideoSurfaceRegistry unregisterSurface:_surfaceId view:self];
39
+ }
40
+ _surfaceId = newSurfaceId;
41
+ if (_surfaceId != nil) {
42
+ [AuVideoSurfaceRegistry registerSurface:_surfaceId view:self];
43
+ }
44
+ }
45
+
46
+ [super updateProps:props oldProps:oldProps];
47
+ }
48
+
49
+ - (void)prepareForRecycle
50
+ {
51
+ [super prepareForRecycle];
52
+ if (_surfaceId != nil) {
53
+ [AuVideoSurfaceRegistry unregisterSurface:_surfaceId view:self];
54
+ _surfaceId = nil;
55
+ }
56
+ }
57
+
58
+ @end
59
+
60
+ Class<RCTComponentViewProtocol> AuVideoSurfaceViewCls(void)
61
+ {
62
+ return AuVideoSurfaceView.class;
63
+ }
@@ -0,0 +1,8 @@
1
+ import { codegenNativeComponent, type ViewProps } from 'react-native';
2
+
3
+ export interface NativeProps extends ViewProps {
4
+ /** Unique id this surface registers under in the native surface registry. */
5
+ surfaceId: string;
6
+ }
7
+
8
+ export default codegenNativeComponent<NativeProps>('AuVideoSurfaceView');
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('AuVideo');
5
+ //# sourceMappingURL=NativeAuVideo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeAuVideo.ts"],"mappings":";;AAAA,SACEA,mBAAmB,QAGd,cAAc;AAqGrB,eAAeA,mBAAmB,CAACC,YAAY,CAAO,SAAS,CAAC","ignoreList":[]}
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+
3
+ import { useRef } from 'react';
4
+ import { Animated, PanResponder, Pressable, StyleSheet, Text, useWindowDimensions } from 'react-native';
5
+ import { FLOATING_SURFACE_ID } from "../core/VideoManager.js";
6
+ import { usePlayback } from "../hooks/usePlayback.js";
7
+ import { useVideoManager } from "../provider/VideoContext.js";
8
+ import { VideoSurface } from "./VideoSurface.js";
9
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ /**
11
+ * Built-in draggable in-app floating window, rendered by VideoProvider and
12
+ * shown by `showFloating()`. Same engine, same position — expand returns
13
+ * to fullscreen, ✕ hides the window (playback continues; call pause()
14
+ * in onModeChanged if you want otherwise).
15
+ */
16
+ export function FloatingPlayer({
17
+ width = 200
18
+ }) {
19
+ const manager = useVideoManager();
20
+ const floating = usePlayback(s => s.floating);
21
+ const screen = useWindowDimensions();
22
+ const height = width * 9 / 16;
23
+ const pan = useRef(new Animated.ValueXY({
24
+ x: screen.width - width - 12,
25
+ y: screen.height - height - 96
26
+ })).current;
27
+ const responder = useRef(PanResponder.create({
28
+ onMoveShouldSetPanResponder: (_e, g) => Math.abs(g.dx) > 4 || Math.abs(g.dy) > 4,
29
+ onPanResponderGrant: () => {
30
+ pan.extractOffset();
31
+ },
32
+ onPanResponderMove: Animated.event([null, {
33
+ dx: pan.x,
34
+ dy: pan.y
35
+ }], {
36
+ useNativeDriver: false
37
+ }),
38
+ onPanResponderRelease: () => {
39
+ pan.flattenOffset();
40
+ }
41
+ })).current;
42
+ if (!floating) {
43
+ return null;
44
+ }
45
+ return /*#__PURE__*/_jsxs(Animated.View, {
46
+ style: [styles.window, {
47
+ width,
48
+ height,
49
+ transform: pan.getTranslateTransform()
50
+ }],
51
+ ...responder.panHandlers,
52
+ children: [/*#__PURE__*/_jsx(VideoSurface, {
53
+ surfaceId: FLOATING_SURFACE_ID,
54
+ autoAttach: true,
55
+ style: styles.surface
56
+ }), /*#__PURE__*/_jsx(Pressable, {
57
+ style: [styles.button, styles.closeButton],
58
+ hitSlop: 8,
59
+ onPress: () => manager.hideFloating(),
60
+ children: /*#__PURE__*/_jsx(Text, {
61
+ style: styles.icon,
62
+ children: "\u2715"
63
+ })
64
+ }), /*#__PURE__*/_jsx(Pressable, {
65
+ style: [styles.button, styles.expandButton],
66
+ hitSlop: 8,
67
+ onPress: () => {
68
+ manager.hideFloating();
69
+ manager.enterFullscreen();
70
+ },
71
+ children: /*#__PURE__*/_jsx(Text, {
72
+ style: styles.icon,
73
+ children: "\u2922"
74
+ })
75
+ })]
76
+ });
77
+ }
78
+ const styles = StyleSheet.create({
79
+ window: {
80
+ position: 'absolute',
81
+ top: 0,
82
+ left: 0,
83
+ borderRadius: 8,
84
+ overflow: 'hidden',
85
+ backgroundColor: '#000',
86
+ elevation: 8,
87
+ shadowColor: '#000',
88
+ shadowOpacity: 0.4,
89
+ shadowRadius: 8,
90
+ shadowOffset: {
91
+ width: 0,
92
+ height: 4
93
+ }
94
+ },
95
+ surface: {
96
+ flex: 1
97
+ },
98
+ button: {
99
+ position: 'absolute',
100
+ top: 4,
101
+ backgroundColor: 'rgba(0,0,0,0.5)',
102
+ borderRadius: 12,
103
+ width: 24,
104
+ height: 24,
105
+ alignItems: 'center',
106
+ justifyContent: 'center'
107
+ },
108
+ closeButton: {
109
+ right: 4
110
+ },
111
+ expandButton: {
112
+ left: 4
113
+ },
114
+ icon: {
115
+ color: '#fff',
116
+ fontSize: 12
117
+ }
118
+ });
119
+ //# sourceMappingURL=FloatingPlayer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useRef","Animated","PanResponder","Pressable","StyleSheet","Text","useWindowDimensions","FLOATING_SURFACE_ID","usePlayback","useVideoManager","VideoSurface","jsx","_jsx","jsxs","_jsxs","FloatingPlayer","width","manager","floating","s","screen","height","pan","ValueXY","x","y","current","responder","create","onMoveShouldSetPanResponder","_e","g","Math","abs","dx","dy","onPanResponderGrant","extractOffset","onPanResponderMove","event","useNativeDriver","onPanResponderRelease","flattenOffset","View","style","styles","window","transform","getTranslateTransform","panHandlers","children","surfaceId","autoAttach","surface","button","closeButton","hitSlop","onPress","hideFloating","icon","expandButton","enterFullscreen","position","top","left","borderRadius","overflow","backgroundColor","elevation","shadowColor","shadowOpacity","shadowRadius","shadowOffset","flex","alignItems","justifyContent","right","color","fontSize"],"sourceRoot":"../../../src","sources":["components/FloatingPlayer.tsx"],"mappings":"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","ignoreList":[]}