@jwplayer/jwplayer-react-native 1.0.1 → 1.0.3
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/.github/ISSUE_TEMPLATE/bug_report.md +10 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +10 -0
- package/.github/ISSUE_TEMPLATE/implement.md +9 -0
- package/.github/ISSUE_TEMPLATE/question.md +10 -0
- package/README.md +2 -0
- package/RNJWPlayer.podspec +2 -2
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -2
- package/android/build.gradle +1 -1
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerModule.java +510 -458
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +384 -32
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerViewManager.java +12 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/Util.java +26 -7
- package/badges/version.svg +1 -1
- package/index.d.ts +41 -7
- package/index.js +22 -2
- package/ios/RNJWPlayer/RNJWPlayerView.swift +47 -34
- package/ios/RNJWPlayer/RNJWPlayerViewController.swift +15 -9
- package/ios/RNJWPlayer/RNJWPlayerViewManager.m +9 -1
- package/ios/RNJWPlayer/RNJWPlayerViewManager.swift +49 -15
- package/jwplayer-jwplayer-react-native-1.0.3.tgz +0 -0
- package/package.json +3 -3
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/android/.gradle/8.1.1/checksums/checksums.lock +0 -0
- package/android/.gradle/8.1.1/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.1.1/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.1.1/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.1.1/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.4/checksums/checksums.lock +0 -0
- package/android/.gradle/8.4/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.4/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.4/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.4/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.4/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/8.4/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.4/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.4/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/8.4/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.4/gc.properties +0 -0
- package/android/.gradle/config.properties +0 -2
- package/android/.gradle/file-system.probe +0 -0
- package/android/.idea/compiler.xml +0 -6
- package/android/.idea/gradle.xml +0 -18
- package/android/.idea/migrations.xml +0 -10
- package/android/.idea/misc.xml +0 -10
- package/android/.idea/vcs.xml +0 -6
- package/android/local.properties +0 -8
- package/ios/RNJWPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/RNJWPlayer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/RNJWPlayer.xcodeproj/project.xcworkspace/xcuserdata/jmilham.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/RNJWPlayer.xcodeproj/xcuserdata/jmilham.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- /package/android/.gradle/{8.1.1 → 8.9}/dependencies-accessors/gc.properties +0 -0
- /package/android/.gradle/{8.1.1 → 8.9}/fileChanges/last-build.bin +0 -0
- /package/android/.gradle/{8.1.1 → 8.9}/gc.properties +0 -0
|
@@ -2,7 +2,11 @@ package com.jwplayer.rnjwplayer;
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
import android.app.Activity;
|
|
5
|
+
import android.app.ActivityManager;
|
|
6
|
+
import android.content.BroadcastReceiver;
|
|
5
7
|
import android.content.Context;
|
|
8
|
+
import android.content.Intent;
|
|
9
|
+
import android.content.IntentFilter;
|
|
6
10
|
import android.content.pm.ActivityInfo;
|
|
7
11
|
import android.graphics.Color;
|
|
8
12
|
import android.graphics.PorterDuff;
|
|
@@ -12,6 +16,8 @@ import android.media.AudioAttributes;
|
|
|
12
16
|
import android.media.AudioFocusRequest;
|
|
13
17
|
import android.media.AudioManager;
|
|
14
18
|
import android.os.Build;
|
|
19
|
+
import android.os.Handler;
|
|
20
|
+
import android.os.Looper;
|
|
15
21
|
import android.util.Log;
|
|
16
22
|
import android.view.View;
|
|
17
23
|
import android.view.ViewGroup;
|
|
@@ -20,7 +26,13 @@ import android.view.WindowManager;
|
|
|
20
26
|
import android.widget.LinearLayout;
|
|
21
27
|
import android.widget.RelativeLayout;
|
|
22
28
|
|
|
29
|
+
import androidx.annotation.NonNull;
|
|
23
30
|
import androidx.appcompat.app.AppCompatActivity;
|
|
31
|
+
import androidx.lifecycle.Lifecycle;
|
|
32
|
+
import androidx.lifecycle.LifecycleEventObserver;
|
|
33
|
+
import androidx.lifecycle.LifecycleObserver;
|
|
34
|
+
import androidx.lifecycle.LifecycleOwner;
|
|
35
|
+
import androidx.lifecycle.LifecycleRegistry;
|
|
24
36
|
|
|
25
37
|
import com.facebook.react.ReactActivity;
|
|
26
38
|
import com.facebook.react.bridge.Arguments;
|
|
@@ -28,15 +40,17 @@ import com.facebook.react.bridge.LifecycleEventListener;
|
|
|
28
40
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
29
41
|
import com.facebook.react.bridge.ReadableArray;
|
|
30
42
|
import com.facebook.react.bridge.ReadableMap;
|
|
43
|
+
import com.facebook.react.bridge.WritableArray;
|
|
31
44
|
import com.facebook.react.bridge.WritableMap;
|
|
32
45
|
import com.facebook.react.common.MapBuilder;
|
|
33
46
|
import com.facebook.react.uimanager.ThemedReactContext;
|
|
34
47
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
|
35
48
|
import com.google.common.collect.ImmutableMap;
|
|
36
49
|
import com.google.gson.Gson;
|
|
37
|
-
import com.jwplayer.pub.api.JsonHelper;
|
|
38
50
|
import com.jwplayer.pub.api.JWPlayer;
|
|
51
|
+
import com.jwplayer.pub.api.JsonHelper;
|
|
39
52
|
import com.jwplayer.pub.api.UiGroup;
|
|
53
|
+
import com.jwplayer.pub.api.background.MediaService;
|
|
40
54
|
import com.jwplayer.pub.api.background.MediaServiceController;
|
|
41
55
|
import com.jwplayer.pub.api.configuration.PlayerConfig;
|
|
42
56
|
import com.jwplayer.pub.api.configuration.UiConfig;
|
|
@@ -96,18 +110,25 @@ import com.jwplayer.pub.api.events.listeners.AdvertisingEvents;
|
|
|
96
110
|
import com.jwplayer.pub.api.events.listeners.CastingEvents;
|
|
97
111
|
import com.jwplayer.pub.api.events.listeners.PipPluginEvents;
|
|
98
112
|
import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents;
|
|
113
|
+
import com.jwplayer.pub.api.fullscreen.ExtensibleFullscreenHandler;
|
|
114
|
+
import com.jwplayer.pub.api.fullscreen.FullscreenDialog;
|
|
99
115
|
import com.jwplayer.pub.api.fullscreen.FullscreenHandler;
|
|
116
|
+
import com.jwplayer.pub.api.fullscreen.delegates.DeviceOrientationDelegate;
|
|
117
|
+
import com.jwplayer.pub.api.fullscreen.delegates.DialogLayoutDelegate;
|
|
118
|
+
import com.jwplayer.pub.api.fullscreen.delegates.SystemUiDelegate;
|
|
100
119
|
import com.jwplayer.pub.api.license.LicenseUtil;
|
|
120
|
+
import com.jwplayer.pub.api.media.captions.Caption;
|
|
101
121
|
import com.jwplayer.pub.api.media.playlists.PlaylistItem;
|
|
102
122
|
import com.jwplayer.ui.views.CueMarkerSeekbar;
|
|
103
123
|
|
|
124
|
+
import org.json.JSONObject;
|
|
125
|
+
|
|
104
126
|
import java.util.ArrayList;
|
|
105
127
|
import java.util.Arrays;
|
|
106
128
|
import java.util.HashMap;
|
|
107
129
|
import java.util.List;
|
|
108
130
|
import java.util.Map;
|
|
109
|
-
|
|
110
|
-
import org.json.JSONObject;
|
|
131
|
+
import java.util.Objects;
|
|
111
132
|
|
|
112
133
|
public class RNJWPlayerView extends RelativeLayout implements
|
|
113
134
|
VideoPlayerEvents.OnFullscreenListener,
|
|
@@ -166,7 +187,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
166
187
|
|
|
167
188
|
AudioManager.OnAudioFocusChangeListener,
|
|
168
189
|
|
|
169
|
-
LifecycleEventListener {
|
|
190
|
+
LifecycleEventListener, LifecycleOwner {
|
|
170
191
|
public RNJWPlayer mPlayerView = null;
|
|
171
192
|
public JWPlayer mPlayer = null;
|
|
172
193
|
|
|
@@ -183,6 +204,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
183
204
|
Boolean fullScreenOnLandscape = false;
|
|
184
205
|
Boolean portraitOnExitFullScreen = false;
|
|
185
206
|
Boolean exitFullScreenOnPortrait = false;
|
|
207
|
+
Boolean playerInModal = false;
|
|
186
208
|
|
|
187
209
|
Number currentPlayingIndex;
|
|
188
210
|
|
|
@@ -212,10 +234,17 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
212
234
|
private ThemedReactContext mThemedReactContext;
|
|
213
235
|
|
|
214
236
|
private MediaServiceController mMediaServiceController;
|
|
237
|
+
private PipHandlerReceiver mReceiver = null;
|
|
215
238
|
|
|
216
239
|
private void doBindService() {
|
|
217
240
|
if (mMediaServiceController != null) {
|
|
218
|
-
|
|
241
|
+
if (!isBackgroundAudioServiceRunning()) {
|
|
242
|
+
// This may not be your expected behavior, but is necessary to avoid crashing
|
|
243
|
+
// Do not use multiple player instances with background audio enabled
|
|
244
|
+
|
|
245
|
+
// don't rebind me if the service is already active with a player.
|
|
246
|
+
mMediaServiceController.bindService();
|
|
247
|
+
}
|
|
219
248
|
}
|
|
220
249
|
}
|
|
221
250
|
|
|
@@ -233,7 +262,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
233
262
|
}
|
|
234
263
|
|
|
235
264
|
private static Context getNonBuggyContext(ThemedReactContext reactContext,
|
|
236
|
-
|
|
265
|
+
ReactApplicationContext appContext) {
|
|
237
266
|
Context superContext = reactContext;
|
|
238
267
|
if (!contextHasBug(appContext.getCurrentActivity())) {
|
|
239
268
|
superContext = appContext.getCurrentActivity();
|
|
@@ -250,10 +279,23 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
250
279
|
return superContext;
|
|
251
280
|
}
|
|
252
281
|
|
|
282
|
+
private boolean isBackgroundAudioServiceRunning() {
|
|
283
|
+
ActivityManager manager = (ActivityManager) mAppContext.getSystemService(Context.ACTIVITY_SERVICE);
|
|
284
|
+
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
|
|
285
|
+
if (MediaService.class.getName().equals(service.service.getClassName())) {
|
|
286
|
+
Log.w(TAG, "MediaService is already running with another player loaded. To avoid crashing, this player, "
|
|
287
|
+
+ mPlayerView.getTag() + " will not be loaded into the background service.");
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
|
|
253
294
|
public RNJWPlayerView(ThemedReactContext reactContext, ReactApplicationContext appContext) {
|
|
254
295
|
super(getNonBuggyContext(reactContext, appContext));
|
|
255
296
|
mAppContext = appContext;
|
|
256
297
|
|
|
298
|
+
registry.setCurrentState(Lifecycle.State.CREATED);
|
|
257
299
|
mThemedReactContext = reactContext;
|
|
258
300
|
|
|
259
301
|
mActivity = (ReactActivity) getActivity();
|
|
@@ -261,11 +303,25 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
261
303
|
mWindow = mActivity.getWindow();
|
|
262
304
|
}
|
|
263
305
|
|
|
306
|
+
if (mActivity != null) {
|
|
307
|
+
mActivity.getLifecycle().addObserver(lifecycleObserver);
|
|
308
|
+
}
|
|
309
|
+
|
|
264
310
|
mRootView = mActivity.findViewById(android.R.id.content);
|
|
265
311
|
|
|
266
312
|
getReactContext().addLifecycleEventListener(this);
|
|
267
313
|
}
|
|
268
314
|
|
|
315
|
+
private LifecycleObserver lifecycleObserver = new LifecycleEventObserver() {
|
|
316
|
+
@Override
|
|
317
|
+
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
|
|
318
|
+
if (event.getTargetState() == Lifecycle.State.DESTROYED) {
|
|
319
|
+
return; // no op: handled elsewhere
|
|
320
|
+
}
|
|
321
|
+
registry.setCurrentState(event.getTargetState());
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
269
325
|
public ReactApplicationContext getAppContext() {
|
|
270
326
|
return mAppContext;
|
|
271
327
|
}
|
|
@@ -288,10 +344,41 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
288
344
|
return mThemedReactContext.getReactApplicationContext().getCurrentActivity();
|
|
289
345
|
}
|
|
290
346
|
|
|
347
|
+
// The registry for lifecycle events. Required by player object. Main use case if for garbage collection / teardown
|
|
348
|
+
private final LifecycleRegistry registry = new LifecycleRegistry(this);
|
|
349
|
+
|
|
350
|
+
@NonNull
|
|
351
|
+
@Override
|
|
352
|
+
public Lifecycle getLifecycle() {
|
|
353
|
+
return registry;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// // closest to `ondestroy` for a view without listening to the activity event
|
|
357
|
+
// // The activity event can be deceptive in React-Native
|
|
358
|
+
// @Override
|
|
359
|
+
// protected void onDetachedFromWindow() {
|
|
360
|
+
// super.onDetachedFromWindow();
|
|
361
|
+
// registry.setCurrentState(Lifecycle.State.DESTROYED);
|
|
362
|
+
// }
|
|
363
|
+
|
|
291
364
|
public void destroyPlayer() {
|
|
292
365
|
if (mPlayer != null) {
|
|
366
|
+
unRegisterReceiver();
|
|
367
|
+
|
|
368
|
+
// If we are casting we need to break the cast session as there is no simple
|
|
369
|
+
// way to reconnect to an existing session if the player that created it is dead
|
|
370
|
+
|
|
371
|
+
// If this doesn't match your use case, using a single player object and load content
|
|
372
|
+
// into it rather than creating a new player for every piece of content.
|
|
293
373
|
mPlayer.stop();
|
|
294
374
|
|
|
375
|
+
// send signal to JW SDK player is destroyed
|
|
376
|
+
registry.setCurrentState(Lifecycle.State.DESTROYED);
|
|
377
|
+
|
|
378
|
+
// Stop listening to activities lifecycle
|
|
379
|
+
mActivity.getLifecycle().removeObserver(lifecycleObserver);
|
|
380
|
+
mPlayer.deregisterActivityForPip();
|
|
381
|
+
|
|
295
382
|
mPlayer.removeListeners(this,
|
|
296
383
|
// VideoPlayerEvents
|
|
297
384
|
EventType.READY,
|
|
@@ -348,7 +435,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
348
435
|
EventType.PIP_OPEN
|
|
349
436
|
);
|
|
350
437
|
|
|
351
|
-
mPlayer
|
|
438
|
+
mPlayer = null;
|
|
352
439
|
mPlayerView = null;
|
|
353
440
|
|
|
354
441
|
getReactContext().removeLifecycleEventListener(this);
|
|
@@ -427,12 +514,117 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
427
514
|
EventType.PIP_OPEN
|
|
428
515
|
);
|
|
429
516
|
|
|
430
|
-
|
|
517
|
+
if (playerInModal) {
|
|
518
|
+
mPlayer.setFullscreenHandler(createModalFullscreenHandler());
|
|
519
|
+
} else {
|
|
520
|
+
mPlayer.setFullscreenHandler(new fullscreenHandler());
|
|
521
|
+
}
|
|
431
522
|
|
|
432
523
|
mPlayer.allowBackgroundAudio(backgroundAudioEnabled);
|
|
433
524
|
}
|
|
434
525
|
}
|
|
435
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Helper to build the a generic `ExtensibleFullscreenHandler` with small tweaks to play nice with Modals
|
|
529
|
+
*
|
|
530
|
+
* @return {@link ExtensibleFullscreenHandler}
|
|
531
|
+
*/
|
|
532
|
+
private ExtensibleFullscreenHandler createModalFullscreenHandler() {
|
|
533
|
+
DeviceOrientationDelegate delegate = getDeviceOrientationDelegate();
|
|
534
|
+
FullscreenDialog dialog = new FullscreenDialog(
|
|
535
|
+
mActivity,
|
|
536
|
+
mActivity,
|
|
537
|
+
android.R.style.Theme_Black_NoTitleBar_Fullscreen
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
return new ExtensibleFullscreenHandler(
|
|
541
|
+
new DialogLayoutDelegate(
|
|
542
|
+
mPlayerView,
|
|
543
|
+
dialog
|
|
544
|
+
),
|
|
545
|
+
delegate,
|
|
546
|
+
new SystemUiDelegate(
|
|
547
|
+
mActivity,
|
|
548
|
+
mActivity.getLifecycle(),
|
|
549
|
+
new Handler(),
|
|
550
|
+
dialog.getWindow().getDecorView()
|
|
551
|
+
)
|
|
552
|
+
) {
|
|
553
|
+
@Override
|
|
554
|
+
public void onFullscreenRequested() {
|
|
555
|
+
// if landscape is priorty we have to turn off full-screen portrait before allowing
|
|
556
|
+
// the default call for full-screen
|
|
557
|
+
mPlayer.allowFullscreenPortrait(!landscapeOnFullScreen);
|
|
558
|
+
super.onFullscreenRequested();
|
|
559
|
+
// safely set it back on UI thread after work can be finished
|
|
560
|
+
final Handler handler = new Handler(Looper.getMainLooper());
|
|
561
|
+
handler.postDelayed(() -> {
|
|
562
|
+
if (mPlayer != null) {
|
|
563
|
+
mPlayer.allowFullscreenPortrait(true);
|
|
564
|
+
}
|
|
565
|
+
}, 100);
|
|
566
|
+
WritableMap eventEnterFullscreen = Arguments.createMap();
|
|
567
|
+
eventEnterFullscreen.putString("message", "onFullscreenRequested");
|
|
568
|
+
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
|
|
569
|
+
getId(),
|
|
570
|
+
"topFullScreenRequested",
|
|
571
|
+
eventEnterFullscreen);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
@Override
|
|
575
|
+
public void onFullscreenExitRequested() {
|
|
576
|
+
super.onFullscreenExitRequested();
|
|
577
|
+
|
|
578
|
+
WritableMap eventExitFullscreen = Arguments.createMap();
|
|
579
|
+
eventExitFullscreen.putString("message", "onFullscreenExitRequested");
|
|
580
|
+
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
|
|
581
|
+
getId(),
|
|
582
|
+
"topFullScreenExitRequested",
|
|
583
|
+
eventExitFullscreen);
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Add logic here for your custom orientation implementation
|
|
590
|
+
*
|
|
591
|
+
* @return Default {@link DeviceOrientationDelegate}
|
|
592
|
+
*/
|
|
593
|
+
private DeviceOrientationDelegate getDeviceOrientationDelegate() {
|
|
594
|
+
DeviceOrientationDelegate delegate = new DeviceOrientationDelegate(
|
|
595
|
+
mActivity,
|
|
596
|
+
mActivity.getLifecycle(),
|
|
597
|
+
new Handler()
|
|
598
|
+
) {
|
|
599
|
+
@Override
|
|
600
|
+
public void setFullscreen(boolean fullscreen) {
|
|
601
|
+
super.setFullscreen(fullscreen);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
@Override
|
|
605
|
+
public void onAllowRotationChanged(boolean allowRotation) {
|
|
606
|
+
super.onAllowRotationChanged(allowRotation);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
@Override
|
|
610
|
+
protected void doRotation(boolean fullscreen, boolean allowFullscreenPortrait) {
|
|
611
|
+
super.doRotation(fullscreen, allowFullscreenPortrait);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
@Override
|
|
615
|
+
protected void doRotationListener() {
|
|
616
|
+
super.doRotationListener();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
@Override
|
|
620
|
+
public void onAllowFullscreenPortrait(boolean allowFullscreenPortrait) {
|
|
621
|
+
super.onAllowFullscreenPortrait(allowFullscreenPortrait);
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
delegate.onAllowRotationChanged(true);
|
|
625
|
+
return delegate;
|
|
626
|
+
}
|
|
627
|
+
|
|
436
628
|
private class fullscreenHandler implements FullscreenHandler {
|
|
437
629
|
ViewGroup mPlayerViewContainer = (ViewGroup) mPlayerView.getParent();
|
|
438
630
|
private View mDecorView;
|
|
@@ -501,11 +693,21 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
501
693
|
mPlayerViewContainer.post(new Runnable() {
|
|
502
694
|
@Override
|
|
503
695
|
public void run() {
|
|
696
|
+
// View may not have been removed properly (especially if returning from PiP)
|
|
697
|
+
mPlayerViewContainer.removeView(mPlayerView);
|
|
698
|
+
|
|
504
699
|
mPlayerViewContainer.addView(mPlayerView, new ViewGroup.LayoutParams(
|
|
505
700
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
506
701
|
ViewGroup.LayoutParams.MATCH_PARENT));
|
|
507
|
-
|
|
508
|
-
|
|
702
|
+
// returning from full-screen portrait requires a different measure
|
|
703
|
+
if (mActivity.getResources().getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
704
|
+
) {
|
|
705
|
+
mPlayerView.layout(mPlayerView.getLeft(), mPlayerViewContainer.getTop(),
|
|
706
|
+
mPlayerViewContainer.getMeasuredWidth(), mPlayerViewContainer.getBottom());
|
|
707
|
+
} else {
|
|
708
|
+
mPlayerView.layout(mPlayerViewContainer.getLeft(), mPlayerViewContainer.getTop(),
|
|
709
|
+
mPlayerViewContainer.getRight(), mPlayerViewContainer.getBottom());
|
|
710
|
+
}
|
|
509
711
|
}
|
|
510
712
|
});
|
|
511
713
|
|
|
@@ -519,12 +721,12 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
519
721
|
|
|
520
722
|
@Override
|
|
521
723
|
public void onAllowRotationChanged(boolean b) {
|
|
522
|
-
Log.e(TAG, "onAllowRotationChanged: " + b
|
|
724
|
+
Log.e(TAG, "onAllowRotationChanged: " + b);
|
|
523
725
|
}
|
|
524
726
|
|
|
525
727
|
@Override
|
|
526
728
|
public void onAllowFullscreenPortraitChanged(boolean allowFullscreenPortrait) {
|
|
527
|
-
Log.e(TAG, "onAllowFullscreenPortraitChanged: " + allowFullscreenPortrait
|
|
729
|
+
Log.e(TAG, "onAllowFullscreenPortraitChanged: " + allowFullscreenPortrait);
|
|
528
730
|
}
|
|
529
731
|
|
|
530
732
|
@Override
|
|
@@ -540,10 +742,101 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
540
742
|
}
|
|
541
743
|
}
|
|
542
744
|
|
|
745
|
+
private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
|
|
746
|
+
|
|
747
|
+
private class PipHandlerReceiver extends BroadcastReceiver {
|
|
748
|
+
|
|
749
|
+
@Override
|
|
750
|
+
public void onReceive(Context context, Intent intent) {
|
|
751
|
+
if (intent == null) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (Objects.equals(intent.getAction(), "onPictureInPictureModeChanged")) {
|
|
755
|
+
if (intent.hasExtra("newConfig") && intent.hasExtra("isInPictureInPictureMode")) {
|
|
756
|
+
// Tell the JWP SDK we are toggling so it can handle toolbar / internal setup
|
|
757
|
+
mPlayer.onPictureInPictureModeChanged(intent.getBooleanExtra("isInPictureInPictureMode", false), intent.getParcelableExtra("newConfig"));
|
|
758
|
+
|
|
759
|
+
View decorView = mActivity.getWindow().getDecorView();
|
|
760
|
+
ViewGroup rootView = decorView.findViewById(android.R.id.content);
|
|
761
|
+
|
|
762
|
+
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
|
|
763
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
764
|
+
ViewGroup.LayoutParams.MATCH_PARENT);
|
|
765
|
+
|
|
766
|
+
if (intent.getBooleanExtra("isInPictureInPictureMode", false)) {
|
|
767
|
+
// Going into Picture in Picture
|
|
768
|
+
ViewGroup parent = (ViewGroup) mPlayerView.getParent();
|
|
769
|
+
|
|
770
|
+
// Remove the player view temporarily
|
|
771
|
+
if (parent != null) {
|
|
772
|
+
parent.removeView(mPlayerView);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Hide all views but player view and keep a handle on them for later
|
|
776
|
+
for (int i = 0; i < rootView.getChildCount(); i++) {
|
|
777
|
+
if (rootView.getChildAt(i) != mPlayerView) {
|
|
778
|
+
rootViewChildrenOriginalVisibility.add(rootView.getChildAt(i).getVisibility());
|
|
779
|
+
rootView.getChildAt(i).setVisibility(View.GONE);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
// Add player view back (This is safe since the JWP SDK has already calculated the PiP size/aspect off the View)
|
|
783
|
+
rootView.addView(mPlayerView, layoutParams);
|
|
784
|
+
} else {
|
|
785
|
+
// Exiting Picture in Picture
|
|
786
|
+
|
|
787
|
+
// Toggle controls to ensure we don't lose them -- weird UX bug fix where controls got lost
|
|
788
|
+
mPlayer.setForceControlsVisibility(true);
|
|
789
|
+
mPlayer.setForceControlsVisibility(false);
|
|
790
|
+
|
|
791
|
+
// If player was in fullscreen when going into PiP, we need to force it back out
|
|
792
|
+
if (mPlayer.getFullscreen()) {
|
|
793
|
+
mPlayer.setFullscreen(false, true);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Strip player view
|
|
797
|
+
rootView.removeView(mPlayerView);
|
|
798
|
+
|
|
799
|
+
// Add visibility back to any other controls
|
|
800
|
+
for (int i = 0; i < rootView.getChildCount(); i++) {
|
|
801
|
+
rootView.getChildAt(i).setVisibility(rootViewChildrenOriginalVisibility.get(i));
|
|
802
|
+
}
|
|
803
|
+
// Clear our list of views
|
|
804
|
+
rootViewChildrenOriginalVisibility.clear();
|
|
805
|
+
// Add player view back in main spot
|
|
806
|
+
addView(mPlayerView, 0, layoutParams);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private void registerReceiver() {
|
|
814
|
+
mReceiver = new PipHandlerReceiver();
|
|
815
|
+
IntentFilter intentFilter = new IntentFilter("onPictureInPictureModeChanged");
|
|
816
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
817
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
818
|
+
// Must be Exported to get intents
|
|
819
|
+
mActivity.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED);
|
|
820
|
+
} else {
|
|
821
|
+
mActivity.registerReceiver(mReceiver, intentFilter); // Safe API level < 34
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
mActivity.registerReceiver(mReceiver, intentFilter); // Safe API level < 34
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
private void unRegisterReceiver() {
|
|
829
|
+
if (mReceiver != null) {
|
|
830
|
+
mActivity.unregisterReceiver(mReceiver);
|
|
831
|
+
mReceiver = null;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
}
|
|
835
|
+
|
|
543
836
|
public void setConfig(ReadableMap prop) {
|
|
544
837
|
if (mConfig == null || !mConfig.equals(prop)) {
|
|
545
838
|
if (mConfig != null && isOnlyDiff(prop, "playlist") && mPlayer != null) { // still safe check, even with JW
|
|
546
|
-
|
|
839
|
+
// JSON change
|
|
547
840
|
PlayerConfig oldConfig = mPlayer.getConfig();
|
|
548
841
|
PlayerConfig config = new PlayerConfig.Builder()
|
|
549
842
|
.autostart(oldConfig.getAutostart())
|
|
@@ -610,7 +903,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
610
903
|
|
|
611
904
|
boolean playlistNotTheSame(ReadableMap prop) {
|
|
612
905
|
return prop.hasKey("playlist") && mPlaylistProp != prop.getArray("playlist") && !Arrays
|
|
613
|
-
.deepEquals(new ReadableArray[]
|
|
906
|
+
.deepEquals(new ReadableArray[]{mPlaylistProp}, new ReadableArray[]{prop.getArray("playlist")});
|
|
614
907
|
}
|
|
615
908
|
|
|
616
909
|
private void setupPlayer(ReadableMap prop) {
|
|
@@ -621,7 +914,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
621
914
|
PlayerConfig jwConfig = null;
|
|
622
915
|
Boolean forceLegacy = prop.hasKey("forceLegacyConfig") ? prop.getBoolean("forceLegacyConfig") : false;
|
|
623
916
|
Boolean isJwConfig = false;
|
|
624
|
-
if(!forceLegacy){
|
|
917
|
+
if (!forceLegacy) {
|
|
625
918
|
try {
|
|
626
919
|
obj = MapUtil.toJSONObject(prop);
|
|
627
920
|
jwConfig = JsonHelper.parseConfigJson(obj);
|
|
@@ -752,6 +1045,11 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
752
1045
|
LinearLayout.LayoutParams.MATCH_PARENT));
|
|
753
1046
|
addView(mPlayerView);
|
|
754
1047
|
|
|
1048
|
+
// Ensure we have a valid state before applying to the player
|
|
1049
|
+
registry.setCurrentState(Lifecycle.State.STARTED);
|
|
1050
|
+
|
|
1051
|
+
mPlayer = mPlayerView.getPlayer(this);
|
|
1052
|
+
|
|
755
1053
|
if (prop.hasKey("controls")) { // Hack for controls hiding not working right away
|
|
756
1054
|
mPlayerView.getPlayer().setControls(prop.getBoolean("controls"));
|
|
757
1055
|
}
|
|
@@ -761,13 +1059,23 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
761
1059
|
mPlayerView.fullScreenOnLandscape = fullScreenOnLandscape;
|
|
762
1060
|
}
|
|
763
1061
|
|
|
1062
|
+
if (prop.hasKey("landscapeOnFullScreen")) {
|
|
1063
|
+
landscapeOnFullScreen = prop.getBoolean("landscapeOnFullScreen");
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (prop.hasKey("portraitOnExitFullScreen")) {
|
|
1067
|
+
portraitOnExitFullScreen = prop.getBoolean("fullScreenOnLandscape");
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (prop.hasKey("playerInModal")) {
|
|
1071
|
+
playerInModal = prop.getBoolean("playerInModal");
|
|
1072
|
+
}
|
|
1073
|
+
|
|
764
1074
|
if (prop.hasKey("exitFullScreenOnPortrait")) {
|
|
765
1075
|
exitFullScreenOnPortrait = prop.getBoolean("exitFullScreenOnPortrait");
|
|
766
1076
|
mPlayerView.exitFullScreenOnPortrait = exitFullScreenOnPortrait;
|
|
767
1077
|
}
|
|
768
1078
|
|
|
769
|
-
mPlayer = mPlayerView.getPlayer();
|
|
770
|
-
|
|
771
1079
|
if (isJwConfig) {
|
|
772
1080
|
mPlayer.setup(jwConfig);
|
|
773
1081
|
} else {
|
|
@@ -778,16 +1086,18 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
778
1086
|
if (mActivity != null && prop.hasKey("pipEnabled")) {
|
|
779
1087
|
boolean pipEnabled = prop.getBoolean("pipEnabled");
|
|
780
1088
|
if (pipEnabled) {
|
|
1089
|
+
registerReceiver();
|
|
781
1090
|
mPlayer.registerActivityForPip(mActivity, mActivity.getSupportActionBar());
|
|
782
1091
|
} else {
|
|
783
1092
|
mPlayer.deregisterActivityForPip();
|
|
1093
|
+
unRegisterReceiver();
|
|
784
1094
|
}
|
|
785
1095
|
}
|
|
786
1096
|
|
|
787
1097
|
// Legacy
|
|
788
1098
|
// This isn't the ideal way to do this on Android. All drawables/colors/themes shoudld
|
|
789
1099
|
// be targed using styling. See `https://docs.jwplayer.com/players/docs/android-styling-guide`
|
|
790
|
-
// for more information on how best to override the JWP styles using XML. If you are unsure of a
|
|
1100
|
+
// for more information on how best to override the JWP styles using XML. If you are unsure of a
|
|
791
1101
|
// color/drawable/theme, open an `Ask` issue.
|
|
792
1102
|
if (mColors != null) {
|
|
793
1103
|
if (mColors.hasKey("backgroundColor")) {
|
|
@@ -844,8 +1154,6 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
844
1154
|
|
|
845
1155
|
if (backgroundAudioEnabled) {
|
|
846
1156
|
audioManager = (AudioManager) simpleContext.getSystemService(Context.AUDIO_SERVICE);
|
|
847
|
-
// Throws a fatal error if using a playlistURL instead of manually created playlist
|
|
848
|
-
// Related to SDK-11346
|
|
849
1157
|
mMediaServiceController = new MediaServiceController.Builder((AppCompatActivity) mActivity, mPlayer)
|
|
850
1158
|
.build();
|
|
851
1159
|
}
|
|
@@ -1103,7 +1411,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1103
1411
|
event.putString("message", "onPlayerAdWarning");
|
|
1104
1412
|
event.putInt("code", adWarningEvent.getCode());
|
|
1105
1413
|
event.putInt("adErrorCode", adWarningEvent.getAdErrorCode());
|
|
1106
|
-
event.putString("
|
|
1414
|
+
event.putString("warning", adWarningEvent.getMessage());
|
|
1107
1415
|
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topPlayerAdWarning", event);
|
|
1108
1416
|
}
|
|
1109
1417
|
|
|
@@ -1209,6 +1517,38 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1209
1517
|
|
|
1210
1518
|
}
|
|
1211
1519
|
|
|
1520
|
+
// Captions Events
|
|
1521
|
+
|
|
1522
|
+
@Override
|
|
1523
|
+
public void onCaptionsChanged(CaptionsChangedEvent captionsChangedEvent) {
|
|
1524
|
+
WritableMap event = Arguments.createMap();
|
|
1525
|
+
event.putString("message", "onCaptionsChanged");
|
|
1526
|
+
event.putInt("index", captionsChangedEvent.getCurrentTrack());
|
|
1527
|
+
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topCaptionsChanged", event);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
@Override
|
|
1531
|
+
public void onCaptionsList(CaptionsListEvent captionsListEvent) {
|
|
1532
|
+
WritableMap event = Arguments.createMap();
|
|
1533
|
+
List<Caption> captionTrackList = captionsListEvent.getCaptions();
|
|
1534
|
+
WritableArray captionTracks = Arguments.createArray();
|
|
1535
|
+
if (captionTrackList != null) {
|
|
1536
|
+
for(int i = 0; i < captionTrackList.size(); i++) {
|
|
1537
|
+
WritableMap captionTrack = Arguments.createMap();
|
|
1538
|
+
Caption track = captionTrackList.get(i);
|
|
1539
|
+
captionTrack.putString("file", track.getFile());
|
|
1540
|
+
captionTrack.putString("label", track.getLabel());
|
|
1541
|
+
captionTrack.putBoolean("default", track.isDefault());
|
|
1542
|
+
captionTracks.pushMap(captionTrack);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
event.putString("message", "onCaptionsList");
|
|
1546
|
+
event.putInt("index", captionsListEvent.getCurrentCaptionIndex());
|
|
1547
|
+
event.putArray("tracks", captionTracks);
|
|
1548
|
+
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topCaptionsList", event);
|
|
1549
|
+
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1212
1552
|
// Player Events
|
|
1213
1553
|
|
|
1214
1554
|
@Override
|
|
@@ -1257,6 +1597,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1257
1597
|
if (ex != null) {
|
|
1258
1598
|
event.putString("error", ex.toString());
|
|
1259
1599
|
event.putString("description", errorEvent.getMessage());
|
|
1600
|
+
event.putInt("errorCode", errorEvent.getErrorCode());
|
|
1260
1601
|
}
|
|
1261
1602
|
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topPlayerError", event);
|
|
1262
1603
|
|
|
@@ -1269,6 +1610,13 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1269
1610
|
doBindService();
|
|
1270
1611
|
requestAudioFocus();
|
|
1271
1612
|
}
|
|
1613
|
+
WritableMap onFirstFrame = Arguments.createMap();
|
|
1614
|
+
onFirstFrame.putString("message", "onLoaded");
|
|
1615
|
+
onFirstFrame.putDouble("loadTime", firstFrameEvent.getLoadTime());
|
|
1616
|
+
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
|
|
1617
|
+
getId(),
|
|
1618
|
+
"topFirstFrame",
|
|
1619
|
+
onFirstFrame);
|
|
1272
1620
|
}
|
|
1273
1621
|
|
|
1274
1622
|
@Override
|
|
@@ -1400,6 +1748,8 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1400
1748
|
public void onSetupError(SetupErrorEvent setupErrorEvent) {
|
|
1401
1749
|
WritableMap event = Arguments.createMap();
|
|
1402
1750
|
event.putString("message", "onSetupError");
|
|
1751
|
+
event.putString("errorMessage", setupErrorEvent.getMessage());
|
|
1752
|
+
event.putInt("errorCode", setupErrorEvent.getCode());
|
|
1403
1753
|
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topSetupPlayerError", event);
|
|
1404
1754
|
|
|
1405
1755
|
updateWakeLock(false);
|
|
@@ -1414,16 +1764,6 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1414
1764
|
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topTime", event);
|
|
1415
1765
|
}
|
|
1416
1766
|
|
|
1417
|
-
@Override
|
|
1418
|
-
public void onCaptionsChanged(CaptionsChangedEvent captionsChangedEvent) {
|
|
1419
|
-
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
@Override
|
|
1423
|
-
public void onCaptionsList(CaptionsListEvent captionsListEvent) {
|
|
1424
|
-
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
1767
|
@Override
|
|
1428
1768
|
public void onMeta(MetaEvent metaEvent) {
|
|
1429
1769
|
|
|
@@ -1443,6 +1783,17 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1443
1783
|
|
|
1444
1784
|
// Casting events
|
|
1445
1785
|
|
|
1786
|
+
private boolean mIsCastActive = false;
|
|
1787
|
+
|
|
1788
|
+
/**
|
|
1789
|
+
* Get if this player-view is currently casting
|
|
1790
|
+
*
|
|
1791
|
+
* @return true if casting
|
|
1792
|
+
*/
|
|
1793
|
+
public boolean getIsCastActive() {
|
|
1794
|
+
return mIsCastActive;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1446
1797
|
@Override
|
|
1447
1798
|
public void onCast(CastEvent castEvent) {
|
|
1448
1799
|
WritableMap event = Arguments.createMap();
|
|
@@ -1451,13 +1802,14 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1451
1802
|
event.putBoolean("active", castEvent.isActive());
|
|
1452
1803
|
event.putBoolean("available", castEvent.isAvailable());
|
|
1453
1804
|
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topCasting", event);
|
|
1805
|
+
mIsCastActive = castEvent.isActive();
|
|
1454
1806
|
// stop/start the background audio service if it's running and we're casting
|
|
1455
1807
|
if (castEvent.isActive()) {
|
|
1456
1808
|
doUnbindService();
|
|
1457
1809
|
} else {
|
|
1458
1810
|
if (backgroundAudioEnabled) {
|
|
1459
1811
|
mMediaServiceController = new MediaServiceController.Builder((AppCompatActivity) mActivity, mPlayer)
|
|
1460
|
-
|
|
1812
|
+
.build();
|
|
1461
1813
|
doBindService();
|
|
1462
1814
|
}
|
|
1463
1815
|
}
|