extended-vlc-player 0.1.3 → 0.1.5
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 +6 -5
- package/android/build.gradle +3 -15
- package/android/src/main/java/expo/modules/extendedvlcplayer/ExtendedVlcPlayerModule.kt +35 -5
- package/android/src/main/java/expo/modules/extendedvlcplayer/ExtendedVlcPlayerViewComponentView.kt +22 -11
- package/android/src/main/java/expo/modules/extendedvlcplayer/PlayerSession.kt +22 -13
- package/app.plugin.js +22 -7
- package/build/useExtendedVlcPlayer.d.ts +2 -2
- package/build/useExtendedVlcPlayer.d.ts.map +1 -1
- package/build/useExtendedVlcPlayer.js +32 -24
- package/build/useExtendedVlcPlayer.js.map +1 -1
- package/ios/ExtendedVlcPlayer.podspec +2 -0
- package/ios/ExtendedVlcPlayerModule.swift +8 -0
- package/ios/ExtendedVlcPlayerViewComponentView.mm +49 -12
- package/ios/PlayerRegistryBridge.swift +13 -0
- package/package.json +1 -1
- package/src/useExtendedVlcPlayer.ts +31 -31
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Under the hood:
|
|
|
7
7
|
| Platform | Engine | Default version | Min OS |
|
|
8
8
|
| -------- | ----------------------------------- | --------------- | --------- |
|
|
9
9
|
| iOS | [MobileVLCKit](https://code.videolan.org/videolan/VLCKit) | `~3.7.3` | iOS 16.4+ |
|
|
10
|
-
| Android | [libVLC](https://code.videolan.org/videolan/vlc-android) | `3.
|
|
10
|
+
| Android | [libVLC](https://code.videolan.org/videolan/vlc-android) | `3.7.5` | API 26+ |
|
|
11
11
|
|
|
12
12
|
The JS surface intentionally mirrors `expo-video` so an existing `<VideoView>` consumer (e.g. `BackgroundVideoPlayer.jsx`) can be migrated with a one-line import swap.
|
|
13
13
|
|
|
@@ -185,7 +185,7 @@ Then in `app.json`:
|
|
|
185
185
|
"plugins": [
|
|
186
186
|
["extended-vlc-player", {
|
|
187
187
|
"ios": { "mobileVlcKitVersion": "3.7.3" },
|
|
188
|
-
"android": { "libVlcVersion": "3.
|
|
188
|
+
"android": { "libVlcVersion": "3.7.5" }
|
|
189
189
|
}]
|
|
190
190
|
]
|
|
191
191
|
```
|
|
@@ -200,7 +200,7 @@ The plugin (`app.plugin.js`) does the following, idempotently:
|
|
|
200
200
|
|
|
201
201
|
- **iOS Podfile** — adds `pod 'MobileVLCKit', '~> 3.7.3'` and a `post_install` hook that pins `IPHONEOS_DEPLOYMENT_TARGET = 16.4` on the MobileVLCKit target.
|
|
202
202
|
- **iOS Info.plist** — adds `"audio"` to `UIBackgroundModes` so the audio session is eligible for background playback and PiP.
|
|
203
|
-
- **Android `android/app/build.gradle`** — adds `implementation "org.videolan.android:libvlc:3.
|
|
203
|
+
- **Android `android/app/build.gradle`** — adds `implementation "org.videolan.android:libvlc-all:3.7.5"`. ABI filters are inherited from the host app.
|
|
204
204
|
- **Android `AndroidManifest.xml`** — adds `android:supportsPictureInPicture="true"` and the `configChanges` set to `MainActivity`.
|
|
205
205
|
|
|
206
206
|
To publish to npm, run `npm publish` from the module root. To consume from a local checkout, `file:` works during development and you should switch to the registry version before shipping (see [Troubleshooting](#troubleshooting)).
|
|
@@ -341,8 +341,9 @@ All options are optional. Defaults match what the plugin is pinned to in CI.
|
|
|
341
341
|
enableBitcode: false,
|
|
342
342
|
},
|
|
343
343
|
android: {
|
|
344
|
-
// org.videolan.android:libvlc version.
|
|
345
|
-
|
|
344
|
+
// org.videolan.android:libvlc-all version. 3.7.5 ships 16 KB-aligned
|
|
345
|
+
// arm64 native binaries required by Android 15+ 16 KB devices.
|
|
346
|
+
libVlcVersion: '3.7.5',
|
|
346
347
|
},
|
|
347
348
|
pip: {
|
|
348
349
|
// Reserved for the snapshot bridge. The current bridge runs at the
|
package/android/build.gradle
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
// Top-level build file for the `extended-vlc-player` local Expo Module.
|
|
2
|
-
buildscript {
|
|
3
|
-
repositories {
|
|
4
|
-
google()
|
|
5
|
-
mavenCentral()
|
|
6
|
-
}
|
|
7
|
-
dependencies {
|
|
8
|
-
classpath 'com.android.tools.build:gradle'
|
|
9
|
-
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin'
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
1
|
apply plugin: 'com.android.library'
|
|
14
2
|
apply plugin: 'kotlin-android'
|
|
15
3
|
apply plugin: 'expo-module-gradle-plugin'
|
|
@@ -20,8 +8,8 @@ version = '0.1.0'
|
|
|
20
8
|
android {
|
|
21
9
|
namespace 'expo.modules.extendedvlcplayer'
|
|
22
10
|
defaultConfig {
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
versionCode 1
|
|
12
|
+
versionName '0.1.0'
|
|
25
13
|
}
|
|
26
14
|
// The libVLC .so files ship native code for four ABIs. The Expo app
|
|
27
15
|
// already filters to its own ABIs in android/app/build.gradle; the host
|
|
@@ -32,5 +20,5 @@ android {
|
|
|
32
20
|
}
|
|
33
21
|
|
|
34
22
|
dependencies {
|
|
35
|
-
implementation 'org.videolan.android:libvlc:3.
|
|
23
|
+
implementation 'org.videolan.android:libvlc-all:3.7.5'
|
|
36
24
|
}
|
|
@@ -2,12 +2,11 @@ package expo.modules.extendedvlcplayer
|
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.app.PictureInPictureParams
|
|
5
|
-
import android.content.Context
|
|
6
|
-
import android.content.res.Configuration
|
|
7
5
|
import android.os.Build
|
|
8
6
|
import android.util.Rational
|
|
9
7
|
import expo.modules.kotlin.modules.Module
|
|
10
8
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
9
|
+
import expo.modules.kotlin.records.Record
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* TurboModule for `extended-vlc-player` on Android.
|
|
@@ -22,9 +21,40 @@ class ExtendedVlcPlayerModule : Module() {
|
|
|
22
21
|
override fun definition() = ModuleDefinition {
|
|
23
22
|
Name("ExtendedVlcPlayer")
|
|
24
23
|
|
|
24
|
+
Function("createPlayer") {
|
|
25
|
+
val context = appContext.reactContext
|
|
26
|
+
?: throw IllegalStateException("React context is not available")
|
|
27
|
+
PlayerRegistry.create(context).first
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Function("destroyPlayer") { instanceId: Int ->
|
|
31
|
+
PlayerRegistry.destroy(instanceId)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
View(ExtendedVlcPlayerViewComponentView::class) {
|
|
35
|
+
Name("ExtendedVlcPlayerView")
|
|
36
|
+
Events(
|
|
37
|
+
"onLoad",
|
|
38
|
+
"onProgress",
|
|
39
|
+
"onPlaying",
|
|
40
|
+
"onPaused",
|
|
41
|
+
"onEnded",
|
|
42
|
+
"onError",
|
|
43
|
+
"onBuffering",
|
|
44
|
+
"onPictureInPictureStart",
|
|
45
|
+
"onPictureInPictureStop"
|
|
46
|
+
)
|
|
47
|
+
Prop("player") { view: ExtendedVlcPlayerViewComponentView, id: Int ->
|
|
48
|
+
view.setPlayer(id)
|
|
49
|
+
}
|
|
50
|
+
Prop("contentFit") { view: ExtendedVlcPlayerViewComponentView, fit: String ->
|
|
51
|
+
view.setContentFit(fit)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
25
55
|
AsyncFunction("isPictureInPictureSupported") {
|
|
26
56
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
|
27
|
-
appContext.
|
|
57
|
+
appContext.currentActivity?.application?.packageManager?.hasSystemFeature(
|
|
28
58
|
android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE
|
|
29
59
|
) == true
|
|
30
60
|
}
|
|
@@ -76,7 +106,7 @@ class ExtendedVlcPlayerModule : Module() {
|
|
|
76
106
|
}
|
|
77
107
|
|
|
78
108
|
AsyncFunction("startPictureInPicture") { instanceId: Int ->
|
|
79
|
-
val activity = appContext.
|
|
109
|
+
val activity = appContext.currentActivity ?: return@AsyncFunction false
|
|
80
110
|
val session = PlayerRegistry.session(instanceId) ?: return@AsyncFunction false
|
|
81
111
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return@AsyncFunction false
|
|
82
112
|
val params = PictureInPictureParams.Builder()
|
|
@@ -96,4 +126,4 @@ class ExtendedVlcPlayerModule : Module() {
|
|
|
96
126
|
class ReplacePayload(
|
|
97
127
|
val uri: String,
|
|
98
128
|
val instanceId: Int = 0
|
|
99
|
-
)
|
|
129
|
+
) : Record
|
package/android/src/main/java/expo/modules/extendedvlcplayer/ExtendedVlcPlayerViewComponentView.kt
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package expo.modules.extendedvlcplayer
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
-
import android.view.View
|
|
5
4
|
import expo.modules.kotlin.AppContext
|
|
6
5
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
7
6
|
import expo.modules.kotlin.views.ExpoView
|
|
@@ -17,6 +16,7 @@ class ExtendedVlcPlayerViewComponentView(context: Context, appContext: AppContex
|
|
|
17
16
|
ExpoView(context, appContext) {
|
|
18
17
|
|
|
19
18
|
private var playerId: Int = 0
|
|
19
|
+
private var contentFit: String = "contain"
|
|
20
20
|
|
|
21
21
|
val onLoad by EventDispatcher<Map<String, Any>>()
|
|
22
22
|
val onProgress by EventDispatcher<Map<String, Any>>()
|
|
@@ -29,11 +29,23 @@ class ExtendedVlcPlayerViewComponentView(context: Context, appContext: AppContex
|
|
|
29
29
|
val onPictureInPictureStop by EventDispatcher<Unit>()
|
|
30
30
|
|
|
31
31
|
fun setPlayer(id: Int) {
|
|
32
|
-
if (playerId == id)
|
|
33
|
-
|
|
32
|
+
if (playerId == id) {
|
|
33
|
+
attachToSession()
|
|
34
|
+
return
|
|
35
|
+
}
|
|
34
36
|
detachFromSession()
|
|
35
37
|
playerId = id
|
|
36
|
-
|
|
38
|
+
attachToSession()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fun setContentFit(value: String) {
|
|
42
|
+
contentFit = value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private fun attachToSession() {
|
|
46
|
+
if (playerId == 0) return
|
|
47
|
+
val session = PlayerRegistry.session(playerId) ?: return
|
|
48
|
+
if (session.drawable.parent === this) return
|
|
37
49
|
val drawable = session.drawable
|
|
38
50
|
drawable.layoutParams = android.view.ViewGroup.LayoutParams(
|
|
39
51
|
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
|
|
@@ -53,12 +65,6 @@ class ExtendedVlcPlayerViewComponentView(context: Context, appContext: AppContex
|
|
|
53
65
|
session.onPictureInPictureStop = { onPictureInPictureStop(Unit) }
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
fun createPlayer(): Int {
|
|
57
|
-
val (id, _) = PlayerRegistry.create(context)
|
|
58
|
-
playerId = id
|
|
59
|
-
return id
|
|
60
|
-
}
|
|
61
|
-
|
|
62
68
|
private fun detachFromSession() {
|
|
63
69
|
if (playerId == 0) return
|
|
64
70
|
val session = PlayerRegistry.session(playerId) ?: return
|
|
@@ -76,7 +82,12 @@ class ExtendedVlcPlayerViewComponentView(context: Context, appContext: AppContex
|
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
override fun onDetachedFromWindow() {
|
|
79
|
-
super.onDetachedFromWindow()
|
|
80
85
|
detachFromSession()
|
|
86
|
+
super.onDetachedFromWindow()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override fun onAttachedToWindow() {
|
|
90
|
+
super.onAttachedToWindow()
|
|
91
|
+
attachToSession()
|
|
81
92
|
}
|
|
82
93
|
}
|
|
@@ -49,9 +49,10 @@ class PlayerSession(
|
|
|
49
49
|
init {
|
|
50
50
|
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
|
51
51
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
|
52
|
-
mediaPlayer.
|
|
52
|
+
val vout = mediaPlayer.getVLCVout()
|
|
53
|
+
vout.setVideoView(surfaceView)
|
|
53
54
|
if (!isAttached) {
|
|
54
|
-
|
|
55
|
+
vout.attachViews()
|
|
55
56
|
isAttached = true
|
|
56
57
|
}
|
|
57
58
|
mediaPlayer.play()
|
|
@@ -60,36 +61,40 @@ class PlayerSession(
|
|
|
60
61
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
|
|
61
62
|
|
|
62
63
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
|
63
|
-
mediaPlayer.
|
|
64
|
+
val vout = mediaPlayer.getVLCVout()
|
|
65
|
+
if (vout.areViewsAttached()) {
|
|
66
|
+
vout.detachViews()
|
|
67
|
+
}
|
|
68
|
+
isAttached = false
|
|
64
69
|
}
|
|
65
70
|
})
|
|
66
71
|
|
|
67
72
|
mediaPlayer.setEventListener { event ->
|
|
68
73
|
when (event.type) {
|
|
69
|
-
MediaPlayer.Event.Playing -> onPlaying?.invoke(mapOf("duration" to (
|
|
74
|
+
MediaPlayer.Event.Playing -> onPlaying?.invoke(mapOf("duration" to durationSeconds()))
|
|
70
75
|
MediaPlayer.Event.Paused -> onPaused?.invoke(emptyMap())
|
|
71
76
|
MediaPlayer.Event.Stopped -> onEnded?.invoke()
|
|
72
77
|
MediaPlayer.Event.EndReached -> onEnded?.invoke()
|
|
73
78
|
MediaPlayer.Event.EncounteredError -> onError?.invoke(
|
|
74
79
|
mapOf(
|
|
75
|
-
"message" to
|
|
80
|
+
"message" to "libVLC error",
|
|
76
81
|
"code" to "VLC_ERROR",
|
|
77
82
|
"domain" to "libVLC"
|
|
78
83
|
)
|
|
79
84
|
)
|
|
80
|
-
MediaPlayer.Event.Buffering -> onBuffering?.invoke(mapOf("isBuffering" to event.
|
|
85
|
+
MediaPlayer.Event.Buffering -> onBuffering?.invoke(mapOf("isBuffering" to (event.getBuffering() < 100.0f)))
|
|
81
86
|
MediaPlayer.Event.Opening -> onLoad?.invoke(
|
|
82
87
|
mapOf(
|
|
83
|
-
"duration" to (
|
|
88
|
+
"duration" to durationSeconds(),
|
|
84
89
|
"audioTracks" to emptyList<Map<String, Any>>(),
|
|
85
90
|
"textTracks" to emptyList<Map<String, Any>>()
|
|
86
91
|
)
|
|
87
92
|
)
|
|
88
93
|
MediaPlayer.Event.TimeChanged -> onProgress?.invoke(
|
|
89
94
|
mapOf(
|
|
90
|
-
"currentTime" to (event.
|
|
91
|
-
"duration" to (
|
|
92
|
-
"position" to (if (
|
|
95
|
+
"currentTime" to (event.getTimeChanged() / 1000.0),
|
|
96
|
+
"duration" to durationSeconds(),
|
|
97
|
+
"position" to (if (mediaPlayer.getLength() > 0) event.getTimeChanged().toDouble() / mediaPlayer.getLength() else 0.0)
|
|
93
98
|
)
|
|
94
99
|
)
|
|
95
100
|
}
|
|
@@ -101,8 +106,11 @@ class PlayerSession(
|
|
|
101
106
|
val media = Media(libVlc, Uri.parse(uri))
|
|
102
107
|
currentMedia = media
|
|
103
108
|
mediaPlayer.media = media
|
|
109
|
+
mediaPlayer.play()
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
private fun durationSeconds(): Double = mediaPlayer.getLength() / 1000.0
|
|
113
|
+
|
|
106
114
|
fun play() = mediaPlayer.play()
|
|
107
115
|
fun pause() = mediaPlayer.pause()
|
|
108
116
|
fun stop() = mediaPlayer.stop()
|
|
@@ -158,11 +166,12 @@ class PlayerSession(
|
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
fun release() {
|
|
169
|
+
val vout = mediaPlayer.getVLCVout()
|
|
170
|
+
if (vout.areViewsAttached()) {
|
|
171
|
+
vout.detachViews()
|
|
172
|
+
}
|
|
161
173
|
mediaPlayer.stop()
|
|
162
174
|
currentMedia?.release()
|
|
163
175
|
libVlc.release()
|
|
164
176
|
}
|
|
165
177
|
}
|
|
166
|
-
|
|
167
|
-
// Helper to convert Kotlin Array to Java Array (libVLC's attachViews is Java).
|
|
168
|
-
private inline fun <reified T> Array<out T>.toJavaArray(): Array<T> = this as Array<T>
|
package/app.plugin.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Plugin options (all optional):
|
|
23
23
|
* {
|
|
24
24
|
* ios: { mobileVlcKitVersion: '3.7.3', enableBitcode: false },
|
|
25
|
-
* android: { libVlcVersion: '3.
|
|
25
|
+
* android: { libVlcVersion: '3.7.5' },
|
|
26
26
|
* pip: { snapshotFps: 30, snapshotQuality: 'medium' },
|
|
27
27
|
* }
|
|
28
28
|
*/
|
|
@@ -65,7 +65,7 @@ function withIosPods(config, options) {
|
|
|
65
65
|
// ---- iOS Info.plist -------------------------------------------------------
|
|
66
66
|
|
|
67
67
|
const INFOPLIST_TAG = 'extended-vlc-player-info-plist';
|
|
68
|
-
const INFOPLIST_AUDIO_SESSION = '
|
|
68
|
+
const INFOPLIST_AUDIO_SESSION = '// extended-vlc-player: keep audio session active for PiP and background playback';
|
|
69
69
|
|
|
70
70
|
function withIosInfoPlist(config) {
|
|
71
71
|
return withInfoPlist(config, (infoPlist) => {
|
|
@@ -80,10 +80,10 @@ function withIosInfoPlist(config) {
|
|
|
80
80
|
// ---- Android build.gradle ------------------------------------------------
|
|
81
81
|
|
|
82
82
|
const GRADLE_TAG = 'extended-vlc-player-gradle';
|
|
83
|
-
const GRADLE_VLC = (vlcVersion) => ` implementation "org.videolan.android:libvlc:${vlcVersion}"`;
|
|
83
|
+
const GRADLE_VLC = (vlcVersion) => ` implementation "org.videolan.android:libvlc-all:${vlcVersion}"`;
|
|
84
84
|
|
|
85
85
|
function withAndroidGradle(config, options) {
|
|
86
|
-
const vlcVersion = (options?.android?.libVlcVersion || '3.
|
|
86
|
+
const vlcVersion = (options?.android?.libVlcVersion || '3.7.5').toString();
|
|
87
87
|
return withAppBuildGradle(config, (gradleConfig) => {
|
|
88
88
|
if (gradleConfig.modResults.language !== 'groovy') {
|
|
89
89
|
// The main app module is Groovy in this Expo template (verified in
|
|
@@ -93,15 +93,30 @@ function withAndroidGradle(config, options) {
|
|
|
93
93
|
'[extended-vlc-player] Android build.gradle is not Groovy. Update the plugin to handle the new DSL.'
|
|
94
94
|
);
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
|
|
97
|
+
const currentContents = gradleConfig.modResults.contents;
|
|
98
|
+
const existingBlock = new RegExp(
|
|
99
|
+
`(// @generated begin ${GRADLE_TAG}\\b[\\s\\S]*?implementation\\s+[\"']org\\.videolan\\.android:libvlc(?:-all)?:)([^\"']+)([\"'])`,
|
|
100
|
+
'm'
|
|
101
|
+
);
|
|
102
|
+
const updatedContents = currentContents.replace(
|
|
103
|
+
existingBlock,
|
|
104
|
+
(_match, prefix, _previousVersion, closingQuote) => `${prefix}${vlcVersion}${closingQuote}`
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (updatedContents !== currentContents) {
|
|
108
|
+
gradleConfig.modResults.contents = updatedContents;
|
|
109
|
+
return gradleConfig;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
gradleConfig.modResults.contents = mergeContents({
|
|
97
113
|
tag: GRADLE_TAG,
|
|
98
|
-
src:
|
|
114
|
+
src: currentContents,
|
|
99
115
|
newSrc: `\n${INFOPLIST_AUDIO_SESSION}\ndependencies {\n${GRADLE_VLC(vlcVersion)}\n}\n`,
|
|
100
116
|
anchor: /^android\s*\{/m,
|
|
101
117
|
offset: 1,
|
|
102
118
|
comment: '//',
|
|
103
119
|
}).contents;
|
|
104
|
-
gradleConfig.modResults.contents = newSrc;
|
|
105
120
|
return gradleConfig;
|
|
106
121
|
});
|
|
107
122
|
}
|
|
@@ -5,8 +5,8 @@ export interface UseExtendedVlcPlayerOptions {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Returns a stable player object for the given source. The native player
|
|
8
|
-
* is created once
|
|
9
|
-
*
|
|
8
|
+
* is created once on mount and replaced in place when `source` changes via
|
|
9
|
+
* the same `replace` API used by `expo-video`.
|
|
10
10
|
*/
|
|
11
11
|
export declare function useExtendedVlcPlayer(source: ExtendedVlcSource, options?: UseExtendedVlcPlayerOptions): ExtendedVlcPlayer;
|
|
12
12
|
//# sourceMappingURL=useExtendedVlcPlayer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExtendedVlcPlayer.d.ts","sourceRoot":"","sources":["../src/useExtendedVlcPlayer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"useExtendedVlcPlayer.d.ts","sourceRoot":"","sources":["../src/useExtendedVlcPlayer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAqCpE,MAAM,WAAW,2BAA2B;IAC1C,sFAAsF;IACtF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,2BAAgC,GACxC,iBAAiB,CA0GnB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { requireNativeModule } from 'expo-modules-core';
|
|
3
3
|
// Native module is registered by the platform-specific module under the
|
|
4
4
|
// JS module name "ExtendedVlcPlayer". The requireNativeModule helper resolves
|
|
@@ -34,8 +34,8 @@ function normalizeSource(source) {
|
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Returns a stable player object for the given source. The native player
|
|
37
|
-
* is created once
|
|
38
|
-
*
|
|
37
|
+
* is created once on mount and replaced in place when `source` changes via
|
|
38
|
+
* the same `replace` API used by `expo-video`.
|
|
39
39
|
*/
|
|
40
40
|
export function useExtendedVlcPlayer(source, options = {}) {
|
|
41
41
|
const normalized = useMemo(() => normalizeSource(source), [source]);
|
|
@@ -53,12 +53,36 @@ export function useExtendedVlcPlayer(source, options = {}) {
|
|
|
53
53
|
isMountedRef.current = false;
|
|
54
54
|
};
|
|
55
55
|
}, []);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
let createdId = 0;
|
|
58
|
+
try {
|
|
59
|
+
createdId = getModule().createPlayer();
|
|
60
|
+
setNativeId(createdId);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.warn('[extended-vlc-player] player creation failed:', error);
|
|
65
|
+
}
|
|
66
|
+
return () => {
|
|
67
|
+
if (createdId <= 0)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
getModule().destroyPlayer(createdId);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
// eslint-disable-next-line no-console
|
|
74
|
+
console.warn('[extended-vlc-player] player cleanup failed:', error);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}, []);
|
|
56
78
|
useEffect(() => {
|
|
57
79
|
// When the source prop changes, ask the native module to swap in place
|
|
58
80
|
// instead of remounting the view (which would reset audio session, PiP
|
|
59
81
|
// delegate wiring, etc.).
|
|
82
|
+
if (nativeId <= 0)
|
|
83
|
+
return;
|
|
60
84
|
try {
|
|
61
|
-
getModule().replace(normalized);
|
|
85
|
+
getModule().replace(nativeId, normalized);
|
|
62
86
|
}
|
|
63
87
|
catch (error) {
|
|
64
88
|
// Surface as console error; the player view will also emit onError
|
|
@@ -66,7 +90,7 @@ export function useExtendedVlcPlayer(source, options = {}) {
|
|
|
66
90
|
// eslint-disable-next-line no-console
|
|
67
91
|
console.warn('[extended-vlc-player] replace failed:', error);
|
|
68
92
|
}
|
|
69
|
-
}, [normalized]);
|
|
93
|
+
}, [nativeId, normalized]);
|
|
70
94
|
// The player object is intentionally stable across renders. Methods are
|
|
71
95
|
// closures over `nativeId` so the latest normalized source is always
|
|
72
96
|
// referenced, but the object identity does not change — consumers can
|
|
@@ -85,12 +109,7 @@ export function useExtendedVlcPlayer(source, options = {}) {
|
|
|
85
109
|
replace: (next) => {
|
|
86
110
|
const nextNormalized = normalizeSource(next);
|
|
87
111
|
try {
|
|
88
|
-
getModule().replace(
|
|
89
|
-
...nextNormalized,
|
|
90
|
-
// Tag the replace with the current nativeId so the existing
|
|
91
|
-
// instance is updated rather than a new one allocated.
|
|
92
|
-
instanceId: nativeId,
|
|
93
|
-
});
|
|
112
|
+
getModule().replace(nativeId, nextNormalized);
|
|
94
113
|
}
|
|
95
114
|
catch (error) {
|
|
96
115
|
// eslint-disable-next-line no-console
|
|
@@ -118,20 +137,9 @@ export function useExtendedVlcPlayer(source, options = {}) {
|
|
|
118
137
|
}
|
|
119
138
|
}
|
|
120
139
|
}, [player, options]);
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
// avoid an extra render.
|
|
124
|
-
const firstRef = useRef(true);
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
if (firstRef.current) {
|
|
127
|
-
firstRef.current = false;
|
|
128
|
-
setNativeId(1);
|
|
129
|
-
}
|
|
130
|
-
}, []);
|
|
131
|
-
// Mark unused variables to keep TypeScript happy with strict checks.
|
|
132
|
-
void useCallback;
|
|
140
|
+
// Keep the ref in the hook for consumers that unmount while a native call
|
|
141
|
+
// is in flight; the native registry owns the actual session lifetime.
|
|
133
142
|
void isMountedRef;
|
|
134
|
-
void undefined;
|
|
135
143
|
return player;
|
|
136
144
|
}
|
|
137
145
|
//# sourceMappingURL=useExtendedVlcPlayer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExtendedVlcPlayer.js","sourceRoot":"","sources":["../src/useExtendedVlcPlayer.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { requireNativeModule } from 'expo-modules-core';\n\nimport type {\n ExtendedVlcLoadEvent,\n ExtendedVlcPlayer,\n ExtendedVlcProgressEvent,\n ExtendedVlcSource,\n} from './types';\n\n// Native module is registered by the platform-specific module under the\n// JS module name \"ExtendedVlcPlayer\". The requireNativeModule helper resolves\n// it at runtime; if the native side is missing, accessing the module throws\n// — we surface a clear error so the consumer knows what failed instead of\n// silently producing a no-op player.\nlet nativeModule: any = null;\ntry {\n nativeModule = requireNativeModule('ExtendedVlcPlayer');\n} catch (error) {\n // Allow JS-side import of the module to succeed even when the native side\n // is not yet built (e.g. in a Jest test environment). We throw a helpful\n // error only when the consumer actually calls a method.\n nativeModule = null;\n}\n\nfunction getModule() {\n if (!nativeModule) {\n throw new Error(\n '[extended-vlc-player] Native module is not available. ' +\n 'Run `npx expo prebuild --clean` and rebuild the app, ' +\n 'or check that the plugin ran successfully.'\n );\n }\n return nativeModule;\n}\n\nfunction normalizeSource(source: ExtendedVlcSource) {\n if (typeof source === 'string') return { uri: source };\n const { uri } = source;\n if (!uri) {\n throw new Error('[extended-vlc-player] source.uri is required');\n }\n return { uri, headers: source.headers ?? null, drm: source.drm ?? null };\n}\n\nexport interface UseExtendedVlcPlayerOptions {\n /** Called once after the player is constructed. Useful for attaching PiP handlers. */\n onReady?: (player: ExtendedVlcPlayer) => void;\n}\n\n/**\n * Returns a stable player object for the given source. The native player\n * is created once (lazy on first method call) and replaced in place when\n * `source` changes via the same `replace` API used by `expo-video`.\n */\nexport function useExtendedVlcPlayer(\n source: ExtendedVlcSource,\n options: UseExtendedVlcPlayerOptions = {}\n): ExtendedVlcPlayer {\n const normalized = useMemo(() => normalizeSource(source), [source]);\n // We hold a numeric id of the \"current native source\" so we can detect\n // when the JS source prop has changed and tell the native module to swap.\n const [nativeId, setNativeId] = useState(0);\n const isMountedRef = useRef(true);\n\n // Event subscription is wired through the Fabric view component, not the\n // module — events flow back into the JS player object via the\n // ExtendedVlcPlayerView's callbacks. We keep the module call surface\n // (play/pause/seek/PiP) here.\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n // When the source prop changes, ask the native module to swap in place\n // instead of remounting the view (which would reset audio session, PiP\n // delegate wiring, etc.).\n try {\n getModule().replace(normalized);\n } catch (error) {\n // Surface as console error; the player view will also emit onError\n // once the Fabric event dispatcher picks it up.\n // eslint-disable-next-line no-console\n console.warn('[extended-vlc-player] replace failed:', error);\n }\n }, [normalized]);\n\n // The player object is intentionally stable across renders. Methods are\n // closures over `nativeId` so the latest normalized source is always\n // referenced, but the object identity does not change — consumers can\n // safely put it in dependency arrays.\n const player = useMemo<ExtendedVlcPlayer>(() => {\n const p: ExtendedVlcPlayer & { _nativeId: number } = {\n _nativeId: nativeId,\n play: () => getModule().play(nativeId),\n pause: () => getModule().pause(nativeId),\n stop: () => getModule().stop(nativeId),\n seek: (seconds: number) => getModule().seek(nativeId, seconds),\n setRate: (rate: number) => getModule().setRate(nativeId, rate),\n setVolume: (volume: number) => getModule().setVolume(nativeId, volume),\n setAudioTrack: (index: number) => getModule().setAudioTrack(nativeId, index),\n setSubtitleTrack: (index: number) => getModule().setSubtitleTrack(nativeId, index),\n replace: (next: ExtendedVlcSource) => {\n const nextNormalized = normalizeSource(next);\n try {\n getModule().replace({\n ...nextNormalized,\n // Tag the replace with the current nativeId so the existing\n // instance is updated rather than a new one allocated.\n instanceId: nativeId,\n });\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn('[extended-vlc-player] replace failed:', error);\n }\n },\n startPictureInPicture: () => getModule().startPictureInPicture(nativeId),\n stopPictureInPicture: () => getModule().stopPictureInPicture(nativeId),\n isPictureInPictureActive: () => getModule().isPictureInPictureActive(nativeId),\n isPictureInPictureSupported: () => getModule().isPictureInPictureSupported(),\n };\n return p;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [nativeId]);\n\n // Bridge the native callback API. The hook surfaces the player as soon\n // as it exists, so consumers can attach PiP handlers from `onReady`.\n useEffect(() => {\n if (options.onReady) {\n try {\n options.onReady(player);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn('[extended-vlc-player] onReady handler threw:', error);\n }\n }\n }, [player, options]);\n\n // Bump the native id once on mount so the first source prop is committed\n // to the native side. We use a ref-based \"first\" instead of an effect to\n // avoid an extra render.\n const firstRef = useRef(true);\n useEffect(() => {\n if (firstRef.current) {\n firstRef.current = false;\n setNativeId(1);\n }\n }, []);\n\n // Mark unused variables to keep TypeScript happy with strict checks.\n void useCallback;\n void isMountedRef;\n void (undefined as ExtendedVlcLoadEvent | ExtendedVlcProgressEvent | undefined);\n\n return player;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useExtendedVlcPlayer.js","sourceRoot":"","sources":["../src/useExtendedVlcPlayer.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { requireNativeModule } from 'expo-modules-core';\n\nimport type { ExtendedVlcPlayer, ExtendedVlcSource } from './types';\n\n// Native module is registered by the platform-specific module under the\n// JS module name \"ExtendedVlcPlayer\". The requireNativeModule helper resolves\n// it at runtime; if the native side is missing, accessing the module throws\n// — we surface a clear error so the consumer knows what failed instead of\n// silently producing a no-op player.\nlet nativeModule: any = null;\ntry {\n nativeModule = requireNativeModule('ExtendedVlcPlayer');\n} catch (error) {\n // Allow JS-side import of the module to succeed even when the native side\n // is not yet built (e.g. in a Jest test environment). We throw a helpful\n // error only when the consumer actually calls a method.\n nativeModule = null;\n}\n\nfunction getModule() {\n if (!nativeModule) {\n throw new Error(\n '[extended-vlc-player] Native module is not available. ' +\n 'Run `npx expo prebuild --clean` and rebuild the app, ' +\n 'or check that the plugin ran successfully.'\n );\n }\n return nativeModule;\n}\n\nfunction normalizeSource(source: ExtendedVlcSource) {\n if (typeof source === 'string') return { uri: source };\n const { uri } = source;\n if (!uri) {\n throw new Error('[extended-vlc-player] source.uri is required');\n }\n return { uri, headers: source.headers ?? null, drm: source.drm ?? null };\n}\n\nexport interface UseExtendedVlcPlayerOptions {\n /** Called once after the player is constructed. Useful for attaching PiP handlers. */\n onReady?: (player: ExtendedVlcPlayer) => void;\n}\n\n/**\n * Returns a stable player object for the given source. The native player\n * is created once on mount and replaced in place when `source` changes via\n * the same `replace` API used by `expo-video`.\n */\nexport function useExtendedVlcPlayer(\n source: ExtendedVlcSource,\n options: UseExtendedVlcPlayerOptions = {}\n): ExtendedVlcPlayer {\n const normalized = useMemo(() => normalizeSource(source), [source]);\n // We hold a numeric id of the \"current native source\" so we can detect\n // when the JS source prop has changed and tell the native module to swap.\n const [nativeId, setNativeId] = useState(0);\n const isMountedRef = useRef(true);\n\n // Event subscription is wired through the Fabric view component, not the\n // module — events flow back into the JS player object via the\n // ExtendedVlcPlayerView's callbacks. We keep the module call surface\n // (play/pause/seek/PiP) here.\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n let createdId = 0;\n try {\n createdId = getModule().createPlayer();\n setNativeId(createdId);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn('[extended-vlc-player] player creation failed:', error);\n }\n\n return () => {\n if (createdId <= 0) return;\n try {\n getModule().destroyPlayer(createdId);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn('[extended-vlc-player] player cleanup failed:', error);\n }\n };\n }, []);\n\n useEffect(() => {\n // When the source prop changes, ask the native module to swap in place\n // instead of remounting the view (which would reset audio session, PiP\n // delegate wiring, etc.).\n if (nativeId <= 0) return;\n try {\n getModule().replace(nativeId, normalized);\n } catch (error) {\n // Surface as console error; the player view will also emit onError\n // once the Fabric event dispatcher picks it up.\n // eslint-disable-next-line no-console\n console.warn('[extended-vlc-player] replace failed:', error);\n }\n }, [nativeId, normalized]);\n\n // The player object is intentionally stable across renders. Methods are\n // closures over `nativeId` so the latest normalized source is always\n // referenced, but the object identity does not change — consumers can\n // safely put it in dependency arrays.\n const player = useMemo<ExtendedVlcPlayer>(() => {\n const p: ExtendedVlcPlayer & { _nativeId: number } = {\n _nativeId: nativeId,\n play: () => getModule().play(nativeId),\n pause: () => getModule().pause(nativeId),\n stop: () => getModule().stop(nativeId),\n seek: (seconds: number) => getModule().seek(nativeId, seconds),\n setRate: (rate: number) => getModule().setRate(nativeId, rate),\n setVolume: (volume: number) => getModule().setVolume(nativeId, volume),\n setAudioTrack: (index: number) => getModule().setAudioTrack(nativeId, index),\n setSubtitleTrack: (index: number) => getModule().setSubtitleTrack(nativeId, index),\n replace: (next: ExtendedVlcSource) => {\n const nextNormalized = normalizeSource(next);\n try {\n getModule().replace(nativeId, nextNormalized);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn('[extended-vlc-player] replace failed:', error);\n }\n },\n startPictureInPicture: () => getModule().startPictureInPicture(nativeId),\n stopPictureInPicture: () => getModule().stopPictureInPicture(nativeId),\n isPictureInPictureActive: () => getModule().isPictureInPictureActive(nativeId),\n isPictureInPictureSupported: () => getModule().isPictureInPictureSupported(),\n };\n return p;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [nativeId]);\n\n // Bridge the native callback API. The hook surfaces the player as soon\n // as it exists, so consumers can attach PiP handlers from `onReady`.\n useEffect(() => {\n if (options.onReady) {\n try {\n options.onReady(player);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn('[extended-vlc-player] onReady handler threw:', error);\n }\n }\n }, [player, options]);\n\n // Keep the ref in the hook for consumers that unmount while a native call\n // is in flight; the native registry owns the actual session lifetime.\n void isMountedRef;\n\n return player;\n}\n"]}
|
|
@@ -24,6 +24,8 @@ Pod::Spec.new do |s|
|
|
|
24
24
|
s.source_files = '**/*.{h,m,mm,swift}'
|
|
25
25
|
s.pod_target_xcconfig = {
|
|
26
26
|
'DEFINES_MODULE' => 'YES',
|
|
27
|
+
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20',
|
|
28
|
+
'CLANG_CXX_LIBRARY' => 'libc++',
|
|
27
29
|
'SWIFT_OBJC_INTERFACE_HEADER_NAME' => 'ExtendedVlcPlayer-Swift.h',
|
|
28
30
|
'OTHER_SWIFT_FLAGS' => '$(inherited) -D COCOAPODS -Xfrontend -module-name -Xfrontend ExtendedVlcPlayer',
|
|
29
31
|
# The host app's Podfile already enables use_frameworks! :linkage => :static
|
|
@@ -19,6 +19,14 @@ public final class ExtendedVlcPlayerModule: Module {
|
|
|
19
19
|
AudioSessionConfigurator.configureForPlayback()
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
Function("createPlayer") { () -> Int in
|
|
23
|
+
PlayerRegistry.shared.next().0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Function("destroyPlayer") { (instanceId: Int) in
|
|
27
|
+
PlayerRegistry.shared.remove(id: instanceId)
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
AsyncFunction("isPictureInPictureSupported") { () -> Bool in
|
|
23
31
|
AVPictureInPictureController.isPictureInPictureSupported()
|
|
24
32
|
}
|
|
@@ -33,6 +33,19 @@ RCT_EXPORT_VIEW_PROPERTY(onBuffering, RCTBubblingEventBlock)
|
|
|
33
33
|
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStart, RCTBubblingEventBlock)
|
|
34
34
|
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStop, RCTBubblingEventBlock)
|
|
35
35
|
|
|
36
|
+
- (void)setPlayer:(NSNumber *)player
|
|
37
|
+
{
|
|
38
|
+
NSNumber *nextId = player ?: @0;
|
|
39
|
+
if (self.playerId != nil && [self.playerId isEqualToNumber:nextId]) {
|
|
40
|
+
[self attachSessionIfPossible];
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[self detachSession];
|
|
45
|
+
self.playerId = nextId;
|
|
46
|
+
[self attachSessionIfPossible];
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
37
50
|
{
|
|
38
51
|
return [RCTViewComponentView new];
|
|
@@ -49,23 +62,47 @@ RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStop, RCTBubblingEventBlock)
|
|
|
49
62
|
- (void)didMoveToWindow
|
|
50
63
|
{
|
|
51
64
|
[super didMoveToWindow];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
session.drawable.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
60
|
-
[self.contentView addSubview:session.drawable];
|
|
61
|
-
[EXVLCPlayerRegistryBridge attachEventSinks:newId view:self];
|
|
62
|
-
}
|
|
65
|
+
[self attachSessionIfPossible];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
- (void)attachSessionIfPossible
|
|
69
|
+
{
|
|
70
|
+
if (self.window == nil || self.playerId == nil || self.playerId.intValue == 0) {
|
|
71
|
+
return;
|
|
63
72
|
}
|
|
73
|
+
|
|
74
|
+
NSValue *boxed = [EXVLCPlayerRegistryBridge sessionForId:self.playerId];
|
|
75
|
+
PlayerSession *session = (PlayerSession *)[boxed nonretainedObjectValue];
|
|
76
|
+
if (session == nil) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (session.drawable.superview != self.contentView) {
|
|
81
|
+
session.drawable.frame = self.contentView.bounds;
|
|
82
|
+
session.drawable.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
83
|
+
[self.contentView addSubview:session.drawable];
|
|
84
|
+
}
|
|
85
|
+
[EXVLCPlayerRegistryBridge attachEventSinks:self.playerId view:self];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
- (void)detachSession
|
|
89
|
+
{
|
|
90
|
+
if (self.playerId == nil || self.playerId.intValue == 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
NSValue *boxed = [EXVLCPlayerRegistryBridge sessionForId:self.playerId];
|
|
95
|
+
PlayerSession *session = (PlayerSession *)[boxed nonretainedObjectValue];
|
|
96
|
+
if (session != nil && session.drawable.superview == self.contentView) {
|
|
97
|
+
[session.drawable removeFromSuperview];
|
|
98
|
+
}
|
|
99
|
+
[EXVLCPlayerRegistryBridge detachEventSinks:self.playerId];
|
|
64
100
|
}
|
|
65
101
|
|
|
66
102
|
- (void)dealloc
|
|
67
103
|
{
|
|
68
|
-
|
|
104
|
+
[self detachSession];
|
|
105
|
+
if (self.playerId != nil && self.playerId.intValue != 0) {
|
|
69
106
|
[EXVLCPlayerRegistryBridge destroySession:self.playerId];
|
|
70
107
|
}
|
|
71
108
|
}
|
|
@@ -56,6 +56,19 @@ final class PlayerRegistryBridge: NSObject {
|
|
|
56
56
|
(view as? ExtendedVlcPlayerViewEventReceiver)?.exvlcEmit("onPictureInPictureStop", [:])
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
@objc static func detachEventSinks(_ id: NSNumber) {
|
|
61
|
+
guard let session = PlayerRegistry.shared.session(for: id.intValue) else { return }
|
|
62
|
+
session.onLoad = nil
|
|
63
|
+
session.onProgress = nil
|
|
64
|
+
session.onPlaying = nil
|
|
65
|
+
session.onPaused = nil
|
|
66
|
+
session.onEnded = nil
|
|
67
|
+
session.onError = nil
|
|
68
|
+
session.onBuffering = nil
|
|
69
|
+
session.onPictureInPictureStart = nil
|
|
70
|
+
session.onPictureInPictureStop = nil
|
|
71
|
+
}
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
/// Implemented by the Fabric component view (in Obj-C++) so that the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "extended-vlc-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "React Native video player built on MobileVLCKit (iOS) and libVLC (Android) with true iOS Picture-in-Picture via AVSampleBufferDisplayLayer bridge.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { requireNativeModule } from 'expo-modules-core';
|
|
3
3
|
|
|
4
|
-
import type {
|
|
5
|
-
ExtendedVlcLoadEvent,
|
|
6
|
-
ExtendedVlcPlayer,
|
|
7
|
-
ExtendedVlcProgressEvent,
|
|
8
|
-
ExtendedVlcSource,
|
|
9
|
-
} from './types';
|
|
4
|
+
import type { ExtendedVlcPlayer, ExtendedVlcSource } from './types';
|
|
10
5
|
|
|
11
6
|
// Native module is registered by the platform-specific module under the
|
|
12
7
|
// JS module name "ExtendedVlcPlayer". The requireNativeModule helper resolves
|
|
@@ -50,8 +45,8 @@ export interface UseExtendedVlcPlayerOptions {
|
|
|
50
45
|
|
|
51
46
|
/**
|
|
52
47
|
* Returns a stable player object for the given source. The native player
|
|
53
|
-
* is created once
|
|
54
|
-
*
|
|
48
|
+
* is created once on mount and replaced in place when `source` changes via
|
|
49
|
+
* the same `replace` API used by `expo-video`.
|
|
55
50
|
*/
|
|
56
51
|
export function useExtendedVlcPlayer(
|
|
57
52
|
source: ExtendedVlcSource,
|
|
@@ -75,19 +70,41 @@ export function useExtendedVlcPlayer(
|
|
|
75
70
|
};
|
|
76
71
|
}, []);
|
|
77
72
|
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
let createdId = 0;
|
|
75
|
+
try {
|
|
76
|
+
createdId = getModule().createPlayer();
|
|
77
|
+
setNativeId(createdId);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.warn('[extended-vlc-player] player creation failed:', error);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
if (createdId <= 0) return;
|
|
85
|
+
try {
|
|
86
|
+
getModule().destroyPlayer(createdId);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.warn('[extended-vlc-player] player cleanup failed:', error);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
78
94
|
useEffect(() => {
|
|
79
95
|
// When the source prop changes, ask the native module to swap in place
|
|
80
96
|
// instead of remounting the view (which would reset audio session, PiP
|
|
81
97
|
// delegate wiring, etc.).
|
|
98
|
+
if (nativeId <= 0) return;
|
|
82
99
|
try {
|
|
83
|
-
getModule().replace(normalized);
|
|
100
|
+
getModule().replace(nativeId, normalized);
|
|
84
101
|
} catch (error) {
|
|
85
102
|
// Surface as console error; the player view will also emit onError
|
|
86
103
|
// once the Fabric event dispatcher picks it up.
|
|
87
104
|
// eslint-disable-next-line no-console
|
|
88
105
|
console.warn('[extended-vlc-player] replace failed:', error);
|
|
89
106
|
}
|
|
90
|
-
}, [normalized]);
|
|
107
|
+
}, [nativeId, normalized]);
|
|
91
108
|
|
|
92
109
|
// The player object is intentionally stable across renders. Methods are
|
|
93
110
|
// closures over `nativeId` so the latest normalized source is always
|
|
@@ -107,12 +124,7 @@ export function useExtendedVlcPlayer(
|
|
|
107
124
|
replace: (next: ExtendedVlcSource) => {
|
|
108
125
|
const nextNormalized = normalizeSource(next);
|
|
109
126
|
try {
|
|
110
|
-
getModule().replace(
|
|
111
|
-
...nextNormalized,
|
|
112
|
-
// Tag the replace with the current nativeId so the existing
|
|
113
|
-
// instance is updated rather than a new one allocated.
|
|
114
|
-
instanceId: nativeId,
|
|
115
|
-
});
|
|
127
|
+
getModule().replace(nativeId, nextNormalized);
|
|
116
128
|
} catch (error) {
|
|
117
129
|
// eslint-disable-next-line no-console
|
|
118
130
|
console.warn('[extended-vlc-player] replace failed:', error);
|
|
@@ -140,21 +152,9 @@ export function useExtendedVlcPlayer(
|
|
|
140
152
|
}
|
|
141
153
|
}, [player, options]);
|
|
142
154
|
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
// avoid an extra render.
|
|
146
|
-
const firstRef = useRef(true);
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
if (firstRef.current) {
|
|
149
|
-
firstRef.current = false;
|
|
150
|
-
setNativeId(1);
|
|
151
|
-
}
|
|
152
|
-
}, []);
|
|
153
|
-
|
|
154
|
-
// Mark unused variables to keep TypeScript happy with strict checks.
|
|
155
|
-
void useCallback;
|
|
155
|
+
// Keep the ref in the hook for consumers that unmount while a native call
|
|
156
|
+
// is in flight; the native registry owns the actual session lifetime.
|
|
156
157
|
void isMountedRef;
|
|
157
|
-
void (undefined as ExtendedVlcLoadEvent | ExtendedVlcProgressEvent | undefined);
|
|
158
158
|
|
|
159
159
|
return player;
|
|
160
160
|
}
|