@npaw/forge-react-native-video 9.6.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/LICENSE +10 -0
- package/NPAWForgeReactNativeVideo.podspec +48 -0
- package/README.md +41 -0
- package/android/build.gradle +98 -0
- package/android/src/main/AndroidManifest.xml +14 -0
- package/android/src/main/java/com/npaw/forge/reactnative/video/NPAWForgeVideoModule.java +341 -0
- package/android/src/main/java/com/npaw/forge/reactnative/video/NPAWForgeVideoPackage.java +53 -0
- package/android/src/main/java/com/npaw/forge/reactnative/video/RNVideoAttachCoordinator.java +0 -0
- package/android/src/test/java/com/npaw/forge/reactnative/video/RNVideoAttachCoordinatorTest.java +218 -0
- package/ios/ForgeRNVPlayerPlugin.swift +80 -0
- package/ios/ForgeVideoModuleImpl.swift +347 -0
- package/ios/NPAWForgeVideoTurboModule.mm +89 -0
- package/package.json +34 -0
- package/src/index.ts +79 -0
- package/src/specs/NativeNPAWForgeVideo.ts +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Copyright (C) NPAW - All Rights Reserved
|
|
2
|
+
|
|
3
|
+
This software is protected under international copyright law. All rights
|
|
4
|
+
reserved and protected by the copyright holders. This software is confidential
|
|
5
|
+
and only available to authorized individuals with the permission of the
|
|
6
|
+
copyright holders. If you obtained this software and do not have permission,
|
|
7
|
+
please contact the copyright holders and delete it.
|
|
8
|
+
|
|
9
|
+
Use of this software requires an active NPAW commercial agreement. See
|
|
10
|
+
https://npaw.com for licensing and support.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Copyright (C) NPAW - All Rights Reserved
|
|
2
|
+
#
|
|
3
|
+
# This source code is protected under international copyright law. All rights
|
|
4
|
+
# reserved and protected by the copyright holders. This file is confidential and
|
|
5
|
+
# only available to authorized individuals with the permission of the copyright
|
|
6
|
+
# holders. If you encounter this file and do not have permission, please contact
|
|
7
|
+
# the copyright holders and delete this file.
|
|
8
|
+
#
|
|
9
|
+
#
|
|
10
|
+
# @author: Victor Soto <victor.soto@nicepeopleatwork.com>
|
|
11
|
+
# @description: CocoaPods spec for the @npaw/forge-react-native-video glue (Forge AVPlayerAdapter onto react-native-video's plugin API).
|
|
12
|
+
|
|
13
|
+
require "json"
|
|
14
|
+
|
|
15
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
16
|
+
|
|
17
|
+
Pod::Spec.new do |s|
|
|
18
|
+
s.name = "NPAWForgeReactNativeVideo"
|
|
19
|
+
s.version = package["version"]
|
|
20
|
+
s.summary = package["description"]
|
|
21
|
+
s.homepage = "https://npaw.com"
|
|
22
|
+
s.license = { :type => "Commercial", :text => "Copyright (C) NPAW - All Rights Reserved" }
|
|
23
|
+
s.authors = { "Victor Soto" => "victor.soto@nicepeopleatwork.com" }
|
|
24
|
+
s.platforms = { :ios => "13.0" }
|
|
25
|
+
s.source = { :path => "." }
|
|
26
|
+
|
|
27
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
28
|
+
s.swift_version = "5.9"
|
|
29
|
+
s.pod_target_xcconfig = {
|
|
30
|
+
"DEFINES_MODULE" => "YES",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
s.dependency "NPAWForgeReactNative"
|
|
34
|
+
s.dependency "react-native-video"
|
|
35
|
+
|
|
36
|
+
# FORGE_APPLE_CONSUMPTION BEGIN (staged: ForgeApple ships inside the core pod)
|
|
37
|
+
s.pod_target_xcconfig = {
|
|
38
|
+
"DEFINES_MODULE" => "YES",
|
|
39
|
+
"OTHER_SWIFT_FLAGS" => "$(inherited) -DFORGE_RN_HYDRATED",
|
|
40
|
+
}
|
|
41
|
+
# FORGE_APPLE_CONSUMPTION END
|
|
42
|
+
|
|
43
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
44
|
+
install_modules_dependencies(s)
|
|
45
|
+
else
|
|
46
|
+
s.dependency "React-Core"
|
|
47
|
+
end
|
|
48
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @npaw/forge-react-native-video
|
|
2
|
+
|
|
3
|
+
`react-native-video` player adapter for NPAW Forge: automatic playback and QoS
|
|
4
|
+
reporting by attaching to the player instance `react-native-video` already
|
|
5
|
+
manages.
|
|
6
|
+
|
|
7
|
+
Use of this package requires an active NPAW commercial agreement. See
|
|
8
|
+
[LICENSE](./LICENSE).
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @npaw/forge-react-native @npaw/forge-react-native-video
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires `react-native-video` v6 and React Native 0.75+ with the New
|
|
17
|
+
Architecture enabled.
|
|
18
|
+
|
|
19
|
+
### iOS
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
cd ios && pod install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Register the adapter alongside your `Video` component; the adapter attaches to
|
|
28
|
+
the underlying player and reports playback to the Forge session created with
|
|
29
|
+
[`@npaw/forge-react-native`](https://www.npmjs.com/package/@npaw/forge-react-native).
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { NPAWForge } from '@npaw/forge-react-native';
|
|
33
|
+
import { attachForgeVideo } from '@npaw/forge-react-native-video';
|
|
34
|
+
|
|
35
|
+
const video = await attachForgeVideo(forge, { identifier: 'main' });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Documentation
|
|
39
|
+
|
|
40
|
+
Integration guides and the API reference are published at
|
|
41
|
+
[npaw.com](https://npaw.com).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Copyright (C) NPAW - All Rights Reserved
|
|
2
|
+
//
|
|
3
|
+
// This source code is protected under international copyright law. All rights
|
|
4
|
+
// reserved and protected by the copyright holders. This file is confidential and
|
|
5
|
+
// only available to authorized individuals with the permission of the copyright
|
|
6
|
+
// holders. If you encounter this file and do not have permission, please contact
|
|
7
|
+
// the copyright holders and delete this file.
|
|
8
|
+
//
|
|
9
|
+
//
|
|
10
|
+
// @author: Victor Soto <victor.soto@nicepeopleatwork.com>
|
|
11
|
+
// @description: Android library build for the @npaw/forge-react-native-video glue module (Forge Media3 adapter onto react-native-video's plugin API). Pure-Java module so it builds under any host-app Kotlin version.
|
|
12
|
+
|
|
13
|
+
def isNewArchitectureEnabled() {
|
|
14
|
+
return project.hasProperty('newArchEnabled') && project.newArchEnabled == 'true'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
apply plugin: 'com.android.library'
|
|
18
|
+
if (isNewArchitectureEnabled()) {
|
|
19
|
+
apply plugin: 'com.facebook.react'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def safeExtGet(prop, fallback) {
|
|
23
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def forgeCoreVersion = project.hasProperty('NpawForge_forgeCoreVersion')
|
|
27
|
+
? project.property('NpawForge_forgeCoreVersion')
|
|
28
|
+
: (System.getenv('FORGE_CORE_VERSION') ?: '9.6.1')
|
|
29
|
+
|
|
30
|
+
def forgeAdapterVersion = project.hasProperty('NpawForge_forgeAdapterVersion')
|
|
31
|
+
? project.property('NpawForge_forgeAdapterVersion')
|
|
32
|
+
: (System.getenv('FORGE_ADAPTER_VERSION') ?: '9.6.1')
|
|
33
|
+
|
|
34
|
+
android {
|
|
35
|
+
namespace 'com.npaw.forge.reactnative.video'
|
|
36
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 35)
|
|
37
|
+
|
|
38
|
+
defaultConfig {
|
|
39
|
+
minSdkVersion safeExtGet('minSdkVersion', 23)
|
|
40
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 34)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
compileOptions {
|
|
44
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
45
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
testOptions {
|
|
49
|
+
unitTests.returnDefaultValues = true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
repositories {
|
|
54
|
+
mavenCentral()
|
|
55
|
+
google()
|
|
56
|
+
def localMavenRepo = System.getenv('FORGE_LOCAL_MAVEN_REPO')
|
|
57
|
+
if (localMavenRepo != null && !localMavenRepo.isEmpty()) {
|
|
58
|
+
maven { url uri(localMavenRepo) }
|
|
59
|
+
}
|
|
60
|
+
maven { url 'https://artifact.plugin.npaw.com/artifactory/plugins/android' }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The core RN library project name comes from React Native autolinking's
|
|
64
|
+
// package-name sanitization; probe the known spellings so this module works
|
|
65
|
+
// wherever the app's settings.gradle placed it.
|
|
66
|
+
def forgeCoreRnProject = [
|
|
67
|
+
':npaw_forge-react-native',
|
|
68
|
+
':forge-react-native',
|
|
69
|
+
':npaw-forge-react-native',
|
|
70
|
+
].collect { findProject(it) }.find { it != null }
|
|
71
|
+
|
|
72
|
+
def reactNativeVideoProject = findProject(':react-native-video')
|
|
73
|
+
|
|
74
|
+
// The Forge media3 adapter declares its player SDK compileOnly (runtime
|
|
75
|
+
// player detection), and react-native-video provides media3 at the app level;
|
|
76
|
+
// this module needs the same media3 surface on its own compile classpath.
|
|
77
|
+
def media3Version = safeExtGet('media3Version', '1.8.0')
|
|
78
|
+
|
|
79
|
+
dependencies {
|
|
80
|
+
implementation 'com.facebook.react:react-native:+'
|
|
81
|
+
// compileOnly + runtimeOnly split — see the core module's build.gradle:
|
|
82
|
+
// keeps the Kotlin-2.3 Forge graph off host-app Kotlin compile classpaths.
|
|
83
|
+
compileOnly "com.npaw.forge:forge-android-core:${forgeCoreVersion}"
|
|
84
|
+
runtimeOnly "com.npaw.forge:forge-android-core:${forgeCoreVersion}"
|
|
85
|
+
compileOnly "com.npaw.forge:forge-adapter-media3:${forgeAdapterVersion}"
|
|
86
|
+
runtimeOnly "com.npaw.forge:forge-adapter-media3:${forgeAdapterVersion}"
|
|
87
|
+
compileOnly "androidx.media3:media3-common:${media3Version}"
|
|
88
|
+
compileOnly "androidx.media3:media3-datasource:${media3Version}"
|
|
89
|
+
compileOnly "androidx.media3:media3-exoplayer:${media3Version}"
|
|
90
|
+
if (forgeCoreRnProject != null) {
|
|
91
|
+
implementation forgeCoreRnProject
|
|
92
|
+
}
|
|
93
|
+
if (reactNativeVideoProject != null) {
|
|
94
|
+
compileOnly reactNativeVideoProject
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
testImplementation 'junit:junit:4.13.2'
|
|
98
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Copyright (C) NPAW - All Rights Reserved
|
|
4
|
+
|
|
5
|
+
This source code is protected under international copyright law. All rights
|
|
6
|
+
reserved and protected by the copyright holders. This file is confidential and
|
|
7
|
+
only available to authorized individuals with the permission of the copyright
|
|
8
|
+
holders. If you encounter this file and do not have permission, please contact
|
|
9
|
+
the copyright holders and delete this file.
|
|
10
|
+
|
|
11
|
+
@author: Victor Soto <victor.soto@nicepeopleatwork.com>
|
|
12
|
+
@description: Manifest stub for the @npaw/forge-react-native-video Android library module.
|
|
13
|
+
-->
|
|
14
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) NPAW - All Rights Reserved
|
|
3
|
+
*
|
|
4
|
+
* This source code is protected under international copyright law. All rights
|
|
5
|
+
* reserved and protected by the copyright holders.
|
|
6
|
+
* This file is confidential and only available to authorized individuals with the
|
|
7
|
+
* permission of the copyright holders. If you encounter this file and do not have
|
|
8
|
+
* permission, please contact the copyright holders and delete this file.
|
|
9
|
+
*
|
|
10
|
+
*
|
|
11
|
+
* @author: Victor Soto <victor.soto@nicepeopleatwork.com>
|
|
12
|
+
* @description: react-native-video glue TurboModule for Android. Implements RNVExoplayerPlugin, smuggles the X-NPAW-Forge header token into the MediaItem tag via overrideMediaItemBuilder, caches player facts on the main thread, and delegates policy to RNVideoAttachCoordinator to bind Forge's Media3PlayerAdapter.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
package com.npaw.forge.reactnative.video;
|
|
16
|
+
|
|
17
|
+
import androidx.annotation.Nullable;
|
|
18
|
+
import android.os.Handler;
|
|
19
|
+
import android.os.Looper;
|
|
20
|
+
import androidx.media3.common.MediaItem;
|
|
21
|
+
import androidx.media3.datasource.DataSource;
|
|
22
|
+
import androidx.media3.exoplayer.ExoPlayer;
|
|
23
|
+
import androidx.media3.exoplayer.drm.DrmSessionManager;
|
|
24
|
+
import androidx.media3.exoplayer.source.MediaSource;
|
|
25
|
+
import com.brentvatne.common.api.Source;
|
|
26
|
+
import com.brentvatne.exoplayer.DRMManagerSpec;
|
|
27
|
+
import com.brentvatne.exoplayer.RNVExoplayerPlugin;
|
|
28
|
+
import com.brentvatne.react.ReactNativeVideoManager;
|
|
29
|
+
import com.facebook.react.bridge.Arguments;
|
|
30
|
+
import com.facebook.react.bridge.Promise;
|
|
31
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
32
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
33
|
+
import com.facebook.react.bridge.UiThreadUtil;
|
|
34
|
+
import com.facebook.react.bridge.WritableMap;
|
|
35
|
+
import com.npaw.forge.android.NPAWForge;
|
|
36
|
+
import com.npaw.forge.android.adapters.media3.Media3PlayerAdapter;
|
|
37
|
+
import com.npaw.forge.android.video.VideoAdapter;
|
|
38
|
+
import com.npaw.forge.reactnative.ForgeInstanceRegistry;
|
|
39
|
+
import java.util.HashMap;
|
|
40
|
+
import java.util.Locale;
|
|
41
|
+
import java.util.Map;
|
|
42
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
43
|
+
|
|
44
|
+
public class NPAWForgeVideoModule extends NativeNPAWForgeVideoSpec
|
|
45
|
+
implements RNVExoplayerPlugin {
|
|
46
|
+
|
|
47
|
+
public static final String NAME = "NPAWForgeVideoRN";
|
|
48
|
+
/** Header customers set on source.requestHeaders for multi-player matching. */
|
|
49
|
+
public static final String TOKEN_HEADER = "x-npaw-forge";
|
|
50
|
+
private static final String TAG_PREFIX = "npaw-forge:";
|
|
51
|
+
private static final String E_UNKNOWN_INSTANCE = "E_FORGE_UNKNOWN_INSTANCE";
|
|
52
|
+
|
|
53
|
+
private static final class PlayerFacts {
|
|
54
|
+
@Nullable volatile String token;
|
|
55
|
+
@Nullable volatile String uri;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private final ConcurrentHashMap<String, PlayerFacts> facts = new ConcurrentHashMap<>();
|
|
59
|
+
private final ConcurrentHashMap<String, ExoPlayer> playersById = new ConcurrentHashMap<>();
|
|
60
|
+
private final ConcurrentHashMap<String, Media3PlayerAdapter> adaptersByPlayerId =
|
|
61
|
+
new ConcurrentHashMap<>();
|
|
62
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
63
|
+
|
|
64
|
+
private final RNVideoAttachCoordinator<ExoPlayer> coordinator =
|
|
65
|
+
new RNVideoAttachCoordinator<>(
|
|
66
|
+
new RNVideoAttachCoordinator.PlayerIntrospection() {
|
|
67
|
+
@Nullable
|
|
68
|
+
@Override
|
|
69
|
+
public String currentToken(String playerId) {
|
|
70
|
+
PlayerFacts f = facts.get(playerId);
|
|
71
|
+
return f == null ? null : f.token;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Nullable
|
|
75
|
+
@Override
|
|
76
|
+
public String currentUri(String playerId) {
|
|
77
|
+
PlayerFacts f = facts.get(playerId);
|
|
78
|
+
return f == null ? null : f.uri;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
new RNVideoAttachCoordinator.Binder<ExoPlayer>() {
|
|
82
|
+
@Override
|
|
83
|
+
public void bind(String instanceId, String identifier, String playerId, ExoPlayer player) {
|
|
84
|
+
bindOnMain(instanceId, identifier, playerId, player);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Override
|
|
88
|
+
public void unbind(
|
|
89
|
+
String instanceId, String identifier, String playerId, boolean fireStop) {
|
|
90
|
+
unbindOnMain(instanceId, identifier, playerId, fireStop);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
public NPAWForgeVideoModule(ReactApplicationContext reactContext) {
|
|
95
|
+
super(reactContext);
|
|
96
|
+
ReactNativeVideoManager.Companion.getInstance().registerPlugin(this);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Override
|
|
100
|
+
public String getName() {
|
|
101
|
+
return NAME;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- spec surface ----
|
|
105
|
+
|
|
106
|
+
@Override
|
|
107
|
+
public void attach(String instanceId, String identifier, @Nullable ReadableMap options, Promise promise) {
|
|
108
|
+
NPAWForge forge = ForgeInstanceRegistry.resolve(instanceId);
|
|
109
|
+
if (forge == null) {
|
|
110
|
+
promise.reject(E_UNKNOWN_INSTANCE, "No NPAWForge instance '" + instanceId + "'");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
ensureVideo(forge, identifier);
|
|
114
|
+
|
|
115
|
+
Map<String, Object> map = options == null ? new HashMap<>() : options.toHashMap();
|
|
116
|
+
String headerToken = stringOrNull(map.get("headerToken"));
|
|
117
|
+
String sourceUri = stringOrNull(map.get("sourceUri"));
|
|
118
|
+
String policy = stringOrNull(map.get("policy"));
|
|
119
|
+
if (policy == null) {
|
|
120
|
+
policy = (headerToken != null || sourceUri != null) ? "match" : "auto";
|
|
121
|
+
}
|
|
122
|
+
boolean stopOnPlayerRemoved =
|
|
123
|
+
!(map.get("stopOnPlayerRemoved") instanceof Boolean)
|
|
124
|
+
|| (Boolean) map.get("stopOnPlayerRemoved");
|
|
125
|
+
|
|
126
|
+
RNVideoAttachCoordinator.AttachOutcome outcome =
|
|
127
|
+
coordinator.attach(
|
|
128
|
+
new RNVideoAttachCoordinator.AttachRequest(
|
|
129
|
+
instanceId, identifier, policy, headerToken, sourceUri, stopOnPlayerRemoved));
|
|
130
|
+
scheduleFactRefreshes();
|
|
131
|
+
promise.resolve(attachResult(outcome));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@Override
|
|
135
|
+
public void detach(String instanceId, String identifier, Promise promise) {
|
|
136
|
+
boolean removed = coordinator.detach(instanceId, identifier, true);
|
|
137
|
+
WritableMap result = Arguments.createMap();
|
|
138
|
+
result.putString("code", removed ? "accepted" : "ignored_invalid_state");
|
|
139
|
+
result.putBoolean("isAccepted", removed);
|
|
140
|
+
result.putString("attachment", "pending");
|
|
141
|
+
promise.resolve(result);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@Override
|
|
145
|
+
public void invalidate() {
|
|
146
|
+
ReactNativeVideoManager.Companion.getInstance().unregisterPlugin(this);
|
|
147
|
+
coordinator.clear();
|
|
148
|
+
facts.clear();
|
|
149
|
+
playersById.clear();
|
|
150
|
+
adaptersByPlayerId.clear();
|
|
151
|
+
super.invalidate();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---- RNVExoplayerPlugin ----
|
|
155
|
+
|
|
156
|
+
@Override
|
|
157
|
+
public void onInstanceCreated(String id, ExoPlayer player) {
|
|
158
|
+
facts.putIfAbsent(id, new PlayerFacts());
|
|
159
|
+
playersById.put(id, player);
|
|
160
|
+
UiThreadUtil.runOnUiThread(
|
|
161
|
+
() -> {
|
|
162
|
+
refreshFacts(id, player);
|
|
163
|
+
coordinator.playerCreated(id, player);
|
|
164
|
+
});
|
|
165
|
+
scheduleFactRefreshes();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@Override
|
|
169
|
+
public void onInstanceRemoved(String id, ExoPlayer player) {
|
|
170
|
+
playersById.remove(id);
|
|
171
|
+
facts.remove(id);
|
|
172
|
+
coordinator.playerRemoved(id);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---- RNVExoplayerPlugin optional hooks ----
|
|
176
|
+
|
|
177
|
+
@Nullable
|
|
178
|
+
@Override
|
|
179
|
+
public DRMManagerSpec getDRMManager() {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@Nullable
|
|
184
|
+
@Override
|
|
185
|
+
public DrmSessionManager overrideDrmSessionManager(Source source, DrmSessionManager drmSessionManager) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@Nullable
|
|
190
|
+
@Override
|
|
191
|
+
public DataSource.Factory overrideMediaDataSourceFactory(
|
|
192
|
+
Source source, DataSource.Factory mediaDataSourceFactory) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@Nullable
|
|
197
|
+
@Override
|
|
198
|
+
public MediaSource.Factory overrideMediaSourceFactory(
|
|
199
|
+
Source source, MediaSource.Factory mediaSourceFactory, DataSource.Factory mediaDataSourceFactory) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Nullable
|
|
204
|
+
@Override
|
|
205
|
+
public MediaItem.Builder overrideMediaItemBuilder(Source source, MediaItem.Builder mediaItemBuilder) {
|
|
206
|
+
String token = tokenFromHeaders(source.getHeaders());
|
|
207
|
+
if (token == null) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
// The tag rides the MediaItem so introspection can read it back from
|
|
211
|
+
// player.currentMediaItem on the main thread - deterministic multi-player
|
|
212
|
+
// correlation without joining override->create call ordering.
|
|
213
|
+
return mediaItemBuilder.setTag(TAG_PREFIX + token);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@Override
|
|
217
|
+
public boolean shouldDisableCache(Source source) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@Override
|
|
222
|
+
public void onInstanceCreated(String id, Object player) {
|
|
223
|
+
if (player instanceof ExoPlayer) {
|
|
224
|
+
onInstanceCreated(id, (ExoPlayer) player);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@Override
|
|
229
|
+
public void onInstanceRemoved(String id, Object player) {
|
|
230
|
+
if (player instanceof ExoPlayer) {
|
|
231
|
+
onInstanceRemoved(id, (ExoPlayer) player);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ---- internals ----
|
|
236
|
+
|
|
237
|
+
private void ensureVideo(NPAWForge forge, String identifier) {
|
|
238
|
+
if (forge.getVideoAdapter(identifier) == null) {
|
|
239
|
+
forge.videoBuilder().setIdentifier(identifier).build();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Sources settle after player creation; instead of a media3 Player.Listener
|
|
245
|
+
* (whose default-method desugaring is fragile across media3 versions), a
|
|
246
|
+
* few delayed main-thread refreshes converge token/URI facts and re-run the
|
|
247
|
+
* pending-match evaluation.
|
|
248
|
+
*/
|
|
249
|
+
private void scheduleFactRefreshes() {
|
|
250
|
+
for (long delayMs : new long[] {0L, 500L, 1500L, 3000L}) {
|
|
251
|
+
mainHandler.postDelayed(
|
|
252
|
+
() -> {
|
|
253
|
+
for (Map.Entry<String, ExoPlayer> entry : playersById.entrySet()) {
|
|
254
|
+
refreshFacts(entry.getKey(), entry.getValue());
|
|
255
|
+
}
|
|
256
|
+
coordinator.playerSourceSettled();
|
|
257
|
+
},
|
|
258
|
+
delayMs);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Main thread only. */
|
|
263
|
+
private void refreshFacts(String playerId, ExoPlayer player) {
|
|
264
|
+
PlayerFacts f = facts.computeIfAbsent(playerId, key -> new PlayerFacts());
|
|
265
|
+
MediaItem item = player.getCurrentMediaItem();
|
|
266
|
+
if (item != null && item.localConfiguration != null) {
|
|
267
|
+
Object tag = item.localConfiguration.tag;
|
|
268
|
+
f.token =
|
|
269
|
+
tag instanceof String && ((String) tag).startsWith(TAG_PREFIX)
|
|
270
|
+
? ((String) tag).substring(TAG_PREFIX.length())
|
|
271
|
+
: null;
|
|
272
|
+
f.uri = item.localConfiguration.uri.toString();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private void bindOnMain(String instanceId, String identifier, String playerId, ExoPlayer player) {
|
|
277
|
+
UiThreadUtil.runOnUiThread(
|
|
278
|
+
() -> {
|
|
279
|
+
NPAWForge forge = ForgeInstanceRegistry.resolve(instanceId);
|
|
280
|
+
if (forge == null || player == null) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
ensureVideo(forge, identifier);
|
|
284
|
+
VideoAdapter video = forge.getVideoAdapter(identifier);
|
|
285
|
+
if (video == null) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
Media3PlayerAdapter adapter = new Media3PlayerAdapter(player);
|
|
289
|
+
video.setPlayerAdapter(adapter);
|
|
290
|
+
adaptersByPlayerId.put(playerId, adapter);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private void unbindOnMain(String instanceId, String identifier, String playerId, boolean fireStop) {
|
|
295
|
+
UiThreadUtil.runOnUiThread(
|
|
296
|
+
() -> {
|
|
297
|
+
adaptersByPlayerId.remove(playerId);
|
|
298
|
+
VideoAdapter video = ForgeInstanceRegistry.resolveVideoAdapter(instanceId, identifier);
|
|
299
|
+
if (video == null) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (fireStop) {
|
|
303
|
+
video.fireStop();
|
|
304
|
+
}
|
|
305
|
+
video.setPlayerAdapter(null);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@Nullable
|
|
310
|
+
private static String tokenFromHeaders(@Nullable Map<String, String> headers) {
|
|
311
|
+
if (headers == null) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
|
315
|
+
if (entry.getKey() != null
|
|
316
|
+
&& TOKEN_HEADER.equals(entry.getKey().toLowerCase(Locale.ROOT))) {
|
|
317
|
+
return entry.getValue();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@Nullable
|
|
324
|
+
private static String stringOrNull(@Nullable Object value) {
|
|
325
|
+
if (value instanceof String && !((String) value).isEmpty()) {
|
|
326
|
+
return (String) value;
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private static WritableMap attachResult(RNVideoAttachCoordinator.AttachOutcome outcome) {
|
|
332
|
+
WritableMap result = Arguments.createMap();
|
|
333
|
+
result.putString("code", outcome.code);
|
|
334
|
+
result.putBoolean("isAccepted", outcome.accepted);
|
|
335
|
+
result.putString("attachment", outcome.attachment);
|
|
336
|
+
if (outcome.reason != null) {
|
|
337
|
+
result.putString("reason", outcome.reason);
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) NPAW - All Rights Reserved
|
|
3
|
+
*
|
|
4
|
+
* This source code is protected under international copyright law. All rights
|
|
5
|
+
* reserved and protected by the copyright holders.
|
|
6
|
+
* This file is confidential and only available to authorized individuals with the
|
|
7
|
+
* permission of the copyright holders. If you encounter this file and do not have
|
|
8
|
+
* permission, please contact the copyright holders and delete this file.
|
|
9
|
+
*
|
|
10
|
+
*
|
|
11
|
+
* @author: Victor Soto <victor.soto@nicepeopleatwork.com>
|
|
12
|
+
* @description: React package registering the NPAWForgeVideo TurboModule for autolinking.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
package com.npaw.forge.reactnative.video;
|
|
16
|
+
|
|
17
|
+
import com.facebook.react.TurboReactPackage;
|
|
18
|
+
import com.facebook.react.bridge.NativeModule;
|
|
19
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
20
|
+
import com.facebook.react.module.model.ReactModuleInfo;
|
|
21
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
22
|
+
import java.util.HashMap;
|
|
23
|
+
import java.util.Map;
|
|
24
|
+
import androidx.annotation.Nullable;
|
|
25
|
+
|
|
26
|
+
public class NPAWForgeVideoPackage extends TurboReactPackage {
|
|
27
|
+
|
|
28
|
+
@Nullable
|
|
29
|
+
@Override
|
|
30
|
+
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
|
|
31
|
+
if (NPAWForgeVideoModule.NAME.equals(name)) {
|
|
32
|
+
return new NPAWForgeVideoModule(reactContext);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Override
|
|
38
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
39
|
+
return () -> {
|
|
40
|
+
Map<String, ReactModuleInfo> modules = new HashMap<>();
|
|
41
|
+
modules.put(
|
|
42
|
+
NPAWForgeVideoModule.NAME,
|
|
43
|
+
new ReactModuleInfo(
|
|
44
|
+
NPAWForgeVideoModule.NAME,
|
|
45
|
+
NPAWForgeVideoModule.class.getName(),
|
|
46
|
+
false,
|
|
47
|
+
false,
|
|
48
|
+
false,
|
|
49
|
+
true));
|
|
50
|
+
return modules;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|