@cinecrew/cinecrew-player 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 (66) hide show
  1. package/CineCrewPlayer.podspec +22 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +6 -0
  4. package/README.md +574 -0
  5. package/android/build.gradle +48 -0
  6. package/android/proguard-rules.pro +29 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/java/com/yuanzhou/vlc/ReactVlcPlayerPackage.java +28 -0
  9. package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/ReactVlcPlayerView.java +988 -0
  10. package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/ReactVlcPlayerViewManager.java +208 -0
  11. package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/VideoEventEmitter.java +136 -0
  12. package/android/src/main/res/values/strings.xml +3 -0
  13. package/app.plugin.cjs +1 -0
  14. package/assets/cinecrew-player-logo.svg +44 -0
  15. package/package.json +132 -0
  16. package/packages/react-native-vlc-media-player/LICENSE +21 -0
  17. package/packages/react-native-vlc-media-player/VLCPlayer.js +207 -0
  18. package/packages/react-native-vlc-media-player/expo/android/withGradleTasks.cjs +49 -0
  19. package/packages/react-native-vlc-media-player/expo/ios/withMobileVlcKit.cjs +28 -0
  20. package/packages/react-native-vlc-media-player/expo/withVlcMediaPlayer.cjs +10 -0
  21. package/packages/react-native-vlc-media-player/index.d.ts +354 -0
  22. package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayer.h +36 -0
  23. package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayer.m +635 -0
  24. package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayerManager.h +5 -0
  25. package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayerManager.m +100 -0
  26. package/src/native/InlineLivePlayer.js +321 -0
  27. package/src/native/MediaPlayerView.js +3093 -0
  28. package/src/native/customization.js +44 -0
  29. package/src/native/index.js +107 -0
  30. package/src/native/media/ElectronVideoPlayer.js +288 -0
  31. package/src/native/media/EmojiPickerModal.js +30 -0
  32. package/src/native/media/LiveChatDrawer.js +1177 -0
  33. package/src/native/media/LiveRecordingOverlay.js +319 -0
  34. package/src/native/media/WebVideoPlayer.js +320 -0
  35. package/src/native/media/YouTubeVideoPlayer.native.js +120 -0
  36. package/src/native/media/YouTubeVideoPlayer.web.js +169 -0
  37. package/src/native/media/player/AudioOnlyView.js +337 -0
  38. package/src/native/media/player/CenterControls.js +94 -0
  39. package/src/native/media/player/ExoVideoFallback.js +329 -0
  40. package/src/native/media/player/PipOverlay.js +1 -0
  41. package/src/native/media/player/PlayerBottomBar.js +295 -0
  42. package/src/native/media/player/PlayerTopBar.js +255 -0
  43. package/src/native/media/player/VLCBoundary.js +41 -0
  44. package/src/native/media/player/VerticalIndicator.js +184 -0
  45. package/src/native/media/player/index.js +9 -0
  46. package/src/native/media/player/playerConstants.js +159 -0
  47. package/src/native/media/web/useWebAc3AudioPlayback.native.js +6 -0
  48. package/src/native/media/web/useWebAc3AudioPlayback.web.js +219 -0
  49. package/src/native/media/web/useWebHlsPlayback.native.js +3 -0
  50. package/src/native/media/web/useWebHlsPlayback.web.js +66 -0
  51. package/src/native/media/web/useWebMediaSession.js +90 -0
  52. package/src/native/media/web/useWebMpegTsPlayback.native.js +3 -0
  53. package/src/native/media/web/useWebMpegTsPlayback.web.js +356 -0
  54. package/src/native/media/web/useWebVideoAspectRatio.js +52 -0
  55. package/src/native/media/web/webPlaybackErrors.js +4 -0
  56. package/src/utils/layoutUtils.js +12 -0
  57. package/src/utils/mediaUtils.js +7 -0
  58. package/src/utils/recording.js +8 -0
  59. package/src/utils/runtimePlatform.js +19 -0
  60. package/src/utils/sourceUtils.js +99 -0
  61. package/src/utils/youtubeHtml.js +34 -0
  62. package/src/web/index.js +1129 -0
  63. package/src/web/styles.css +378 -0
  64. package/types/index.d.ts +268 -0
  65. package/vendor/mpegts.js/LICENSE +202 -0
  66. package/vendor/mpegts.js/mpegts.js +3 -0
@@ -0,0 +1,988 @@
1
+ package com.yuanzhou.vlc.vlcplayer;
2
+
3
+ import android.annotation.SuppressLint;
4
+ import android.content.Context;
5
+ import android.graphics.Bitmap;
6
+ import android.graphics.SurfaceTexture;
7
+ import android.media.AudioManager;
8
+ import android.net.Uri;
9
+ import android.os.Handler;
10
+ import android.os.PowerManager;
11
+ import android.util.Log;
12
+ import android.view.TextureView;
13
+ import android.view.View;
14
+ import com.facebook.react.bridge.Arguments;
15
+ import com.facebook.react.bridge.LifecycleEventListener;
16
+ import com.facebook.react.bridge.ReadableArray;
17
+ import com.facebook.react.bridge.ReadableMap;
18
+ import com.facebook.react.bridge.WritableArray;
19
+ import com.facebook.react.bridge.WritableMap;
20
+ import com.facebook.react.bridge.WritableNativeArray;
21
+ import com.facebook.react.uimanager.ThemedReactContext;
22
+ import java.io.File;
23
+ import java.io.FileOutputStream;
24
+ import java.util.ArrayList;
25
+ import org.videolan.libvlc.Dialog;
26
+ import org.videolan.libvlc.LibVLC;
27
+ import org.videolan.libvlc.Media;
28
+ import org.videolan.libvlc.MediaPlayer;
29
+ import org.videolan.libvlc.interfaces.IVLCVout;
30
+
31
+ @SuppressLint("ViewConstructor")
32
+ class ReactVlcPlayerView extends TextureView
33
+ implements LifecycleEventListener,
34
+ TextureView.SurfaceTextureListener,
35
+ AudioManager.OnAudioFocusChangeListener {
36
+
37
+ private static final String TAG = "ReactVlcPlayerView";
38
+ private static final String EVENT_PROP_DURATION = "duration";
39
+ private static final String EVENT_PROP_SUCCESS = "success";
40
+ private static final String EVENT_PROP_ERROR = "error";
41
+
42
+ private final VideoEventEmitter eventEmitter;
43
+ private LibVLC libvlc;
44
+ private MediaPlayer mMediaPlayer = null;
45
+ private boolean mMuted = false;
46
+ private boolean isSurfaceViewDestory;
47
+ private String src;
48
+ private String subtitleUri;
49
+ private ReadableMap srcMap;
50
+ private int mVideoHeight = 0;
51
+ private int mVideoWidth = 0;
52
+ private int mVideoVisibleHeight = 0;
53
+ private int mVideoVisibleWidth = 0;
54
+ private int mSarNum = 0;
55
+ private int mSarDen = 0;
56
+
57
+ private boolean isPaused = true;
58
+ private boolean isHostPaused = false;
59
+ private boolean playInBackground = false;
60
+ private boolean repeatEnabled = false;
61
+ private int preVolume = 100;
62
+ private boolean autoAspectRatio = false;
63
+ private boolean acceptInvalidCertificates = false;
64
+
65
+ private float mProgressUpdateInterval = 0;
66
+ private Handler mProgressUpdateHandler = new Handler();
67
+ private Runnable mProgressUpdateRunnable = null;
68
+
69
+ private final ThemedReactContext themedReactContext;
70
+ private final AudioManager audioManager;
71
+
72
+ private String mVideoInfoHash = null;
73
+
74
+ public ReactVlcPlayerView(ThemedReactContext context) {
75
+ super(context);
76
+ this.eventEmitter = new VideoEventEmitter(context);
77
+ this.themedReactContext = context;
78
+ audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
79
+ this.setSurfaceTextureListener(this);
80
+
81
+ this.addOnLayoutChangeListener(onLayoutChangeListener);
82
+ context.addLifecycleEventListener(this);
83
+ }
84
+
85
+ @Override
86
+ public void setId(int id) {
87
+ super.setId(id);
88
+ eventEmitter.setViewId(id);
89
+ }
90
+
91
+ @Override
92
+ protected void onAttachedToWindow() {
93
+ super.onAttachedToWindow();
94
+ }
95
+
96
+ @Override
97
+ protected void onDetachedFromWindow() {
98
+ super.onDetachedFromWindow();
99
+ stopPlayback();
100
+ }
101
+
102
+ private PowerManager.WakeLock wakeLock = null;
103
+
104
+ private void acquireWakeLock() {
105
+ if (wakeLock == null) {
106
+ try {
107
+ PowerManager pm = (PowerManager) themedReactContext.getSystemService(Context.POWER_SERVICE);
108
+ if (pm != null) {
109
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CineCrew:VLCBackgroundAudio");
110
+ wakeLock.setReferenceCounted(false);
111
+ }
112
+ } catch (Exception e) {
113
+ Log.e(TAG, "Failed to create WakeLock", e);
114
+ }
115
+ }
116
+ if (wakeLock != null && !wakeLock.isHeld()) {
117
+ try {
118
+ wakeLock.acquire(3 * 60 * 60 * 1000L);
119
+ } catch (Exception e) {
120
+ Log.e(TAG, "Failed to acquire WakeLock", e);
121
+ }
122
+ }
123
+ }
124
+
125
+ private void releaseWakeLock() {
126
+ if (wakeLock != null && wakeLock.isHeld()) {
127
+ try {
128
+ wakeLock.release();
129
+ } catch (Exception e) {
130
+ Log.e(TAG, "Failed to release WakeLock", e);
131
+ }
132
+ }
133
+ }
134
+
135
+ // LifecycleEventListener implementation
136
+
137
+ @Override
138
+ public void onHostResume() {
139
+ if (mMediaPlayer == null || !isHostPaused) {
140
+ return;
141
+ }
142
+
143
+ if (playInBackground) {
144
+ if (isSurfaceViewDestory && getSurfaceTexture() != null) {
145
+ attachVideoSurface();
146
+ }
147
+ isHostPaused = false;
148
+ if (!isPaused) {
149
+ mMediaPlayer.play();
150
+ acquireWakeLock();
151
+ }
152
+ return;
153
+ }
154
+
155
+ if (isSurfaceViewDestory) {
156
+ attachVideoSurface();
157
+ isHostPaused = false;
158
+ isPaused = false;
159
+ mMediaPlayer.play();
160
+ }
161
+ }
162
+
163
+ @Override
164
+ public void onHostPause() {
165
+ if (playInBackground) {
166
+ if (!isPaused && mMediaPlayer != null) {
167
+ // Keep VLC alive for audio-only playback while Android locks
168
+ // the screen or sends the activity to the background.
169
+ isHostPaused = true;
170
+ IVLCVout vlcOut = mMediaPlayer.getVLCVout();
171
+ if (vlcOut != null && vlcOut.areViewsAttached()) {
172
+ vlcOut.detachViews();
173
+ }
174
+ acquireWakeLock();
175
+ }
176
+ return;
177
+ }
178
+
179
+ releaseWakeLock();
180
+ if (!isPaused && mMediaPlayer != null) {
181
+ isPaused = true;
182
+ isHostPaused = true;
183
+ mMediaPlayer.pause();
184
+ WritableMap map = Arguments.createMap();
185
+ map.putString("type", "Paused");
186
+ eventEmitter.onVideoStateChange(map);
187
+ }
188
+ }
189
+
190
+ @Override
191
+ public void onHostDestroy() {
192
+ releaseWakeLock();
193
+ stopPlayback();
194
+ }
195
+
196
+ // AudioManager.OnAudioFocusChangeListener implementation
197
+ @Override
198
+ public void onAudioFocusChange(int focusChange) {
199
+ // VLC owns native audio focus; the component does not apply ducking or
200
+ // pause policy from Android's focus callback.
201
+ }
202
+
203
+ private void setProgressUpdateRunnable() {
204
+ if (mMediaPlayer == null || mProgressUpdateInterval <= 0) {
205
+ return;
206
+ }
207
+
208
+ mProgressUpdateRunnable =
209
+ () -> {
210
+ if (mMediaPlayer != null && !isPaused) {
211
+ WritableMap map = Arguments.createMap();
212
+ map.putBoolean("isPlaying", mMediaPlayer.isPlaying());
213
+ map.putDouble("position", mMediaPlayer.getPosition());
214
+ map.putDouble("currentTime", mMediaPlayer.getTime());
215
+ map.putDouble(EVENT_PROP_DURATION, mMediaPlayer.getLength());
216
+ updateVideoInfo();
217
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_PROGRESS);
218
+ }
219
+
220
+ mProgressUpdateHandler.postDelayed(
221
+ mProgressUpdateRunnable, Math.round(mProgressUpdateInterval));
222
+ };
223
+ mProgressUpdateHandler.postDelayed(mProgressUpdateRunnable, 0);
224
+ }
225
+
226
+ /*************
227
+ * Events Listener
228
+ *************/
229
+
230
+ private View.OnLayoutChangeListener onLayoutChangeListener =
231
+ new View.OnLayoutChangeListener() {
232
+
233
+ @Override
234
+ public void onLayoutChange(
235
+ View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
236
+ if (view.getWidth() > 0 && view.getHeight() > 0) {
237
+ mVideoWidth = view.getWidth(); // 获取宽度
238
+ mVideoHeight = view.getHeight(); // 获取高度
239
+ if (mMediaPlayer != null) {
240
+ IVLCVout vlcOut = mMediaPlayer.getVLCVout();
241
+ vlcOut.setWindowSize(mVideoWidth, mVideoHeight);
242
+ if (autoAspectRatio) {
243
+ mMediaPlayer.setAspectRatio(mVideoWidth + ":" + mVideoHeight);
244
+ }
245
+ }
246
+ }
247
+ }
248
+ };
249
+
250
+ /** 播放过程中的时间事件监听 */
251
+ private MediaPlayer.EventListener mPlayerListener =
252
+ new MediaPlayer.EventListener() {
253
+ long currentTime = 0;
254
+ long totalLength = 0;
255
+
256
+ @Override
257
+ public void onEvent(MediaPlayer.Event event) {
258
+ boolean isPlaying = mMediaPlayer.isPlaying();
259
+ currentTime = mMediaPlayer.getTime();
260
+ float position = mMediaPlayer.getPosition();
261
+ totalLength = mMediaPlayer.getLength();
262
+ WritableMap map = Arguments.createMap();
263
+ map.putBoolean("isPlaying", isPlaying);
264
+ map.putDouble("position", position);
265
+ map.putDouble("currentTime", currentTime);
266
+ map.putDouble(EVENT_PROP_DURATION, totalLength);
267
+
268
+ switch (event.type) {
269
+ case MediaPlayer.Event.EndReached:
270
+ map.putString("type", "Ended");
271
+ if (repeatEnabled) {
272
+ mMediaPlayer.setTime(0);
273
+ mMediaPlayer.play();
274
+ } else {
275
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_END);
276
+ }
277
+ break;
278
+ case MediaPlayer.Event.Playing:
279
+ map.putString("type", "Playing");
280
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_ON_IS_PLAYING);
281
+ break;
282
+ case MediaPlayer.Event.Opening:
283
+ map.putString("type", "Opening");
284
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_ON_OPEN);
285
+ break;
286
+ case MediaPlayer.Event.Paused:
287
+ map.putString("type", "Paused");
288
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_ON_PAUSED);
289
+ break;
290
+ case MediaPlayer.Event.Buffering:
291
+ map.putDouble("bufferRate", event.getBuffering());
292
+ map.putString("type", "Buffering");
293
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_ON_VIDEO_BUFFERING);
294
+ break;
295
+ case MediaPlayer.Event.Stopped:
296
+ map.putString("type", "Stopped");
297
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_ON_VIDEO_STOPPED);
298
+ break;
299
+ case MediaPlayer.Event.EncounteredError:
300
+ map.putString("type", "Error");
301
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_ON_ERROR);
302
+
303
+ break;
304
+ case MediaPlayer.Event.TimeChanged:
305
+ map.putString("type", "TimeChanged");
306
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_SEEK);
307
+ break;
308
+ case MediaPlayer.Event.RecordChanged:
309
+ map.putString("type", "RecordingPath");
310
+ map.putBoolean("isRecording", event.getRecording());
311
+ // Record started emits and event with the record path (but no file).
312
+ // Only want to emit when recording has stopped and the recording is created.
313
+ if (!event.getRecording() && event.getRecordPath() != null) {
314
+ map.putString("recordPath", event.getRecordPath());
315
+ }
316
+ eventEmitter.sendEvent(map, VideoEventEmitter.EVENT_RECORDING_STATE);
317
+ break;
318
+ default:
319
+ map.putString("type", event.type + "");
320
+ eventEmitter.onVideoStateChange(map);
321
+ break;
322
+ }
323
+ }
324
+ };
325
+
326
+ private IVLCVout.OnNewVideoLayoutListener onNewVideoLayoutListener =
327
+ new IVLCVout.OnNewVideoLayoutListener() {
328
+ @Override
329
+ public void onNewVideoLayout(
330
+ IVLCVout vout,
331
+ int width,
332
+ int height,
333
+ int visibleWidth,
334
+ int visibleHeight,
335
+ int sarNum,
336
+ int sarDen) {
337
+ if (width * height == 0) {
338
+ return;
339
+ }
340
+ // store video size
341
+ mVideoWidth = width;
342
+ mVideoHeight = height;
343
+ mVideoVisibleWidth = visibleWidth;
344
+ mVideoVisibleHeight = visibleHeight;
345
+ mSarNum = sarNum;
346
+ mSarDen = sarDen;
347
+ WritableMap map = Arguments.createMap();
348
+ map.putInt("mVideoWidth", mVideoWidth);
349
+ map.putInt("mVideoHeight", mVideoHeight);
350
+ map.putInt("mVideoVisibleWidth", mVideoVisibleWidth);
351
+ map.putInt("mVideoVisibleHeight", mVideoVisibleHeight);
352
+ map.putInt("mSarNum", mSarNum);
353
+ map.putInt("mSarDen", mSarDen);
354
+ map.putString("type", "onNewVideoLayout");
355
+ eventEmitter.onVideoStateChange(map);
356
+ }
357
+ };
358
+
359
+ IVLCVout.Callback callback =
360
+ new IVLCVout.Callback() {
361
+ @Override
362
+ public void onSurfacesCreated(IVLCVout ivlcVout) {
363
+ isSurfaceViewDestory = false;
364
+ }
365
+
366
+ @Override
367
+ public void onSurfacesDestroyed(IVLCVout ivlcVout) {
368
+ isSurfaceViewDestory = true;
369
+ }
370
+ };
371
+
372
+ /*************
373
+ * MediaPlayer
374
+ *************/
375
+
376
+ private void stopPlayback() {
377
+ onStopPlayback();
378
+ releasePlayer();
379
+ }
380
+
381
+ private void onStopPlayback() {
382
+ setKeepScreenOn(false);
383
+ audioManager.abandonAudioFocus(this);
384
+ }
385
+
386
+ private void attachVideoSurface() {
387
+ if (mMediaPlayer == null || getSurfaceTexture() == null) {
388
+ return;
389
+ }
390
+
391
+ IVLCVout vlcOut = mMediaPlayer.getVLCVout();
392
+ if (vlcOut.areViewsAttached()) {
393
+ vlcOut.detachViews();
394
+ }
395
+ vlcOut.setVideoSurface(getSurfaceTexture());
396
+ vlcOut.attachViews(onNewVideoLayoutListener);
397
+ isSurfaceViewDestory = false;
398
+ }
399
+
400
+ private void createPlayer(boolean autoplayResume, boolean isResume) {
401
+ releasePlayer();
402
+ if (getSurfaceTexture() == null) {
403
+ return;
404
+ }
405
+
406
+ try {
407
+ initializePlayer(autoplayResume, isResume);
408
+ eventEmitter.loadStart();
409
+ setProgressUpdateRunnable();
410
+ } catch (Exception e) {
411
+ eventEmitter.error("Failed to create VLC player", e);
412
+ }
413
+ }
414
+
415
+ private void initializePlayer(boolean autoplayResume, boolean isResume) {
416
+ ArrayList<String> initOptions = getStringOptions("initOptions");
417
+ int initType = srcMap.hasKey("initType") ? srcMap.getInt("initType") : 1;
418
+ libvlc = initType == 1 ? new LibVLC(getContext()) : new LibVLC(getContext(), initOptions);
419
+ mMediaPlayer = new MediaPlayer(libvlc);
420
+ setMutedModifier(mMuted);
421
+ mMediaPlayer.setEventListener(mPlayerListener);
422
+ registerDialogCallbacks();
423
+
424
+ IVLCVout vlcOut = mMediaPlayer.getVLCVout();
425
+ configureSurfaceSize(vlcOut);
426
+ configureMedia(getMedia());
427
+ attachSurfaceIfNeeded(vlcOut);
428
+ startPlayerIfNeeded(autoplayResume, isResume);
429
+ }
430
+
431
+ private ArrayList<String> getStringOptions(String key) {
432
+ ArrayList<String> options = new ArrayList<>();
433
+ ReadableArray values = srcMap.hasKey(key) ? srcMap.getArray(key) : null;
434
+ if (values == null) {
435
+ return options;
436
+ }
437
+ for (int index = 0; index < values.size(); index++) {
438
+ options.add(values.getString(index));
439
+ }
440
+ return options;
441
+ }
442
+
443
+ private void registerDialogCallbacks() {
444
+ Dialog.setCallbacks(
445
+ libvlc,
446
+ new Dialog.Callbacks() {
447
+ @Override
448
+ public void onDisplay(Dialog.QuestionDialog dialog) {
449
+ handleCertificateDialog(dialog);
450
+ }
451
+
452
+ @Override
453
+ public void onDisplay(Dialog.ErrorMessage dialog) {
454
+ // VLC's default error dialog behavior is intentionally retained.
455
+ }
456
+
457
+ @Override
458
+ public void onDisplay(Dialog.LoginDialog dialog) {
459
+ // VLC's default login dialog behavior is intentionally retained.
460
+ }
461
+
462
+ @Override
463
+ public void onDisplay(Dialog.ProgressDialog dialog) {
464
+ // VLC's default progress dialog behavior is intentionally retained.
465
+ }
466
+
467
+ @Override
468
+ public void onCanceled(Dialog dialog) {
469
+ // There is no app-side state to restore when VLC cancels a dialog.
470
+ }
471
+
472
+ @Override
473
+ public void onProgressUpdate(Dialog.ProgressDialog dialog) {
474
+ // VLC owns progress presentation; no bridge update is needed here.
475
+ }
476
+ });
477
+ }
478
+
479
+ private void configureSurfaceSize(IVLCVout vlcOut) {
480
+ if (mVideoWidth <= 0 || mVideoHeight <= 0) {
481
+ return;
482
+ }
483
+ vlcOut.setWindowSize(mVideoWidth, mVideoHeight);
484
+ if (autoAspectRatio) {
485
+ mMediaPlayer.setAspectRatio(mVideoWidth + ":" + mVideoHeight);
486
+ }
487
+ }
488
+
489
+ private Media getMedia() {
490
+ String uriString = srcMap.hasKey("uri") ? srcMap.getString("uri") : null;
491
+ boolean isNetwork = srcMap.hasKey("isNetwork") && srcMap.getBoolean("isNetwork");
492
+ boolean useUri =
493
+ isNetwork
494
+ || (uriString != null
495
+ && (uriString.startsWith("file://") || uriString.startsWith("content://")));
496
+ Media media;
497
+ if (useUri) {
498
+ media = new Media(libvlc, Uri.parse(uriString));
499
+ } else {
500
+ media = new Media(libvlc, uriString);
501
+ }
502
+ applyHardwareDecoder(media);
503
+ for (String option : getStringOptions("mediaOptions")) {
504
+ media.addOption(option);
505
+ }
506
+ return media;
507
+ }
508
+
509
+ private void applyHardwareDecoder(Media media) {
510
+ if (!srcMap.hasKey("hwDecoderEnabled") || !srcMap.hasKey("hwDecoderForced")) {
511
+ return;
512
+ }
513
+ media.setHWDecoderEnabled(
514
+ srcMap.getInt("hwDecoderEnabled") >= 1, srcMap.getInt("hwDecoderForced") >= 1);
515
+ }
516
+
517
+ private void configureMedia(Media media) {
518
+ mVideoInfoHash = null;
519
+ mMediaPlayer.setMedia(media);
520
+ media.release();
521
+ mMediaPlayer.setScale(0);
522
+ if (subtitleUri != null) {
523
+ mMediaPlayer.addSlave(Media.Slave.Type.Subtitle, subtitleUri, true);
524
+ }
525
+ }
526
+
527
+ private void attachSurfaceIfNeeded(IVLCVout vlcOut) {
528
+ if (vlcOut.areViewsAttached()) {
529
+ return;
530
+ }
531
+ vlcOut.addCallback(callback);
532
+ vlcOut.setVideoSurface(getSurfaceTexture());
533
+ vlcOut.attachViews(onNewVideoLayoutListener);
534
+ }
535
+
536
+ private void startPlayerIfNeeded(boolean autoplayResume, boolean isResume) {
537
+ boolean shouldPlay =
538
+ isResume ? autoplayResume : !srcMap.hasKey("autoplay") || srcMap.getBoolean("autoplay");
539
+ if (!shouldPlay) {
540
+ return;
541
+ }
542
+ isPaused = false;
543
+ mMediaPlayer.play();
544
+ }
545
+
546
+ private void releasePlayer() {
547
+ if (libvlc == null) {
548
+ mMediaPlayer = null;
549
+ return;
550
+ }
551
+
552
+ if (mMediaPlayer != null) {
553
+ final IVLCVout vout = mMediaPlayer.getVLCVout();
554
+ vout.removeCallback(callback);
555
+ vout.detachViews();
556
+ mMediaPlayer.release();
557
+ }
558
+ mMediaPlayer = null;
559
+ libvlc.release();
560
+ libvlc = null;
561
+ releaseWakeLock();
562
+
563
+ if (mProgressUpdateRunnable != null) {
564
+ mProgressUpdateHandler.removeCallbacks(mProgressUpdateRunnable);
565
+ }
566
+ }
567
+
568
+ /**
569
+ * 视频进度调整
570
+ *
571
+ * @param position
572
+ */
573
+ public void setPosition(float position) {
574
+ if (mMediaPlayer != null && position >= 0 && position <= 1) {
575
+ mMediaPlayer.setPosition(position);
576
+ }
577
+ }
578
+
579
+ public void setSubtitleUri(String subtitleUri) {
580
+ this.subtitleUri = subtitleUri;
581
+ if (mMediaPlayer != null) {
582
+ mMediaPlayer.addSlave(Media.Slave.Type.Subtitle, this.subtitleUri, true);
583
+ }
584
+ }
585
+
586
+ /**
587
+ * 设置资源路径
588
+ *
589
+ * @param uri
590
+ * @param isNetStr
591
+ */
592
+ public void setSrc(String uri, boolean isNetStr, boolean autoplay) {
593
+ if (mMediaPlayer != null && uri != null && uri.equals(this.src)) {
594
+ return;
595
+ }
596
+ this.src = uri;
597
+ createPlayer(autoplay, false);
598
+ }
599
+
600
+ public void setSrc(ReadableMap src) {
601
+ if (src == null || !src.hasKey("uri") || src.isNull("uri")) {
602
+ return;
603
+ }
604
+
605
+ String nextUri = src.getString("uri");
606
+ if (mMediaPlayer != null
607
+ && srcMap != null
608
+ && srcMap.hasKey("uri")
609
+ && !srcMap.isNull("uri")
610
+ && nextUri != null
611
+ && nextUri.equals(srcMap.getString("uri"))) {
612
+ // React Native may resend the source map when an unrelated prop
613
+ // changes (for example volume). Do not restart the media item.
614
+ srcMap = src;
615
+ return;
616
+ }
617
+
618
+ this.srcMap = src;
619
+ createPlayer(true, false);
620
+ }
621
+
622
+ /**
623
+ * 改变播放速率
624
+ *
625
+ * @param rateModifier
626
+ */
627
+ public void setRateModifier(float rateModifier) {
628
+ if (mMediaPlayer != null) {
629
+ mMediaPlayer.setRate(rateModifier);
630
+ }
631
+ }
632
+
633
+ public void setmProgressUpdateInterval(float interval) {
634
+ if (Float.compare(mProgressUpdateInterval, interval) == 0) {
635
+ return;
636
+ }
637
+ mProgressUpdateInterval = interval;
638
+ if (mProgressUpdateRunnable != null) {
639
+ mProgressUpdateHandler.removeCallbacks(mProgressUpdateRunnable);
640
+ }
641
+ if (mMediaPlayer != null && mProgressUpdateInterval > 0) {
642
+ setProgressUpdateRunnable();
643
+ }
644
+ }
645
+
646
+ /**
647
+ * 改变声音大小
648
+ *
649
+ * @param volumeModifier
650
+ */
651
+ public void setVolumeModifier(int volumeModifier) {
652
+ if (mMediaPlayer != null) {
653
+ mMediaPlayer.setVolume(volumeModifier);
654
+ }
655
+ }
656
+
657
+ /**
658
+ * 改变静音状态
659
+ *
660
+ * @param muted
661
+ */
662
+ public void setMutedModifier(boolean muted) {
663
+ mMuted = muted;
664
+ if (mMediaPlayer != null) {
665
+ if (muted) {
666
+ this.preVolume = mMediaPlayer.getVolume();
667
+ mMediaPlayer.setVolume(0);
668
+ } else {
669
+ mMediaPlayer.setVolume(this.preVolume);
670
+ }
671
+ }
672
+ }
673
+
674
+ /**
675
+ * 改变播放状态
676
+ *
677
+ * @param paused
678
+ */
679
+ public void setPausedModifier(boolean paused) {
680
+ if (mMediaPlayer != null) {
681
+ if (paused) {
682
+ isPaused = true;
683
+ releaseWakeLock();
684
+ mMediaPlayer.pause();
685
+ } else {
686
+ isPaused = false;
687
+ if (playInBackground) {
688
+ acquireWakeLock();
689
+ }
690
+ mMediaPlayer.play();
691
+ }
692
+ } else {
693
+ createPlayer(!paused, false);
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Take a screenshot of the current video frame
699
+ *
700
+ * @param path The file path where to save the screenshot
701
+ * @return boolean indicating if the screenshot was taken successfully
702
+ */
703
+ public boolean doSnapshot(String path) {
704
+ if (mMediaPlayer != null) {
705
+ try {
706
+ Bitmap bitmap = getBitmap();
707
+ if (bitmap == null) {
708
+ WritableMap event = Arguments.createMap();
709
+ event.putBoolean(EVENT_PROP_SUCCESS, false);
710
+ event.putString(EVENT_PROP_ERROR, "Failed to capture bitmap");
711
+ eventEmitter.sendEvent(event, VideoEventEmitter.EVENT_ON_SNAPSHOT);
712
+ return false;
713
+ }
714
+
715
+ File file = new File(path);
716
+ file.getParentFile().mkdirs();
717
+
718
+ FileOutputStream out = new FileOutputStream(file);
719
+
720
+ String extension = path.substring(path.lastIndexOf(".") + 1);
721
+ if (extension.equals("png")) {
722
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
723
+ } else {
724
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
725
+ }
726
+ out.flush();
727
+ out.close();
728
+
729
+ bitmap.recycle();
730
+
731
+ WritableMap event = Arguments.createMap();
732
+ event.putBoolean(EVENT_PROP_SUCCESS, true);
733
+ event.putString("path", path);
734
+ eventEmitter.sendEvent(event, VideoEventEmitter.EVENT_ON_SNAPSHOT);
735
+ return true;
736
+ } catch (Exception e) {
737
+ WritableMap event = Arguments.createMap();
738
+ event.putBoolean(EVENT_PROP_SUCCESS, false);
739
+ event.putString(EVENT_PROP_ERROR, e.getMessage());
740
+ eventEmitter.sendEvent(event, VideoEventEmitter.EVENT_ON_SNAPSHOT);
741
+ return false;
742
+ }
743
+ }
744
+ WritableMap event = Arguments.createMap();
745
+ event.putBoolean(EVENT_PROP_SUCCESS, false);
746
+ event.putString(EVENT_PROP_ERROR, "MediaPlayer is null");
747
+ eventEmitter.sendEvent(event, VideoEventEmitter.EVENT_ON_SNAPSHOT);
748
+ return false;
749
+ }
750
+
751
+ /**
752
+ * 重新加载视频
753
+ *
754
+ * @param autoplay
755
+ */
756
+ public void doResume(boolean autoplay) {
757
+ createPlayer(autoplay, true);
758
+ }
759
+
760
+ public void setRepeatModifier(boolean repeat) {
761
+ repeatEnabled = repeat;
762
+ }
763
+
764
+ /**
765
+ * 改变宽高比
766
+ *
767
+ * @param aspectRatio
768
+ */
769
+ public void setAspectRatio(String aspectRatio) {
770
+ if (mMediaPlayer != null) {
771
+ if (aspectRatio == null
772
+ || aspectRatio.isEmpty()
773
+ || "FIT".equalsIgnoreCase(aspectRatio)
774
+ || "FIT_SCREEN".equalsIgnoreCase(aspectRatio)) {
775
+ mMediaPlayer.setAspectRatio(null);
776
+ mMediaPlayer.setScale(0);
777
+ } else {
778
+ mMediaPlayer.setAspectRatio(aspectRatio);
779
+ }
780
+ }
781
+ }
782
+
783
+ public void setAutoAspectRatio(boolean auto) {
784
+ autoAspectRatio = auto;
785
+ }
786
+
787
+ public void setPlayInBackground(boolean play) {
788
+ playInBackground = play;
789
+ if (play && !isPaused) {
790
+ acquireWakeLock();
791
+ } else if (!play) {
792
+ releaseWakeLock();
793
+ }
794
+ }
795
+
796
+ public void setPlayWhenInactive(boolean play) {
797
+ setPlayInBackground(play);
798
+ }
799
+
800
+ public void setAudioTrack(int track) {
801
+ if (mMediaPlayer != null) {
802
+ mMediaPlayer.setAudioTrack(track);
803
+ }
804
+ }
805
+
806
+ public void setTextTrack(int track) {
807
+ if (mMediaPlayer != null) {
808
+ mMediaPlayer.setSpuTrack(track);
809
+ }
810
+ }
811
+
812
+ public void startRecording(String recordingPath) {
813
+ if (mMediaPlayer == null) {
814
+ return;
815
+ }
816
+ if (recordingPath != null) {
817
+ mMediaPlayer.record(recordingPath);
818
+ }
819
+ }
820
+
821
+ public void stopRecording() {
822
+ if (mMediaPlayer == null) {
823
+ return;
824
+ }
825
+ mMediaPlayer.record(null);
826
+ }
827
+
828
+ public void stopPlayer() {
829
+ if (mMediaPlayer == null) {
830
+ return;
831
+ }
832
+ mMediaPlayer.stop();
833
+ }
834
+
835
+ private void handleCertificateDialog(Dialog.QuestionDialog dialog) {
836
+ String text = dialog.getText();
837
+
838
+ // Check if it's a certificate validation dialog
839
+ if (text != null
840
+ && (text.contains("certificate")
841
+ || text.contains("SSL")
842
+ || text.contains("TLS")
843
+ || text.contains("cert"))) {
844
+ if (acceptInvalidCertificates) {
845
+ // Auto-accept invalid certificate
846
+ dialog.postAction(1); // Action 1 typically means "Accept"
847
+ } else {
848
+ // Reject invalid certificate (default secure behavior)
849
+ dialog.postAction(2); // Action 2 typically means "Reject"
850
+ }
851
+ } else {
852
+ // For non-certificate dialogs, dismiss
853
+ dialog.dismiss();
854
+ }
855
+ }
856
+
857
+ public void setAcceptInvalidCertificates(boolean accept) {
858
+ this.acceptInvalidCertificates = accept;
859
+ }
860
+
861
+ public void cleanUpResources() {
862
+ removeOnLayoutChangeListener(onLayoutChangeListener);
863
+ stopPlayback();
864
+ }
865
+
866
+ @Override
867
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
868
+ mVideoWidth = width;
869
+ mVideoHeight = height;
870
+
871
+ // Reuse the existing VLC media player after a lock-screen surface
872
+ // recreation. Recreating it here restarts the stream at 00:00.
873
+ if (playInBackground && mMediaPlayer != null) {
874
+ attachVideoSurface();
875
+ isHostPaused = false;
876
+ if (!isPaused) {
877
+ mMediaPlayer.play();
878
+ }
879
+ return;
880
+ }
881
+
882
+ createPlayer(true, false);
883
+ }
884
+
885
+ @Override
886
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
887
+ mVideoWidth = width;
888
+ mVideoHeight = height;
889
+ if (mMediaPlayer != null) {
890
+ IVLCVout vlcOut = mMediaPlayer.getVLCVout();
891
+ if (vlcOut != null) {
892
+ vlcOut.setWindowSize(width, height);
893
+ }
894
+ if (autoAspectRatio) {
895
+ mMediaPlayer.setAspectRatio(width + ":" + height);
896
+ }
897
+ }
898
+ }
899
+
900
+ @Override
901
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
902
+ isSurfaceViewDestory = true;
903
+ if (playInBackground && mMediaPlayer != null) {
904
+ IVLCVout vlcOut = mMediaPlayer.getVLCVout();
905
+ if (vlcOut != null && vlcOut.areViewsAttached()) {
906
+ vlcOut.detachViews();
907
+ }
908
+ }
909
+ return true;
910
+ }
911
+
912
+ @Override
913
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
914
+ // VLC renders frames directly to the texture; no per-frame Java work is needed.
915
+ }
916
+
917
+ private void updateVideoInfo() {
918
+ if (mMediaPlayer == null) {
919
+ return;
920
+ }
921
+ String currentHash = buildVideoInfoHash();
922
+ if (currentHash.equals(mVideoInfoHash)) {
923
+ return;
924
+ }
925
+
926
+ WritableMap info = Arguments.createMap();
927
+ info.putDouble(EVENT_PROP_DURATION, mMediaPlayer.getLength());
928
+ putTrackInfo(info, "audioTracks", mMediaPlayer.getAudioTracks());
929
+ putTrackInfo(info, "textTracks", mMediaPlayer.getSpuTracks());
930
+ putVideoSize(info, mMediaPlayer.getCurrentVideoTrack());
931
+ eventEmitter.sendEvent(info, VideoEventEmitter.EVENT_ON_LOAD);
932
+ mVideoInfoHash = currentHash;
933
+ }
934
+
935
+ private String buildVideoInfoHash() {
936
+ StringBuilder infoHash = new StringBuilder();
937
+ infoHash.append("duration:").append(mMediaPlayer.getLength()).append(';');
938
+ appendTrackHash(infoHash, "audioTracks", mMediaPlayer.getAudioTracks());
939
+ appendTrackHash(infoHash, "textTracks", mMediaPlayer.getSpuTracks());
940
+ Media.VideoTrack video = mMediaPlayer.getCurrentVideoTrack();
941
+ if (video != null) {
942
+ infoHash
943
+ .append("videoSize:")
944
+ .append(video.width)
945
+ .append('x')
946
+ .append(video.height)
947
+ .append(';');
948
+ }
949
+ return infoHash.toString();
950
+ }
951
+
952
+ private void appendTrackHash(
953
+ StringBuilder infoHash, String name, MediaPlayer.TrackDescription[] tracks) {
954
+ if (tracks == null || tracks.length == 0) {
955
+ return;
956
+ }
957
+ infoHash.append(name).append(':');
958
+ for (MediaPlayer.TrackDescription track : tracks) {
959
+ infoHash.append(track.id).append(':').append(track.name).append(',');
960
+ }
961
+ infoHash.append(';');
962
+ }
963
+
964
+ private void putTrackInfo(
965
+ WritableMap info, String name, MediaPlayer.TrackDescription[] trackDescriptions) {
966
+ if (trackDescriptions == null || trackDescriptions.length == 0) {
967
+ return;
968
+ }
969
+ WritableArray tracks = new WritableNativeArray();
970
+ for (MediaPlayer.TrackDescription track : trackDescriptions) {
971
+ WritableMap trackMap = Arguments.createMap();
972
+ trackMap.putInt("id", track.id);
973
+ trackMap.putString("name", track.name);
974
+ tracks.pushMap(trackMap);
975
+ }
976
+ info.putArray(name, tracks);
977
+ }
978
+
979
+ private void putVideoSize(WritableMap info, Media.VideoTrack video) {
980
+ if (video == null) {
981
+ return;
982
+ }
983
+ WritableMap videoSize = Arguments.createMap();
984
+ videoSize.putInt("width", video.width);
985
+ videoSize.putInt("height", video.height);
986
+ info.putMap("videoSize", videoSize);
987
+ }
988
+ }