@jwplayer/jwplayer-react-native 1.0.0 → 1.0.2
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/CODEOWNERS +1 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +12 -2
- package/.github/ISSUE_TEMPLATE/feature_request.md +12 -2
- package/.github/ISSUE_TEMPLATE/implement.md +26 -0
- package/.github/ISSUE_TEMPLATE/question.md +12 -2
- package/README.md +25 -3
- 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 +2 -2
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerView.java +259 -11
- package/android/src/main/java/com/jwplayer/rnjwplayer/RNJWPlayerViewManager.java +4 -0
- package/android/src/main/java/com/jwplayer/rnjwplayer/Util.java +24 -3
- package/badges/version.svg +1 -1
- package/index.d.ts +14 -2
- package/index.js +8 -2
- package/ios/RNJWPlayer/RNJWPlayerView.swift +202 -196
- package/ios/RNJWPlayer/RNJWPlayerViewController.swift +27 -23
- package/ios/RNJWPlayer/RNJWPlayerViewManager.m +4 -0
- package/ios/RNJWPlayer/RNJWPlayerViewManager.swift +5 -3
- package/package.json +3 -3
- package/android/.gradle/8.1.1/checksums/checksums.lock +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.2/checksums/checksums.lock +0 -0
- package/android/.gradle/8.2/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.2/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.2/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.2/gc.properties +0 -0
- package/android/.gradle/config.properties +0 -2
- package/android/.idea/gradle.xml +0 -12
- package/android/.idea/migrations.xml +0 -10
- package/android/.idea/misc.xml +0 -10
- package/android/.idea/vcs.xml +0 -6
- package/android/.idea/workspace.xml +0 -54
- package/android/local.properties +0 -8
- /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
package/.github/CODEOWNERS
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# SDK Team
|
|
2
|
-
@jwplayer/team_sdks
|
|
2
|
+
* @jwplayer/team_sdks
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: Bug report
|
|
3
3
|
about: Create a report to help us improve
|
|
4
4
|
title: "[BUG]"
|
|
5
|
-
labels:
|
|
6
|
-
assignees: ''
|
|
5
|
+
labels: "needs-grooming"
|
|
6
|
+
assignees: '@jwplayer/team_sdks'
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -36,3 +36,13 @@ If you are having a build issue, we would like to know about your machine.
|
|
|
36
36
|
|
|
37
37
|
**Additional context**
|
|
38
38
|
Add any other context about the problem here.
|
|
39
|
+
|
|
40
|
+
**JWP Ticketing**
|
|
41
|
+
To expedite resolution and maintain confidentiality, submit a JWP [request](https://support.jwplayer.com/hc/en-us/requests/new) in addition to this new `Issue`. A JWP request offers the following benefits:
|
|
42
|
+
* Associates the `Issue` with your company
|
|
43
|
+
* Permits securely share sensitive information related to the bug, request, or question
|
|
44
|
+
* Enhances JWP's ability to track progress and provide timely updates
|
|
45
|
+
* Enables you to track and manage multiple issues through a single platform
|
|
46
|
+
|
|
47
|
+
| ℹ️ While `Issues` are valuable for open-source collaboration, a JWP request ensures that you will receive clear timelines and efficient support. |
|
|
48
|
+
|:---|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: Feature request
|
|
3
3
|
about: Suggest an idea for this project
|
|
4
4
|
title: "[FEAT]"
|
|
5
|
-
labels: ''
|
|
6
|
-
assignees: ''
|
|
5
|
+
labels: 'needs-grooming'
|
|
6
|
+
assignees: '@jwplayer/team_sdks'
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -18,3 +18,13 @@ A clear and concise description of any alternative solutions or features you've
|
|
|
18
18
|
|
|
19
19
|
**Additional context**
|
|
20
20
|
Add any other context or screenshots about the feature request here.
|
|
21
|
+
|
|
22
|
+
**JWP Ticketing**
|
|
23
|
+
To expedite resolution and maintain confidentiality, submit a JWP [request](https://support.jwplayer.com/hc/en-us/requests/new) in addition to this new `Issue`. A JWP request offers the following benefits:
|
|
24
|
+
* Associates the `Issue` with your company
|
|
25
|
+
* Permits securely share sensitive information related to the bug, request, or question
|
|
26
|
+
* Enhances JWP's ability to track progress and provide timely updates
|
|
27
|
+
* Enables you to track and manage multiple issues through a single platform
|
|
28
|
+
|
|
29
|
+
| ℹ️ While `Issues` are valuable for open-source collaboration, a JWP request ensures that you will receive clear timelines and efficient support. |
|
|
30
|
+
|:---|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Implementation
|
|
3
|
+
about: Request an implementation of a native feature or upgrade of SDK
|
|
4
|
+
title: "[IMPLEMENT]"
|
|
5
|
+
labels: 'needs-grooming'
|
|
6
|
+
assignees: '@jwplayer/team_sdks'
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**What needs to be implemented**
|
|
11
|
+
|
|
12
|
+
**How should it be implemented**
|
|
13
|
+
|
|
14
|
+
**Acceptance Criteria**
|
|
15
|
+
|
|
16
|
+
**Additional context**
|
|
17
|
+
|
|
18
|
+
**JWP Ticketing**
|
|
19
|
+
To expedite resolution and maintain confidentiality, submit a JWP [request](https://support.jwplayer.com/hc/en-us/requests/new) in addition to this new `Issue`. A JWP request offers the following benefits:
|
|
20
|
+
* Associates the `Issue` with your company
|
|
21
|
+
* Permits securely share sensitive information related to the bug, request, or question
|
|
22
|
+
* Enhances JWP's ability to track progress and provide timely updates
|
|
23
|
+
* Enables you to track and manage multiple issues through a single platform
|
|
24
|
+
|
|
25
|
+
| ℹ️ While `Issues` are valuable for open-source collaboration, a JWP request ensures that you will receive clear timelines and efficient support. |
|
|
26
|
+
|:---|
|
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
name: Question
|
|
3
3
|
about: Ask a question about the project or product
|
|
4
4
|
title: "[ASK]"
|
|
5
|
-
labels: ''
|
|
6
|
-
assignees: ''
|
|
5
|
+
labels: 'needs-grooming'
|
|
6
|
+
assignees: '@jwplayer/team_sdks'
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
**What isn't clear?**
|
|
11
11
|
What information are we not providing that you think we should?
|
|
12
|
+
|
|
13
|
+
**JWP Ticketing**
|
|
14
|
+
To expedite resolution and maintain confidentiality, submit a JWP [request](https://support.jwplayer.com/hc/en-us/requests/new) in addition to this new `Issue`. A JWP request offers the following benefits:
|
|
15
|
+
* Associates the `Issue` with your company
|
|
16
|
+
* Permits securely share sensitive information related to the bug, request, or question
|
|
17
|
+
* Enhances JWP's ability to track progress and provide timely updates
|
|
18
|
+
* Enables you to track and manage multiple issues through a single platform
|
|
19
|
+
|
|
20
|
+
| ℹ️ While `Issues` are valuable for open-source collaboration, a JWP request ensures that you will receive clear timelines and efficient support. |
|
|
21
|
+
|:---|
|
package/README.md
CHANGED
|
@@ -49,8 +49,9 @@ Follow these steps to add the library to your Android project:
|
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
For more details and guidance regarding configuration and requirements, see the [JWP Android SDK documentation](https://docs.jwplayer.com/players/docs/android-overview#requirements).
|
|
52
53
|
|
|
53
|
-
<br
|
|
54
|
+
<br />
|
|
54
55
|
|
|
55
56
|
### iOS
|
|
56
57
|
|
|
@@ -71,6 +72,8 @@ Follow these steps to add the library to your iOS project:
|
|
|
71
72
|
pod install
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
For more details and guidance regarding configuration and requirements, see the [JWP iOS SDK documentation](https://docs.jwplayer.com/players/docs/ios-overview#requirements).
|
|
76
|
+
|
|
74
77
|
<br /><br />
|
|
75
78
|
|
|
76
79
|
## Usage
|
|
@@ -138,7 +141,7 @@ Follow these steps to configure the media playback experience in your app:
|
|
|
138
141
|
: 'YOUR_IOS_SDK_KEY',
|
|
139
142
|
backgroundAudioEnabled: true,
|
|
140
143
|
autostart: true,
|
|
141
|
-
styling: {
|
|
144
|
+
styling: { // only (mostly) compatible with iOS
|
|
142
145
|
colors: {
|
|
143
146
|
timeslider: {
|
|
144
147
|
rail: "0000FF",
|
|
@@ -175,6 +178,7 @@ Follow these steps to configure the media playback experience in your app:
|
|
|
175
178
|
onPlayerError={event => this.onPlayerError(event)}
|
|
176
179
|
onBuffer={() => this.onBuffer()}
|
|
177
180
|
onTime={event => this.onTime(event)}
|
|
181
|
+
onLoaded={event => this.onLoaded(event)}
|
|
178
182
|
onFullScreen={() => this.onFullScreen()}
|
|
179
183
|
onFullScreenExit={() => this.onFullScreenExit()}
|
|
180
184
|
/>
|
|
@@ -238,7 +242,7 @@ Follow these steps to run the example project:
|
|
|
238
242
|
|
|
239
243
|
## Advanced Topics
|
|
240
244
|
|
|
241
|
-
[Advertising](#advertising) | [Background Audio](#background-audio) | [Casting](#casting) | [DRM](#drm) | [Picture in Picture (PiP)](#picture-in-picture-pip)
|
|
245
|
+
[Advertising](#advertising) | [Background Audio](#background-audio) | [Casting](#casting) | [DRM](#drm) | [Picture in Picture (PiP)](#picture-in-picture-pip) | [Styling](#styling)
|
|
242
246
|
|
|
243
247
|
<br />
|
|
244
248
|
|
|
@@ -408,6 +412,24 @@ If you use a different provider for DRM or this does not work for your use case,
|
|
|
408
412
|
|
|
409
413
|
<br /><br />
|
|
410
414
|
|
|
415
|
+
### Styling
|
|
416
|
+
|
|
417
|
+
[Android](#android-styling) | [iOS](#ios-styling)
|
|
418
|
+
|
|
419
|
+
#### Android Styling
|
|
420
|
+
The `styling` prop will not work when using the modern prop convention that matches the JWP Delivery API. Even when using the `forceLegacyConfig` prop, Android may not respect your choices.
|
|
421
|
+
|
|
422
|
+
Android styling is best handled through overring JWP IDs in your apps resources files. See the documentation [here](https://docs.jwplayer.com/players/docs/android-styling-guide).
|
|
423
|
+
|
|
424
|
+
A sample of overring a color via XML can be seen in this [colors file](Example/android/app/src/main/res/values/colors.xml). The color specified here is the default, but if you wish to change it, the color will be updated on the player.
|
|
425
|
+
|
|
426
|
+
<br />
|
|
427
|
+
|
|
428
|
+
#### iOS Styling
|
|
429
|
+
You can use the styling elements as defined in the [Legacy Readme](/docs/legacy_readme.md#styling).
|
|
430
|
+
|
|
431
|
+
<br /><br />
|
|
432
|
+
|
|
411
433
|
## Contributing
|
|
412
434
|
|
|
413
435
|
- All contributions should correlate to an open issue. We hope to avoid doubling the work between contributors.
|
package/RNJWPlayer.podspec
CHANGED
|
@@ -12,7 +12,7 @@ Pod::Spec.new do |s|
|
|
|
12
12
|
s.platform = :ios, "14.0"
|
|
13
13
|
s.source = { :git => "https://github.com/jwplayer/jwplayer-react-native.git", :tag => "v#{s.version}" }
|
|
14
14
|
s.source_files = "ios/RNJWPlayer/*.{h,m,swift}"
|
|
15
|
-
s.dependency 'JWPlayerKit', '
|
|
15
|
+
s.dependency 'JWPlayerKit', '>= 4.19.0'
|
|
16
16
|
s.dependency 'React-Core'
|
|
17
17
|
s.static_framework = true
|
|
18
18
|
s.info_plist = {
|
|
@@ -35,7 +35,7 @@ Pod::Spec.new do |s|
|
|
|
35
35
|
end
|
|
36
36
|
if defined?($RNJWPlayerUseGoogleIMA)
|
|
37
37
|
Pod::UI.puts "RNJWPlayer: enable IMA SDK"
|
|
38
|
-
s.dependency 'GoogleAds-IMA-iOS-SDK', '~>3.
|
|
38
|
+
s.dependency 'GoogleAds-IMA-iOS-SDK', '~> 3.22'
|
|
39
39
|
s.pod_target_xcconfig = {
|
|
40
40
|
'OTHER_SWIFT_FLAGS' => '$(inherited) -D USE_GOOGLE_IMA'
|
|
41
41
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#
|
|
2
|
-
gradle.version=8.
|
|
1
|
+
#Tue Aug 06 14:41:55 PDT 2024
|
|
2
|
+
gradle.version=8.9
|
package/android/build.gradle
CHANGED
|
@@ -66,7 +66,7 @@ allprojects {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
def jwPlayerVersion = "4.
|
|
69
|
+
def jwPlayerVersion = "4.18.3"
|
|
70
70
|
def exoplayerVersion = "2.18.7" // Deprecated. Use Media3 when targeting JW SDK > 4.16.0
|
|
71
71
|
def media3ExoVersion = "1.1.1"
|
|
72
72
|
|
|
@@ -82,7 +82,7 @@ dependencies {
|
|
|
82
82
|
|
|
83
83
|
// Ad dependencies
|
|
84
84
|
if (useIMA) {
|
|
85
|
-
implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.
|
|
85
|
+
implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.33.0'
|
|
86
86
|
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
|
10
10
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
|
11
11
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
|
12
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
|
12
13
|
<!-- READ_MEDIA_IMAGES, READ_MEDIA_VIDEO or READ_MEDIA_AUDIO.-->
|
|
13
14
|
|
|
14
15
|
<application>
|
|
15
16
|
<service
|
|
16
17
|
android:name="com.jwplayer.pub.api.background.MediaService"
|
|
18
|
+
android:foregroundServiceType="mediaPlayback"
|
|
17
19
|
android:exported="false">
|
|
18
20
|
<intent-filter>
|
|
19
21
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
|
@@ -2,7 +2,10 @@ package com.jwplayer.rnjwplayer;
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
import android.app.Activity;
|
|
5
|
+
import android.content.BroadcastReceiver;
|
|
5
6
|
import android.content.Context;
|
|
7
|
+
import android.content.Intent;
|
|
8
|
+
import android.content.IntentFilter;
|
|
6
9
|
import android.content.pm.ActivityInfo;
|
|
7
10
|
import android.graphics.Color;
|
|
8
11
|
import android.graphics.PorterDuff;
|
|
@@ -12,11 +15,15 @@ import android.media.AudioAttributes;
|
|
|
12
15
|
import android.media.AudioFocusRequest;
|
|
13
16
|
import android.media.AudioManager;
|
|
14
17
|
import android.os.Build;
|
|
18
|
+
import android.os.Handler;
|
|
19
|
+
import android.os.Looper;
|
|
15
20
|
import android.util.Log;
|
|
21
|
+
import android.view.Choreographer;
|
|
16
22
|
import android.view.View;
|
|
17
23
|
import android.view.ViewGroup;
|
|
18
24
|
import android.view.Window;
|
|
19
25
|
import android.view.WindowManager;
|
|
26
|
+
import android.widget.FrameLayout;
|
|
20
27
|
import android.widget.LinearLayout;
|
|
21
28
|
import android.widget.RelativeLayout;
|
|
22
29
|
|
|
@@ -96,7 +103,12 @@ import com.jwplayer.pub.api.events.listeners.AdvertisingEvents;
|
|
|
96
103
|
import com.jwplayer.pub.api.events.listeners.CastingEvents;
|
|
97
104
|
import com.jwplayer.pub.api.events.listeners.PipPluginEvents;
|
|
98
105
|
import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents;
|
|
106
|
+
import com.jwplayer.pub.api.fullscreen.ExtensibleFullscreenHandler;
|
|
107
|
+
import com.jwplayer.pub.api.fullscreen.FullscreenDialog;
|
|
99
108
|
import com.jwplayer.pub.api.fullscreen.FullscreenHandler;
|
|
109
|
+
import com.jwplayer.pub.api.fullscreen.delegates.DeviceOrientationDelegate;
|
|
110
|
+
import com.jwplayer.pub.api.fullscreen.delegates.DialogLayoutDelegate;
|
|
111
|
+
import com.jwplayer.pub.api.fullscreen.delegates.SystemUiDelegate;
|
|
100
112
|
import com.jwplayer.pub.api.license.LicenseUtil;
|
|
101
113
|
import com.jwplayer.pub.api.media.playlists.PlaylistItem;
|
|
102
114
|
import com.jwplayer.ui.views.CueMarkerSeekbar;
|
|
@@ -106,6 +118,7 @@ import java.util.Arrays;
|
|
|
106
118
|
import java.util.HashMap;
|
|
107
119
|
import java.util.List;
|
|
108
120
|
import java.util.Map;
|
|
121
|
+
import java.util.Objects;
|
|
109
122
|
|
|
110
123
|
import org.json.JSONObject;
|
|
111
124
|
|
|
@@ -183,6 +196,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
183
196
|
Boolean fullScreenOnLandscape = false;
|
|
184
197
|
Boolean portraitOnExitFullScreen = false;
|
|
185
198
|
Boolean exitFullScreenOnPortrait = false;
|
|
199
|
+
Boolean playerInModal = false;
|
|
186
200
|
|
|
187
201
|
Number currentPlayingIndex;
|
|
188
202
|
|
|
@@ -212,6 +226,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
212
226
|
private ThemedReactContext mThemedReactContext;
|
|
213
227
|
|
|
214
228
|
private MediaServiceController mMediaServiceController;
|
|
229
|
+
private PipHandlerReceiver mReceiver = null;
|
|
215
230
|
|
|
216
231
|
private void doBindService() {
|
|
217
232
|
if (mMediaServiceController != null) {
|
|
@@ -233,7 +248,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
233
248
|
}
|
|
234
249
|
|
|
235
250
|
private static Context getNonBuggyContext(ThemedReactContext reactContext,
|
|
236
|
-
|
|
251
|
+
ReactApplicationContext appContext) {
|
|
237
252
|
Context superContext = reactContext;
|
|
238
253
|
if (!contextHasBug(appContext.getCurrentActivity())) {
|
|
239
254
|
superContext = appContext.getCurrentActivity();
|
|
@@ -290,6 +305,8 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
290
305
|
|
|
291
306
|
public void destroyPlayer() {
|
|
292
307
|
if (mPlayer != null) {
|
|
308
|
+
unRegisterReceiver();
|
|
309
|
+
mPlayer.deregisterActivityForPip();
|
|
293
310
|
mPlayer.stop();
|
|
294
311
|
|
|
295
312
|
mPlayer.removeListeners(this,
|
|
@@ -348,7 +365,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
348
365
|
EventType.PIP_OPEN
|
|
349
366
|
);
|
|
350
367
|
|
|
351
|
-
mPlayer
|
|
368
|
+
mPlayer = null;
|
|
352
369
|
mPlayerView = null;
|
|
353
370
|
|
|
354
371
|
getReactContext().removeLifecycleEventListener(this);
|
|
@@ -427,12 +444,116 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
427
444
|
EventType.PIP_OPEN
|
|
428
445
|
);
|
|
429
446
|
|
|
430
|
-
|
|
447
|
+
if (playerInModal) {
|
|
448
|
+
mPlayer.setFullscreenHandler(createModalFullscreenHandler());
|
|
449
|
+
} else {
|
|
450
|
+
mPlayer.setFullscreenHandler(new fullscreenHandler());
|
|
451
|
+
}
|
|
431
452
|
|
|
432
453
|
mPlayer.allowBackgroundAudio(backgroundAudioEnabled);
|
|
433
454
|
}
|
|
434
455
|
}
|
|
435
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Helper to build the a generic `ExtensibleFullscreenHandler` with small tweaks to play nice with Modals
|
|
459
|
+
* @return {@link ExtensibleFullscreenHandler}
|
|
460
|
+
*/
|
|
461
|
+
private ExtensibleFullscreenHandler createModalFullscreenHandler() {
|
|
462
|
+
DeviceOrientationDelegate delegate = getDeviceOrientationDelegate();
|
|
463
|
+
FullscreenDialog dialog = new FullscreenDialog(
|
|
464
|
+
mActivity,
|
|
465
|
+
mActivity,
|
|
466
|
+
android.R.style.Theme_Black_NoTitleBar_Fullscreen
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
return new ExtensibleFullscreenHandler(
|
|
470
|
+
new DialogLayoutDelegate(
|
|
471
|
+
mPlayerView,
|
|
472
|
+
dialog
|
|
473
|
+
),
|
|
474
|
+
delegate,
|
|
475
|
+
new SystemUiDelegate(
|
|
476
|
+
mActivity,
|
|
477
|
+
mActivity.getLifecycle(),
|
|
478
|
+
new Handler(),
|
|
479
|
+
dialog.getWindow().getDecorView()
|
|
480
|
+
)
|
|
481
|
+
) {
|
|
482
|
+
@Override
|
|
483
|
+
public void onFullscreenRequested() {
|
|
484
|
+
// if landscape is priorty we have to turn off full-screen portrait before allowing
|
|
485
|
+
// the default call for full-screen
|
|
486
|
+
mPlayer.allowFullscreenPortrait(!landscapeOnFullScreen);
|
|
487
|
+
super.onFullscreenRequested();
|
|
488
|
+
// safely set it back on UI thread after work can be finished
|
|
489
|
+
final Handler handler = new Handler(Looper.getMainLooper());
|
|
490
|
+
handler.postDelayed(() -> {
|
|
491
|
+
if (mPlayer != null) {
|
|
492
|
+
mPlayer.allowFullscreenPortrait(true);
|
|
493
|
+
}
|
|
494
|
+
}, 100);
|
|
495
|
+
WritableMap eventEnterFullscreen = Arguments.createMap();
|
|
496
|
+
eventEnterFullscreen.putString("message", "onFullscreenRequested");
|
|
497
|
+
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
|
|
498
|
+
getId(),
|
|
499
|
+
"topFullScreenRequested",
|
|
500
|
+
eventEnterFullscreen);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@Override
|
|
504
|
+
public void onFullscreenExitRequested() {
|
|
505
|
+
super.onFullscreenExitRequested();
|
|
506
|
+
|
|
507
|
+
WritableMap eventExitFullscreen = Arguments.createMap();
|
|
508
|
+
eventExitFullscreen.putString("message", "onFullscreenExitRequested");
|
|
509
|
+
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
|
|
510
|
+
getId(),
|
|
511
|
+
"topFullScreenExitRequested",
|
|
512
|
+
eventExitFullscreen);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Add logic here for your custom orientation implementation
|
|
519
|
+
*
|
|
520
|
+
* @return Default {@link DeviceOrientationDelegate}
|
|
521
|
+
*/
|
|
522
|
+
private DeviceOrientationDelegate getDeviceOrientationDelegate() {
|
|
523
|
+
DeviceOrientationDelegate delegate = new DeviceOrientationDelegate(
|
|
524
|
+
mActivity,
|
|
525
|
+
mActivity.getLifecycle(),
|
|
526
|
+
new Handler()
|
|
527
|
+
) {
|
|
528
|
+
@Override
|
|
529
|
+
public void setFullscreen(boolean fullscreen) {
|
|
530
|
+
super.setFullscreen(fullscreen);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
@Override
|
|
534
|
+
public void onAllowRotationChanged(boolean allowRotation) {
|
|
535
|
+
super.onAllowRotationChanged(allowRotation);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@Override
|
|
539
|
+
protected void doRotation(boolean fullscreen, boolean allowFullscreenPortrait) {
|
|
540
|
+
super.doRotation(fullscreen, allowFullscreenPortrait);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
@Override
|
|
544
|
+
protected void doRotationListener() {
|
|
545
|
+
super.doRotationListener();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
@Override
|
|
549
|
+
public void onAllowFullscreenPortrait(boolean allowFullscreenPortrait) {
|
|
550
|
+
super.onAllowFullscreenPortrait(allowFullscreenPortrait);
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
delegate.onAllowRotationChanged(true);
|
|
554
|
+
return delegate;
|
|
555
|
+
}
|
|
556
|
+
|
|
436
557
|
private class fullscreenHandler implements FullscreenHandler {
|
|
437
558
|
ViewGroup mPlayerViewContainer = (ViewGroup) mPlayerView.getParent();
|
|
438
559
|
private View mDecorView;
|
|
@@ -504,8 +625,15 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
504
625
|
mPlayerViewContainer.addView(mPlayerView, new ViewGroup.LayoutParams(
|
|
505
626
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
506
627
|
ViewGroup.LayoutParams.MATCH_PARENT));
|
|
507
|
-
|
|
508
|
-
|
|
628
|
+
// returning from full-screen portrait requires a different measure
|
|
629
|
+
if (mActivity.getResources().getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
630
|
+
) {
|
|
631
|
+
mPlayerView.layout(mPlayerView.getLeft(), mPlayerViewContainer.getTop(),
|
|
632
|
+
mPlayerViewContainer.getMeasuredWidth(), mPlayerViewContainer.getBottom());
|
|
633
|
+
} else {
|
|
634
|
+
mPlayerView.layout(mPlayerViewContainer.getLeft(), mPlayerViewContainer.getTop(),
|
|
635
|
+
mPlayerViewContainer.getRight(), mPlayerViewContainer.getBottom());
|
|
636
|
+
}
|
|
509
637
|
}
|
|
510
638
|
});
|
|
511
639
|
|
|
@@ -519,12 +647,12 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
519
647
|
|
|
520
648
|
@Override
|
|
521
649
|
public void onAllowRotationChanged(boolean b) {
|
|
522
|
-
Log.e(TAG, "onAllowRotationChanged: " + b
|
|
650
|
+
Log.e(TAG, "onAllowRotationChanged: " + b);
|
|
523
651
|
}
|
|
524
652
|
|
|
525
653
|
@Override
|
|
526
654
|
public void onAllowFullscreenPortraitChanged(boolean allowFullscreenPortrait) {
|
|
527
|
-
Log.e(TAG, "onAllowFullscreenPortraitChanged: " + allowFullscreenPortrait
|
|
655
|
+
Log.e(TAG, "onAllowFullscreenPortraitChanged: " + allowFullscreenPortrait);
|
|
528
656
|
}
|
|
529
657
|
|
|
530
658
|
@Override
|
|
@@ -540,10 +668,96 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
540
668
|
}
|
|
541
669
|
}
|
|
542
670
|
|
|
671
|
+
private ArrayList<Integer> rootViewChildrenOriginalVisibility = new ArrayList<Integer>();
|
|
672
|
+
|
|
673
|
+
private class PipHandlerReceiver extends BroadcastReceiver {
|
|
674
|
+
|
|
675
|
+
@Override
|
|
676
|
+
public void onReceive(Context context, Intent intent) {
|
|
677
|
+
if (intent == null) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (Objects.equals(intent.getAction(), "onPictureInPictureModeChanged")) {
|
|
681
|
+
if (intent.hasExtra("newConfig") && intent.hasExtra("isInPictureInPictureMode")) {
|
|
682
|
+
// Tell the JWP SDK we are toggling so it can handle toolbar / internal setup
|
|
683
|
+
mPlayer.onPictureInPictureModeChanged(intent.getBooleanExtra("isInPictureInPictureMode", false), intent.getParcelableExtra("newConfig"));
|
|
684
|
+
|
|
685
|
+
View decorView = mActivity.getWindow().getDecorView();
|
|
686
|
+
ViewGroup rootView = decorView.findViewById(android.R.id.content);
|
|
687
|
+
|
|
688
|
+
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
|
|
689
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
690
|
+
ViewGroup.LayoutParams.MATCH_PARENT);
|
|
691
|
+
|
|
692
|
+
if (intent.getBooleanExtra("isInPictureInPictureMode", false)) {
|
|
693
|
+
// Going into Picture in Picture
|
|
694
|
+
ViewGroup parent = (ViewGroup) mPlayerView.getParent();
|
|
695
|
+
|
|
696
|
+
// Remove the player view temporarily
|
|
697
|
+
if (parent != null) {
|
|
698
|
+
parent.removeView(mPlayerView);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Hide all views but player view and keep a handle on them for later
|
|
702
|
+
for (int i = 0; i < rootView.getChildCount(); i++) {
|
|
703
|
+
if (rootView.getChildAt(i) != mPlayerView) {
|
|
704
|
+
rootViewChildrenOriginalVisibility.add(rootView.getChildAt(i).getVisibility());
|
|
705
|
+
rootView.getChildAt(i).setVisibility(View.GONE);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
// Add player view back (This is safe since the JWP SDK has already calculated the PiP size/aspect off the View)
|
|
709
|
+
rootView.addView(mPlayerView, layoutParams);
|
|
710
|
+
} else {
|
|
711
|
+
// Exiting Picture in Picture
|
|
712
|
+
|
|
713
|
+
// Toggle controls to ensure we don't lose them -- weird UX bug fix where controls got lost
|
|
714
|
+
mPlayer.setForceControlsVisibility(true);
|
|
715
|
+
mPlayer.setForceControlsVisibility(false);
|
|
716
|
+
|
|
717
|
+
// Strip player view
|
|
718
|
+
rootView.removeView(mPlayerView);
|
|
719
|
+
|
|
720
|
+
// Add visibility back to any other controls
|
|
721
|
+
for (int i = 0; i < rootView.getChildCount(); i++) {
|
|
722
|
+
rootView.getChildAt(i).setVisibility(rootViewChildrenOriginalVisibility.get(i));
|
|
723
|
+
}
|
|
724
|
+
// Clear our list of views
|
|
725
|
+
rootViewChildrenOriginalVisibility.clear();
|
|
726
|
+
// Add player view back in main spot
|
|
727
|
+
addView(mPlayerView, 0, layoutParams);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
private void registerReceiver() {
|
|
735
|
+
mReceiver = new PipHandlerReceiver();
|
|
736
|
+
IntentFilter intentFilter = new IntentFilter("onPictureInPictureModeChanged");
|
|
737
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
738
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
739
|
+
// Must be Exported to get intents
|
|
740
|
+
mActivity.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED);
|
|
741
|
+
} else {
|
|
742
|
+
mActivity.registerReceiver(mReceiver, intentFilter); // Safe API level < 34
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
mActivity.registerReceiver(mReceiver, intentFilter); // Safe API level < 34
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private void unRegisterReceiver() {
|
|
750
|
+
if (mReceiver != null) {
|
|
751
|
+
mActivity.unregisterReceiver(mReceiver);
|
|
752
|
+
mReceiver = null;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
}
|
|
756
|
+
|
|
543
757
|
public void setConfig(ReadableMap prop) {
|
|
544
758
|
if (mConfig == null || !mConfig.equals(prop)) {
|
|
545
759
|
if (mConfig != null && isOnlyDiff(prop, "playlist") && mPlayer != null) { // still safe check, even with JW
|
|
546
|
-
|
|
760
|
+
// JSON change
|
|
547
761
|
PlayerConfig oldConfig = mPlayer.getConfig();
|
|
548
762
|
PlayerConfig config = new PlayerConfig.Builder()
|
|
549
763
|
.autostart(oldConfig.getAutostart())
|
|
@@ -610,7 +824,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
610
824
|
|
|
611
825
|
boolean playlistNotTheSame(ReadableMap prop) {
|
|
612
826
|
return prop.hasKey("playlist") && mPlaylistProp != prop.getArray("playlist") && !Arrays
|
|
613
|
-
.deepEquals(new ReadableArray[]
|
|
827
|
+
.deepEquals(new ReadableArray[]{mPlaylistProp}, new ReadableArray[]{prop.getArray("playlist")});
|
|
614
828
|
}
|
|
615
829
|
|
|
616
830
|
private void setupPlayer(ReadableMap prop) {
|
|
@@ -621,7 +835,7 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
621
835
|
PlayerConfig jwConfig = null;
|
|
622
836
|
Boolean forceLegacy = prop.hasKey("forceLegacyConfig") ? prop.getBoolean("forceLegacyConfig") : false;
|
|
623
837
|
Boolean isJwConfig = false;
|
|
624
|
-
if(!forceLegacy){
|
|
838
|
+
if (!forceLegacy) {
|
|
625
839
|
try {
|
|
626
840
|
obj = MapUtil.toJSONObject(prop);
|
|
627
841
|
jwConfig = JsonHelper.parseConfigJson(obj);
|
|
@@ -761,6 +975,18 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
761
975
|
mPlayerView.fullScreenOnLandscape = fullScreenOnLandscape;
|
|
762
976
|
}
|
|
763
977
|
|
|
978
|
+
if (prop.hasKey("landscapeOnFullScreen")) {
|
|
979
|
+
landscapeOnFullScreen = prop.getBoolean("landscapeOnFullScreen");
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (prop.hasKey("portraitOnExitFullScreen")) {
|
|
983
|
+
portraitOnExitFullScreen = prop.getBoolean("fullScreenOnLandscape");
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (prop.hasKey("playerInModal")) {
|
|
987
|
+
playerInModal = prop.getBoolean("playerInModal");
|
|
988
|
+
}
|
|
989
|
+
|
|
764
990
|
if (prop.hasKey("exitFullScreenOnPortrait")) {
|
|
765
991
|
exitFullScreenOnPortrait = prop.getBoolean("exitFullScreenOnPortrait");
|
|
766
992
|
mPlayerView.exitFullScreenOnPortrait = exitFullScreenOnPortrait;
|
|
@@ -778,14 +1004,19 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
778
1004
|
if (mActivity != null && prop.hasKey("pipEnabled")) {
|
|
779
1005
|
boolean pipEnabled = prop.getBoolean("pipEnabled");
|
|
780
1006
|
if (pipEnabled) {
|
|
1007
|
+
registerReceiver();
|
|
781
1008
|
mPlayer.registerActivityForPip(mActivity, mActivity.getSupportActionBar());
|
|
782
1009
|
} else {
|
|
783
1010
|
mPlayer.deregisterActivityForPip();
|
|
1011
|
+
unRegisterReceiver();
|
|
784
1012
|
}
|
|
785
1013
|
}
|
|
786
1014
|
|
|
787
1015
|
// Legacy
|
|
788
|
-
// This isn't the ideal way to do this on Android
|
|
1016
|
+
// This isn't the ideal way to do this on Android. All drawables/colors/themes shoudld
|
|
1017
|
+
// be targed using styling. See `https://docs.jwplayer.com/players/docs/android-styling-guide`
|
|
1018
|
+
// for more information on how best to override the JWP styles using XML. If you are unsure of a
|
|
1019
|
+
// color/drawable/theme, open an `Ask` issue.
|
|
789
1020
|
if (mColors != null) {
|
|
790
1021
|
if (mColors.hasKey("backgroundColor")) {
|
|
791
1022
|
mPlayerView.setBackgroundColor(Color.parseColor("#" + mColors.getString("backgroundColor")));
|
|
@@ -1266,6 +1497,13 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1266
1497
|
doBindService();
|
|
1267
1498
|
requestAudioFocus();
|
|
1268
1499
|
}
|
|
1500
|
+
WritableMap onFirstFrame = Arguments.createMap();
|
|
1501
|
+
onFirstFrame.putString("message", "onLoaded");
|
|
1502
|
+
onFirstFrame.putDouble("loadTime", firstFrameEvent.getLoadTime());
|
|
1503
|
+
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(
|
|
1504
|
+
getId(),
|
|
1505
|
+
"topFirstFrame",
|
|
1506
|
+
onFirstFrame);
|
|
1269
1507
|
}
|
|
1270
1508
|
|
|
1271
1509
|
@Override
|
|
@@ -1448,6 +1686,16 @@ public class RNJWPlayerView extends RelativeLayout implements
|
|
|
1448
1686
|
event.putBoolean("active", castEvent.isActive());
|
|
1449
1687
|
event.putBoolean("available", castEvent.isAvailable());
|
|
1450
1688
|
getReactContext().getJSModule(RCTEventEmitter.class).receiveEvent(getId(), "topCasting", event);
|
|
1689
|
+
// stop/start the background audio service if it's running and we're casting
|
|
1690
|
+
if (castEvent.isActive()) {
|
|
1691
|
+
doUnbindService();
|
|
1692
|
+
} else {
|
|
1693
|
+
if (backgroundAudioEnabled) {
|
|
1694
|
+
mMediaServiceController = new MediaServiceController.Builder((AppCompatActivity) mActivity, mPlayer)
|
|
1695
|
+
.build();
|
|
1696
|
+
doBindService();
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1451
1699
|
}
|
|
1452
1700
|
|
|
1453
1701
|
// LifecycleEventListener
|