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,28 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "AuVideo"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/grdp99/react-native-au-video.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+ s.frameworks = "AVFoundation", "AVKit"
20
+ s.swift_version = "5.9"
21
+ # Swift core + ObjC++ glue in one pod: the glue imports the generated
22
+ # <AuVideo/AuVideo-Swift.h>, which requires the pod to define a module.
23
+ s.pod_target_xcconfig = {
24
+ "DEFINES_MODULE" => "YES",
25
+ }
26
+
27
+ install_modules_dependencies(s)
28
+ end
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gurdeep Singh
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # react-native-video-provider
2
+
3
+ **One native player. Many surfaces. Zero interruptions.**
4
+
5
+ A singleton-engine video library for React Native (Android + iOS, New
6
+ Architecture). The app owns exactly one native playback engine (ExoPlayer /
7
+ AVPlayer) for its whole lifetime; React components are just *rendering
8
+ surfaces* the engine attaches to. Moving from a feed cell to a detail screen
9
+ to fullscreen to a floating window never reloads, rebuffers, or resets
10
+ position — exactly how YouTube, Netflix and Twitter/X players behave.
11
+
12
+ ```
13
+ Feed ──▶ Detail ──▶ Fullscreen (rotation unlocked) ──▶ Floating window
14
+ same engine · same buffer · same position
15
+ ```
16
+
17
+ - 🎯 **Singleton engine** — created silently once by the provider; survives
18
+ navigation, tab switches, unmounts, Fast Refresh
19
+ - 🔁 **Same-video handoff** — `setSource` with the same `id` is a no-op for
20
+ the engine; the player just re-parents to the new surface
21
+ - 📺 **Fullscreen** — built-in host that unlocks rotation while visible and
22
+ restores the previous orientation lock on exit
23
+ - 🎈 **Floating player** — built-in draggable in-app window
24
+ - 🖼 **Picture in Picture** — Android + iOS
25
+ - ⚡ **New Architecture native** — TurboModule + Fabric, typed end to end
26
+ - 🧠 **Zustand-powered state** — selector subscriptions, no re-render storms
27
+
28
+ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full design and
29
+ [docs/API.md](docs/API.md) for the complete API reference.
30
+
31
+ ## Installation
32
+
33
+ ```sh
34
+ npm install react-native-video-provider
35
+ cd ios && pod install
36
+ ```
37
+
38
+ Requires React Native 0.80+ with the New Architecture enabled.
39
+
40
+ ## Quick start
41
+
42
+ **1. Wrap the app once.** The provider silently creates the engine and mounts
43
+ the fullscreen/floating hosts above your app:
44
+
45
+ ```tsx
46
+ import { VideoProvider } from 'react-native-video-provider';
47
+
48
+ export default function App() {
49
+ return (
50
+ <VideoProvider>
51
+ <Navigation />
52
+ </VideoProvider>
53
+ );
54
+ }
55
+ ```
56
+
57
+ **2. Play a video anywhere:**
58
+
59
+ ```tsx
60
+ import { VideoPlayer } from 'react-native-video-provider';
61
+
62
+ <VideoPlayer
63
+ source={{ id: '123', uri: 'https://example.com/video.m3u8', title: 'Big Buck Bunny' }}
64
+ style={{ aspectRatio: 16 / 9 }}
65
+ />
66
+ ```
67
+
68
+ **3. Open a detail screen with the same video** — because the `id` matches,
69
+ the engine is untouched and playback continues from the exact frame:
70
+
71
+ ```tsx
72
+ // DetailScreen.tsx — same source id ⇒ handoff, not reload
73
+ <VideoPlayer source={{ id: '123', uri }} style={{ aspectRatio: 16 / 9 }} />
74
+ ```
75
+
76
+ **4. Fullscreen / floating / PiP from anywhere:**
77
+
78
+ ```tsx
79
+ const player = useVideo();
80
+
81
+ player.enterFullscreen(); // rotation unlocks while visible
82
+ player.showFloating(); // draggable in-app window
83
+ player.enterPiP(); // system picture-in-picture
84
+ ```
85
+
86
+ ## Surfaces (the core idea)
87
+
88
+ `<VideoSurface>` never creates a player — it registers a mount point. The
89
+ engine renders into at most one surface at a time; `attach(id)` re-parents
90
+ the native player view with no playback interruption. If you attach to a
91
+ surface that hasn't mounted yet (navigation in flight), the engine attaches
92
+ the moment it appears.
93
+
94
+ ```tsx
95
+ <VideoSurface surfaceId="feed" style={{ aspectRatio: 16 / 9 }} />
96
+
97
+ const player = useVideo();
98
+ player.setSource(video); // load (or hand off)
99
+ player.attach('feed'); // render here
100
+ ```
101
+
102
+ `<VideoPlayer>` is the convenience wrapper that does `setSource` + `attach` +
103
+ optional controls in one component.
104
+
105
+ ## State & events
106
+
107
+ ```tsx
108
+ // Selector subscriptions — re-render only for what you display
109
+ const position = usePlayback((s) => s.position);
110
+ const { isFullscreen, toggle } = useFullscreen();
111
+
112
+ useVideoEvents({
113
+ onEnd: () => playNext(),
114
+ onError: (e) => console.warn(e.code, e.message),
115
+ });
116
+ ```
117
+
118
+ ## Platform setup
119
+
120
+ ### iOS — fullscreen rotation
121
+
122
+ iOS asks the AppDelegate which orientations are allowed. Forward that to the
123
+ library so rotation unlocks only while fullscreen is visible (skip this if
124
+ your app already supports all orientations):
125
+
126
+ ```swift
127
+ // AppDelegate.swift
128
+ import AuVideo
129
+
130
+ func application(_ application: UIApplication,
131
+ supportedInterfaceOrientationsFor window: UIWindow?)
132
+ -> UIInterfaceOrientationMask {
133
+ return AuVideoOrientation.mask(withDefault: .portrait)
134
+ }
135
+ ```
136
+
137
+ ### iOS — Picture in Picture / background audio
138
+
139
+ Enable *Audio, AirPlay and Picture in Picture* in Signing & Capabilities →
140
+ Background Modes (adds `audio` to `UIBackgroundModes` in Info.plist).
141
+
142
+ ### Android — Picture in Picture
143
+
144
+ Declare PiP support on your main activity in `AndroidManifest.xml`:
145
+
146
+ ```xml
147
+ <activity
148
+ android:name=".MainActivity"
149
+ android:supportsPictureInPicture="true"
150
+ android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|density" />
151
+ ```
152
+
153
+ (The `orientation|screenSize` entries are part of the default RN template and
154
+ also keep fullscreen rotation from recreating the activity.)
155
+
156
+ ## Example app
157
+
158
+ ```sh
159
+ yarn
160
+ yarn example android # or: yarn example ios
161
+ ```
162
+
163
+ The example shows a feed → detail handoff with a live status panel, plus
164
+ fullscreen, floating and PiP buttons.
165
+
166
+ ## Roadmap
167
+
168
+ - Queue (next/previous/playlist/autoplay)
169
+ - Background playback (Android MediaSessionService + notification, iOS remote
170
+ commands / lock-screen controls)
171
+ - Quality, subtitle and audio-track selection
172
+ - Brightness/volume swipe gestures, pinch zoom
173
+ - True ahead-of-time preloading (Media3 `PreloadManager`)
174
+
175
+ ## Contributing
176
+
177
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and the
178
+ [architecture doc](docs/ARCHITECTURE.md). PRs that add a second player
179
+ instance will be rejected on principle. 🙂
180
+
181
+ ## License
182
+
183
+ MIT
@@ -0,0 +1,59 @@
1
+ buildscript {
2
+ ext.AuVideo = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ media3Version: "1.8.0"
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+
14
+ return AuVideo[prop]
15
+ }
16
+
17
+ repositories {
18
+ google()
19
+ mavenCentral()
20
+ }
21
+
22
+ dependencies {
23
+ classpath "com.android.tools.build:gradle:8.7.2"
24
+ // noinspection DifferentKotlinGradleVersion
25
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
26
+ }
27
+ }
28
+
29
+
30
+ apply plugin: "com.android.library"
31
+ apply plugin: "kotlin-android"
32
+
33
+ apply plugin: "com.facebook.react"
34
+
35
+ android {
36
+ namespace "com.auvideo"
37
+
38
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
39
+
40
+ defaultConfig {
41
+ minSdkVersion getExtOrDefault("minSdkVersion")
42
+ }
43
+
44
+ compileOptions {
45
+ sourceCompatibility JavaVersion.VERSION_17
46
+ targetCompatibility JavaVersion.VERSION_17
47
+ }
48
+ }
49
+
50
+ dependencies {
51
+ implementation "com.facebook.react:react-android"
52
+
53
+ def media3Version = getExtOrDefault("media3Version")
54
+ implementation "androidx.media3:media3-exoplayer:${media3Version}"
55
+ implementation "androidx.media3:media3-exoplayer-hls:${media3Version}"
56
+ implementation "androidx.media3:media3-exoplayer-dash:${media3Version}"
57
+ implementation "androidx.media3:media3-ui:${media3Version}"
58
+ implementation "androidx.core:core-ktx:1.13.1"
59
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,259 @@
1
+ package com.auvideo
2
+
3
+ import android.app.PictureInPictureParams
4
+ import android.content.pm.ActivityInfo
5
+ import android.content.pm.PackageManager
6
+ import android.os.Build
7
+ import android.util.Rational
8
+ import androidx.core.view.WindowCompat
9
+ import androidx.core.view.WindowInsetsCompat
10
+ import androidx.core.view.WindowInsetsControllerCompat
11
+ import com.facebook.react.bridge.Arguments
12
+ import com.facebook.react.bridge.Promise
13
+ import com.facebook.react.bridge.ReactApplicationContext
14
+ import com.facebook.react.bridge.ReadableMap
15
+ import com.facebook.react.bridge.UiThreadUtil
16
+
17
+ class AuVideoModule(reactContext: ReactApplicationContext) :
18
+ NativeAuVideoSpec(reactContext), PlayerCore.Listener {
19
+
20
+ private var previousOrientation: Int? = null
21
+
22
+ // ------------------------------------------------------------ lifecycle
23
+
24
+ override fun nativeInit() {
25
+ UiThreadUtil.runOnUiThread {
26
+ PlayerCore.listener = this
27
+ PlayerCore.initialize(reactApplicationContext)
28
+ }
29
+ }
30
+
31
+ override fun releasePlayer() {
32
+ UiThreadUtil.runOnUiThread {
33
+ PlayerCore.release()
34
+ PlayerCore.listener = null
35
+ }
36
+ }
37
+
38
+ override fun invalidate() {
39
+ PlayerCore.listener = null
40
+ super.invalidate()
41
+ }
42
+
43
+ // -------------------------------------------------------------- source
44
+
45
+ override fun setSource(source: ReadableMap, autoplay: Boolean) {
46
+ val spec = parseSource(source) ?: return
47
+ UiThreadUtil.runOnUiThread { PlayerCore.setSource(spec, autoplay) }
48
+ }
49
+
50
+ override fun preload(source: ReadableMap) {
51
+ val spec = parseSource(source) ?: return
52
+ UiThreadUtil.runOnUiThread { PlayerCore.preload(spec) }
53
+ }
54
+
55
+ private fun parseSource(map: ReadableMap): SourceSpec? {
56
+ val id = map.getString("id") ?: return null
57
+ val uri = map.getString("uri") ?: return null
58
+ val headers = HashMap<String, String>()
59
+ map.getMap("headers")?.let { h ->
60
+ val it = h.keySetIterator()
61
+ while (it.hasNextKey()) {
62
+ val key = it.nextKey()
63
+ h.getString(key)?.let { value -> headers[key] = value }
64
+ }
65
+ }
66
+ return SourceSpec(
67
+ id = id,
68
+ uri = uri,
69
+ headers = headers,
70
+ title = map.getString("title"),
71
+ artist = map.getString("artist"),
72
+ artworkUri = map.getString("artworkUri"),
73
+ startPosition = if (map.hasKey("startPosition")) map.getDouble("startPosition") else 0.0,
74
+ )
75
+ }
76
+
77
+ // ------------------------------------------------------------- commands
78
+
79
+ override fun play() {
80
+ UiThreadUtil.runOnUiThread { PlayerCore.play() }
81
+ }
82
+
83
+ override fun pause() {
84
+ UiThreadUtil.runOnUiThread { PlayerCore.pause() }
85
+ }
86
+
87
+ override fun stop() {
88
+ UiThreadUtil.runOnUiThread { PlayerCore.stop() }
89
+ }
90
+
91
+ override fun seekTo(position: Double) {
92
+ UiThreadUtil.runOnUiThread { PlayerCore.seekTo(position) }
93
+ }
94
+
95
+ override fun setRate(rate: Double) {
96
+ UiThreadUtil.runOnUiThread { PlayerCore.setRate(rate) }
97
+ }
98
+
99
+ override fun setVolume(volume: Double) {
100
+ UiThreadUtil.runOnUiThread { PlayerCore.setVolume(volume) }
101
+ }
102
+
103
+ override fun setMuted(muted: Boolean) {
104
+ UiThreadUtil.runOnUiThread { PlayerCore.setMuted(muted) }
105
+ }
106
+
107
+ override fun setRepeat(repeat: Boolean) {
108
+ UiThreadUtil.runOnUiThread { PlayerCore.setRepeat(repeat) }
109
+ }
110
+
111
+ override fun setResizeMode(mode: String) {
112
+ UiThreadUtil.runOnUiThread { PlayerCore.setResizeMode(mode) }
113
+ }
114
+
115
+ override fun getPosition(promise: Promise) {
116
+ UiThreadUtil.runOnUiThread { promise.resolve(PlayerCore.positionSeconds()) }
117
+ }
118
+
119
+ // ------------------------------------------------------------- surfaces
120
+
121
+ override fun attach(surfaceId: String) {
122
+ UiThreadUtil.runOnUiThread { PlayerCore.attach(surfaceId) }
123
+ }
124
+
125
+ override fun detach() {
126
+ UiThreadUtil.runOnUiThread { PlayerCore.detach() }
127
+ }
128
+
129
+ // ----------------------------------------------------------- fullscreen
130
+
131
+ override fun enterFullscreen() {
132
+ UiThreadUtil.runOnUiThread {
133
+ val activity = currentActivity ?: return@runOnUiThread
134
+ if (previousOrientation == null) {
135
+ previousOrientation = activity.requestedOrientation
136
+ }
137
+ // Unlock rotation while fullscreen is visible, even in portrait-locked apps.
138
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
139
+
140
+ val window = activity.window
141
+ WindowCompat.setDecorFitsSystemWindows(window, false)
142
+ WindowInsetsControllerCompat(window, window.decorView).apply {
143
+ systemBarsBehavior =
144
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
145
+ hide(WindowInsetsCompat.Type.systemBars())
146
+ }
147
+ }
148
+ }
149
+
150
+ override fun exitFullscreen() {
151
+ UiThreadUtil.runOnUiThread {
152
+ val activity = currentActivity ?: return@runOnUiThread
153
+ activity.requestedOrientation =
154
+ previousOrientation ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
155
+ previousOrientation = null
156
+
157
+ val window = activity.window
158
+ WindowCompat.setDecorFitsSystemWindows(window, true)
159
+ WindowInsetsControllerCompat(window, window.decorView)
160
+ .show(WindowInsetsCompat.Type.systemBars())
161
+ }
162
+ }
163
+
164
+ // ------------------------------------------------------------------ pip
165
+
166
+ override fun enterPip(promise: Promise) {
167
+ UiThreadUtil.runOnUiThread {
168
+ val activity = currentActivity
169
+ if (
170
+ activity == null ||
171
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
172
+ !activity.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
173
+ ) {
174
+ promise.resolve(false)
175
+ return@runOnUiThread
176
+ }
177
+ try {
178
+ val params = PictureInPictureParams.Builder()
179
+ .setAspectRatio(Rational(16, 9))
180
+ .build()
181
+ val entered = activity.enterPictureInPictureMode(params)
182
+ if (entered) {
183
+ emitOnPipChange(Arguments.createMap().apply { putBoolean("active", true) })
184
+ }
185
+ promise.resolve(entered)
186
+ } catch (e: Exception) {
187
+ promise.resolve(false)
188
+ }
189
+ }
190
+ }
191
+
192
+ override fun exitPip() {
193
+ UiThreadUtil.runOnUiThread {
194
+ // Android has no direct "leave PiP" API; bringing the task forward
195
+ // expands the PiP window back into the full activity.
196
+ val activity = currentActivity ?: return@runOnUiThread
197
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.isInPictureInPictureMode) {
198
+ activity.moveTaskToBack(false)
199
+ emitOnPipChange(Arguments.createMap().apply { putBoolean("active", false) })
200
+ }
201
+ }
202
+ }
203
+
204
+ // --------------------------------------------------- PlayerCore.Listener
205
+
206
+ override fun onStatusChange(status: String) {
207
+ emitOnStatusChange(Arguments.createMap().apply { putString("status", status) })
208
+ }
209
+
210
+ override fun onLoad(videoId: String, duration: Double, width: Int, height: Int) {
211
+ emitOnLoad(
212
+ Arguments.createMap().apply {
213
+ putString("videoId", videoId)
214
+ putDouble("duration", duration)
215
+ putDouble("width", width.toDouble())
216
+ putDouble("height", height.toDouble())
217
+ }
218
+ )
219
+ }
220
+
221
+ override fun onProgress(position: Double, duration: Double, buffered: Double) {
222
+ emitOnProgress(
223
+ Arguments.createMap().apply {
224
+ putDouble("position", position)
225
+ putDouble("duration", duration)
226
+ putDouble("buffered", buffered)
227
+ }
228
+ )
229
+ }
230
+
231
+ override fun onSeek(position: Double) {
232
+ emitOnSeek(Arguments.createMap().apply { putDouble("position", position) })
233
+ }
234
+
235
+ override fun onEnd() {
236
+ emitOnEnd()
237
+ }
238
+
239
+ override fun onError(code: String, message: String) {
240
+ emitOnError(
241
+ Arguments.createMap().apply {
242
+ putString("code", code)
243
+ putString("message", message)
244
+ }
245
+ )
246
+ }
247
+
248
+ override fun onAttach(surfaceId: String) {
249
+ emitOnAttach(Arguments.createMap().apply { putString("surfaceId", surfaceId) })
250
+ }
251
+
252
+ override fun onDetach(surfaceId: String) {
253
+ emitOnDetach(Arguments.createMap().apply { putString("surfaceId", surfaceId) })
254
+ }
255
+
256
+ companion object {
257
+ const val NAME = NativeAuVideoSpec.NAME
258
+ }
259
+ }
@@ -0,0 +1,35 @@
1
+ package com.auvideo
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import com.facebook.react.uimanager.ViewManager
9
+
10
+ class AuVideoPackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
+ return if (name == AuVideoModule.NAME) {
13
+ AuVideoModule(reactContext)
14
+ } else {
15
+ null
16
+ }
17
+ }
18
+
19
+ override fun createViewManagers(
20
+ reactContext: ReactApplicationContext
21
+ ): List<ViewManager<*, *>> = listOf(AuVideoSurfaceViewManager())
22
+
23
+ override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
24
+ mapOf(
25
+ AuVideoModule.NAME to ReactModuleInfo(
26
+ name = AuVideoModule.NAME,
27
+ className = AuVideoModule.NAME,
28
+ canOverrideExistingModule = false,
29
+ needsEagerInit = false,
30
+ isCxxModule = false,
31
+ isTurboModule = true
32
+ )
33
+ )
34
+ }
35
+ }
@@ -0,0 +1,40 @@
1
+ package com.auvideo
2
+
3
+ import android.content.Context
4
+ import android.view.View
5
+ import android.widget.FrameLayout
6
+
7
+ /**
8
+ * A dumb mount point the player view gets re-parented into. Does not create
9
+ * or own any player resources.
10
+ */
11
+ class AuVideoSurfaceView(context: Context) : FrameLayout(context) {
12
+
13
+ var surfaceId: String? = null
14
+ set(value) {
15
+ if (field == value) return
16
+ field?.let { SurfaceRegistry.unregister(it, this) }
17
+ field = value
18
+ value?.let { SurfaceRegistry.register(it, this) }
19
+ }
20
+
21
+ // RN lays this view out itself; children added natively (the player view)
22
+ // would otherwise never get measured. Standard measure-and-layout relay.
23
+ private val measureAndLayout = Runnable {
24
+ measure(
25
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
26
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
27
+ )
28
+ layout(left, top, right, bottom)
29
+ }
30
+
31
+ override fun requestLayout() {
32
+ super.requestLayout()
33
+ post(measureAndLayout)
34
+ }
35
+
36
+ override fun onViewAdded(child: View?) {
37
+ super.onViewAdded(child)
38
+ post(measureAndLayout)
39
+ }
40
+ }
@@ -0,0 +1,38 @@
1
+ package com.auvideo
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.SimpleViewManager
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.uimanager.ViewManagerDelegate
7
+ import com.facebook.react.uimanager.annotations.ReactProp
8
+ import com.facebook.react.viewmanagers.AuVideoSurfaceViewManagerDelegate
9
+ import com.facebook.react.viewmanagers.AuVideoSurfaceViewManagerInterface
10
+
11
+ @ReactModule(name = AuVideoSurfaceViewManager.NAME)
12
+ class AuVideoSurfaceViewManager :
13
+ SimpleViewManager<AuVideoSurfaceView>(),
14
+ AuVideoSurfaceViewManagerInterface<AuVideoSurfaceView> {
15
+
16
+ private val delegate = AuVideoSurfaceViewManagerDelegate(this)
17
+
18
+ override fun getDelegate(): ViewManagerDelegate<AuVideoSurfaceView> = delegate
19
+
20
+ override fun getName(): String = NAME
21
+
22
+ override fun createViewInstance(context: ThemedReactContext): AuVideoSurfaceView =
23
+ AuVideoSurfaceView(context)
24
+
25
+ @ReactProp(name = "surfaceId")
26
+ override fun setSurfaceId(view: AuVideoSurfaceView, value: String?) {
27
+ view.surfaceId = value
28
+ }
29
+
30
+ override fun onDropViewInstance(view: AuVideoSurfaceView) {
31
+ view.surfaceId = null
32
+ super.onDropViewInstance(view)
33
+ }
34
+
35
+ companion object {
36
+ const val NAME = "AuVideoSurfaceView"
37
+ }
38
+ }