@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.
- package/CineCrewPlayer.podspec +22 -0
- package/LICENSE +21 -0
- package/NOTICE +6 -0
- package/README.md +574 -0
- package/android/build.gradle +48 -0
- package/android/proguard-rules.pro +29 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/yuanzhou/vlc/ReactVlcPlayerPackage.java +28 -0
- package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/ReactVlcPlayerView.java +988 -0
- package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/ReactVlcPlayerViewManager.java +208 -0
- package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/VideoEventEmitter.java +136 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/app.plugin.cjs +1 -0
- package/assets/cinecrew-player-logo.svg +44 -0
- package/package.json +132 -0
- package/packages/react-native-vlc-media-player/LICENSE +21 -0
- package/packages/react-native-vlc-media-player/VLCPlayer.js +207 -0
- package/packages/react-native-vlc-media-player/expo/android/withGradleTasks.cjs +49 -0
- package/packages/react-native-vlc-media-player/expo/ios/withMobileVlcKit.cjs +28 -0
- package/packages/react-native-vlc-media-player/expo/withVlcMediaPlayer.cjs +10 -0
- package/packages/react-native-vlc-media-player/index.d.ts +354 -0
- package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayer.h +36 -0
- package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayer.m +635 -0
- package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayerManager.h +5 -0
- package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayerManager.m +100 -0
- package/src/native/InlineLivePlayer.js +321 -0
- package/src/native/MediaPlayerView.js +3093 -0
- package/src/native/customization.js +44 -0
- package/src/native/index.js +107 -0
- package/src/native/media/ElectronVideoPlayer.js +288 -0
- package/src/native/media/EmojiPickerModal.js +30 -0
- package/src/native/media/LiveChatDrawer.js +1177 -0
- package/src/native/media/LiveRecordingOverlay.js +319 -0
- package/src/native/media/WebVideoPlayer.js +320 -0
- package/src/native/media/YouTubeVideoPlayer.native.js +120 -0
- package/src/native/media/YouTubeVideoPlayer.web.js +169 -0
- package/src/native/media/player/AudioOnlyView.js +337 -0
- package/src/native/media/player/CenterControls.js +94 -0
- package/src/native/media/player/ExoVideoFallback.js +329 -0
- package/src/native/media/player/PipOverlay.js +1 -0
- package/src/native/media/player/PlayerBottomBar.js +295 -0
- package/src/native/media/player/PlayerTopBar.js +255 -0
- package/src/native/media/player/VLCBoundary.js +41 -0
- package/src/native/media/player/VerticalIndicator.js +184 -0
- package/src/native/media/player/index.js +9 -0
- package/src/native/media/player/playerConstants.js +159 -0
- package/src/native/media/web/useWebAc3AudioPlayback.native.js +6 -0
- package/src/native/media/web/useWebAc3AudioPlayback.web.js +219 -0
- package/src/native/media/web/useWebHlsPlayback.native.js +3 -0
- package/src/native/media/web/useWebHlsPlayback.web.js +66 -0
- package/src/native/media/web/useWebMediaSession.js +90 -0
- package/src/native/media/web/useWebMpegTsPlayback.native.js +3 -0
- package/src/native/media/web/useWebMpegTsPlayback.web.js +356 -0
- package/src/native/media/web/useWebVideoAspectRatio.js +52 -0
- package/src/native/media/web/webPlaybackErrors.js +4 -0
- package/src/utils/layoutUtils.js +12 -0
- package/src/utils/mediaUtils.js +7 -0
- package/src/utils/recording.js +8 -0
- package/src/utils/runtimePlatform.js +19 -0
- package/src/utils/sourceUtils.js +99 -0
- package/src/utils/youtubeHtml.js +34 -0
- package/src/web/index.js +1129 -0
- package/src/web/styles.css +378 -0
- package/types/index.d.ts +268 -0
- package/vendor/mpegts.js/LICENSE +202 -0
- 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
|
+
}
|