diff --git a/android/build.gradle b/android/build.gradle index 28d555fe5b..54179b180a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -43,6 +43,7 @@ def ExoplayerDependenciesList = [ "useExoplayerHls", "useExoplayerRtsp", ] +def media3_buildFromSource = safeExtGet('buildFromMedia3Source').toBoolean() ?: false def ExoplayerDependencies = ExoplayerDependenciesList.collectEntries { property -> [(property): safeExtGet(property)?.toBoolean() ?: false] @@ -52,14 +53,17 @@ ExoplayerDependenciesList.each { propertyName -> def propertyValue = ExoplayerDependencies[propertyName] println "$propertyName: $propertyValue" } +println "buildFromSource: $media3_buildFromSource" // This string is used to define build path. // As react native build output directory is react-native path of the module. // We need to force a new path on each configuration change. // If you add a new build parameter, please add the new value in this string -def configStringPath = ExoplayerDependencies.collect { property, value -> - property + value -}.join('').md5() +def configStringPath = ExoplayerDependencies + .collect { property, value -> property + value} + .join('') + .concat("buildFromSource:$media3_buildFromSource") + .md5() if (isNewArchitectureEnabled()) { apply plugin: "com.facebook.react" @@ -189,46 +193,87 @@ dependencies { implementation "androidx.activity:activity-ktx:$androidxActivity_version" // For media playback using ExoPlayer - implementation "androidx.media3:media3-exoplayer:$media3_version" + if (media3_buildFromSource) { + implementation(project(":media-lib-exoplayer")) + } else { + implementation "androidx.media3:media3-exoplayer:$media3_version" + } if (ExoplayerDependencies["useExoplayerSmoothStreaming"]) { // For Smooth Streaming playback support with ExoPlayer - implementation "androidx.media3:media3-exoplayer-smoothstreaming:$media3_version" + if (media3_buildFromSource) { + implementation(project(":media-lib-exoplayer-smoothstreaming")) + } else { + implementation "androidx.media3:media3-exoplayer-smoothstreaming:$media3_version" + } } if (ExoplayerDependencies["useExoplayerDash"]) { // For DASH playback support with ExoPlayer - implementation "androidx.media3:media3-exoplayer-dash:$media3_version" + if (media3_buildFromSource) { + implementation(project(":media-lib-exoplayer-dash")) + } else { + implementation "androidx.media3:media3-exoplayer-dash:$media3_version" + } } if (ExoplayerDependencies["useExoplayerHls"]) { - // For HLS playback support with ExoPlayer - implementation "androidx.media3:media3-exoplayer-hls:$media3_version" + // For HLS playback support with ExoPlayer + if (media3_buildFromSource) { + implementation(project(":media-lib-exoplayer-hls")) + } else { + implementation "androidx.media3:media3-exoplayer-hls:$media3_version" + } } // For RTSP playback support with ExoPlayer if (ExoplayerDependencies["useExoplayerRtsp"]) { - implementation "androidx.media3:media3-exoplayer-rtsp:$media3_version" + if (media3_buildFromSource) { + implementation(project(":media-lib-exoplayer-rtsp")) + } else { + implementation "androidx.media3:media3-exoplayer-rtsp:$media3_version" + } } // For ad insertion using the Interactive Media Ads SDK with ExoPlayer if (ExoplayerDependencies["useExoplayerIMA"]) { - implementation "androidx.media3:media3-exoplayer-ima:$media3_version" + if (media3_buildFromSource) { + implementation(project(":media-lib-exoplayer-ima")) + } else { + implementation "androidx.media3:media3-exoplayer-ima:$media3_version" + } } - // For loading data using the OkHttp network stack - implementation "androidx.media3:media3-datasource-okhttp:$media3_version" + if (media3_buildFromSource) { + // For loading data using the OkHttp network stack + implementation(project(":media-lib-datasource-okhttp")) - // For building media playback UIs - implementation "androidx.media3:media3-ui:$media3_version" + // For building media playback UIs + implementation(project(":media-lib-ui")) - // For exposing and controlling media sessions - implementation "androidx.media3:media3-session:$media3_version" + // For exposing and controlling media sessions + implementation(project(":media-lib-session")) - // Common functionality for loading data - implementation "androidx.media3:media3-datasource:$media3_version" - // Common functionality used across multiple media libraries - implementation "androidx.media3:media3-common:$media3_version" + // Common functionality for loading data + implementation(project(":media-lib-datasource")) + // Common functionality used across multiple media libraries + implementation(project(":media-lib-common")) + } else { + // For loading data using the OkHttp network stack + implementation "androidx.media3:media3-datasource-okhttp:$media3_version" + + // For building media playback UIs + implementation "androidx.media3:media3-ui:$media3_version" + + // For exposing and controlling media sessions + implementation "androidx.media3:media3-session:$media3_version" + + // Common functionality for loading data + implementation "androidx.media3:media3-datasource:$media3_version" + + // Common functionality used across multiple media libraries + implementation "androidx.media3:media3-common:$media3_version" + } implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } diff --git a/android/gradle.properties b/android/gradle.properties index f19a0a192f..32cd544dc5 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -12,3 +12,4 @@ RNVideo_useExoplayerDash=true RNVideo_useExoplayerHls=true RNVideo_androidxCoreVersion=1.9.0 RNVideo_androidxActivityVersion=1.7.0 +RNVideo_buildFromMedia3Source=false diff --git a/android/src/main/java/androidx/media3/exoplayer/hls/HlsMediaSource.java b/android/src/main/java/androidx/media3/exoplayer/hls/HlsMediaSource.java index 69f11f1f16..478bcaeecc 100644 --- a/android/src/main/java/androidx/media3/exoplayer/hls/HlsMediaSource.java +++ b/android/src/main/java/androidx/media3/exoplayer/hls/HlsMediaSource.java @@ -30,5 +30,9 @@ public int[] getSupportedTypes() { public MediaSource createMediaSource(MediaItem mediaItem) { return null; } + + public Factory setAllowChunklessPreparation(boolean allowChunklessPreparation) { + return this; + } } } diff --git a/android/src/main/java/com/brentvatne/common/api/ViewType.kt b/android/src/main/java/com/brentvatne/common/api/ViewType.kt new file mode 100644 index 0000000000..c171a231a2 --- /dev/null +++ b/android/src/main/java/com/brentvatne/common/api/ViewType.kt @@ -0,0 +1,19 @@ +package com.brentvatne.common.api + +internal object ViewType { + /** + * View used will be a TextureView. + */ + const val VIEW_TYPE_TEXTURE = 0 + + /** + * View used will be a SurfaceView. + */ + const val VIEW_TYPE_SURFACE = 1 + + /** + * View used will be a SurfaceView with secure flag set. + */ + const val VIEW_TYPE_SURFACE_SECURE = 2 + annotation class ViewType +} diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index e254975f40..96c2f91862 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -26,12 +26,14 @@ import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SubtitleStyle; +import com.brentvatne.common.api.ViewType; +import com.brentvatne.common.toolbox.DebugLog; import com.google.common.collect.ImmutableList; import java.util.List; public final class ExoPlayerView extends FrameLayout implements AdViewProvider { - + private final static String TAG = "ExoPlayerView"; private View surfaceView; private final View shutterView; private final SubtitleView subtitleLayout; @@ -42,8 +44,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { private final ViewGroup.LayoutParams layoutParams; private final FrameLayout adOverlayFrameLayout; - private boolean useTextureView = true; - private boolean useSecureView = false; + private @ViewType.ViewType int viewType = ViewType.VIEW_TYPE_SURFACE; private boolean hideShutterView = false; public ExoPlayerView(Context context) { @@ -81,7 +82,7 @@ public ExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) { subtitleLayout.setUserDefaultStyle(); subtitleLayout.setUserDefaultTextSize(); - updateSurfaceView(); + updateSurfaceView(viewType); adOverlayFrameLayout = new FrameLayout(context); @@ -134,28 +135,36 @@ public void setShutterColor(Integer color) { shutterView.setBackgroundColor(color); } - private void updateSurfaceView() { - View view; - if (!useTextureView || useSecureView) { - view = new SurfaceView(context); - if (useSecureView) { - ((SurfaceView)view).setSecure(true); + public void updateSurfaceView(@ViewType.ViewType int viewType) { + this.viewType = viewType; + boolean viewNeedRefresh = false; + if (viewType == ViewType.VIEW_TYPE_SURFACE || viewType == ViewType.VIEW_TYPE_SURFACE_SECURE) { + if (!(surfaceView instanceof SurfaceView)) { + surfaceView = new SurfaceView(context); + viewNeedRefresh = true; + } + ((SurfaceView)surfaceView).setSecure(viewType == ViewType.VIEW_TYPE_SURFACE_SECURE); + } else if (viewType == ViewType.VIEW_TYPE_TEXTURE) { + if (!(surfaceView instanceof TextureView)) { + surfaceView = new TextureView(context); + viewNeedRefresh = true; } - } else { - view = new TextureView(context); // Support opacity properly: - ((TextureView) view).setOpaque(false); + ((TextureView) surfaceView).setOpaque(false); + } else { + DebugLog.wtf(TAG, "wtf is this texture " + viewType); } - view.setLayoutParams(layoutParams); + if (viewNeedRefresh) { + surfaceView.setLayoutParams(layoutParams); - surfaceView = view; - if (layout.getChildAt(0) != null) { - layout.removeViewAt(0); - } - layout.addView(surfaceView, 0, layoutParams); + if (layout.getChildAt(0) != null) { + layout.removeViewAt(0); + } + layout.addView(surfaceView, 0, layoutParams); - if (this.player != null) { - setVideoView(); + if (this.player != null) { + setVideoView(); + } } } @@ -211,20 +220,6 @@ public void setResizeMode(@ResizeMode.Mode int resizeMode) { } } - public void setUseTextureView(boolean useTextureView) { - if (useTextureView != this.useTextureView) { - this.useTextureView = useTextureView; - updateSurfaceView(); - } - } - - public void useSecureView(boolean useSecureView) { - if (useSecureView != this.useSecureView) { - this.useSecureView = useSecureView; - updateSurfaceView(); - } - } - public void setHideShutterView(boolean hideShutterView) { this.hideShutterView = hideShutterView; updateShutterViewVisibility(); diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index e0a14a71d4..98c02ae934 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -123,6 +123,7 @@ import com.brentvatne.common.toolbox.DebugLog; import com.brentvatne.react.BuildConfig; import com.brentvatne.react.R; +import com.brentvatne.react.ReactNativeVideoManager; import com.brentvatne.receiver.AudioBecomingNoisyReceiver; import com.brentvatne.receiver.BecomingNoisyListener; import com.facebook.react.bridge.LifecycleEventListener; @@ -258,6 +259,9 @@ public class ReactExoplayerView extends FrameLayout implements private long lastDuration = -1; private boolean viewHasDropped = false; + + private String instanceId = String.valueOf(UUID.randomUUID()); + private void updateProgress() { if (player != null) { if (playerControlView != null && isPlayingAd() && controls) { @@ -585,6 +589,10 @@ private void refreshDebugState() { } } + public void setViewType(int viewType) { + exoPlayerView.updateSurfaceView(viewType); + } + private class RNVLoadControl extends DefaultLoadControl { private final int availableHeapInBytes; private final Runtime runtime; @@ -756,6 +764,7 @@ private void initializePlayerCore(ReactExoplayerView self) { .setLoadControl(loadControl) .setMediaSourceFactory(mediaSourceFactory) .build(); + ReactNativeVideoManager.Companion.getInstance().onInstanceCreated(instanceId, player); refreshDebugState(); player.addListener(self); player.setVolume(muted ? 0.f : audioVolume * 1); @@ -1150,6 +1159,7 @@ private void releasePlayer() { player.removeListener(this); trackSelector = null; + ReactNativeVideoManager.Companion.getInstance().onInstanceRemoved(instanceId, player); player = null; } @@ -2237,15 +2247,6 @@ public void setFullscreen(boolean fullscreen) { updateFullScreenButtonVisibility(); } - public void setUseTextureView(boolean useTextureView) { - boolean finallyUseTextureView = useTextureView && drmProps == null; - exoPlayerView.setUseTextureView(finallyUseTextureView); - } - - public void useSecureView(boolean useSecureView) { - exoPlayerView.useSecureView(useSecureView); - } - public void setHideShutterView(boolean hideShutterView) { exoPlayerView.setHideShutterView(hideShutterView); } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index c43349b9c6..af8e4a51c6 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -17,9 +17,11 @@ import com.brentvatne.common.api.SideLoadedTextTrackList; import com.brentvatne.common.api.Source; import com.brentvatne.common.api.SubtitleStyle; +import com.brentvatne.common.api.ViewType; import com.brentvatne.common.react.VideoEventEmitter; import com.brentvatne.common.toolbox.DebugLog; import com.brentvatne.common.toolbox.ReactBridgeUtils; +import com.brentvatne.react.ReactNativeVideoManager; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; @@ -68,8 +70,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager = ArrayList() + private var pluginList: ArrayList = ArrayList() + + /** + * register a new ReactExoplayerViewManager in the managed list + */ + fun registerView(newInstance: ReactExoplayerViewManager): () -> Boolean = + { + if (instanceList.size > 2) { + DebugLog.d(TAG, "multiple Video displayed ?") + } + instanceList.add(newInstance) + } + + /** + * unregister existing ReactExoplayerViewManager in the managed list + */ + fun unregisterView(newInstance: ReactExoplayerViewManager): () -> Boolean = + { + instanceList.remove(newInstance) + } + + /** + * register a new plugin in the managed list + */ + fun registerPlugin(plugin: RNVPlugin) { + pluginList.add(plugin) + return + } + + /** + * unregister a plugin from the managed list + */ + fun unregisterPlugin(plugin: RNVPlugin) { + pluginList.remove(plugin) + return + } + + override fun onInstanceCreated(id: String, player: Any) { + pluginList.forEach { it.onInstanceCreated(id, player) } + } + + override fun onInstanceRemoved(id: String, player: Any) { + pluginList.forEach { it.onInstanceRemoved(id, player) } + } +} diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java b/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java deleted file mode 100644 index 6f93485889..0000000000 --- a/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.brentvatne.react; - -import com.brentvatne.exoplayer.DefaultReactExoplayerConfig; -import com.brentvatne.exoplayer.ReactExoplayerConfig; -import com.brentvatne.exoplayer.ReactExoplayerViewManager; -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class ReactVideoPackage implements ReactPackage { - - private ReactExoplayerConfig config; - - public ReactVideoPackage() { - } - - public ReactVideoPackage(ReactExoplayerConfig config) { - this.config = config; - } - - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - List modules = new ArrayList(); - - modules.add(new VideoDecoderPropertiesModule(reactContext)); - modules.add(new VideoManagerModule(reactContext)); - - return modules; - } - - // Deprecated RN 0.47 - public List> createJSModules() { - return Collections.emptyList(); - } - - - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - if (config == null) { - config = new DefaultReactExoplayerConfig(reactContext); - } - return Collections.singletonList(new ReactExoplayerViewManager(config)); - } -} diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoPackage.kt b/android/src/main/java/com/brentvatne/react/ReactVideoPackage.kt new file mode 100644 index 0000000000..99aed98510 --- /dev/null +++ b/android/src/main/java/com/brentvatne/react/ReactVideoPackage.kt @@ -0,0 +1,29 @@ +package com.brentvatne.react + +import com.brentvatne.exoplayer.DefaultReactExoplayerConfig +import com.brentvatne.exoplayer.ReactExoplayerConfig +import com.brentvatne.exoplayer.ReactExoplayerViewManager +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.JavaScriptModule +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class ReactVideoPackage @JvmOverloads constructor(private var config: ReactExoplayerConfig? = null) : ReactPackage { + + override fun createNativeModules(reactContext: ReactApplicationContext): List = + listOf( + VideoDecoderPropertiesModule(reactContext), + VideoManagerModule(reactContext) + ) + + // Deprecated RN 0.47 + fun createJSModules(): List> = emptyList() + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + if (config == null) { + config = DefaultReactExoplayerConfig(reactContext) + } + return listOf(ReactExoplayerViewManager(config!!)) + } +} diff --git a/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java b/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java deleted file mode 100644 index f6340c32b8..0000000000 --- a/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.brentvatne.react; - -import android.annotation.SuppressLint; -import android.media.MediaCodecInfo; -import android.media.MediaCodecList; -import android.media.MediaDrm; -import android.media.MediaFormat; -import android.media.UnsupportedSchemeException; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; - -import java.util.UUID; - -@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) -public class VideoDecoderPropertiesModule extends ReactContextBaseJavaModule { - - ReactApplicationContext reactContext; - - @NonNull - @Override - public String getName() { - return "VideoDecoderProperties"; - } - - @SuppressLint("ObsoleteSdkInt") - @ReactMethod - public void getWidevineLevel(Promise p) { - int widevineLevel = 0; - - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { - p.resolve(widevineLevel); - return; - } - final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); - final String WIDEVINE_SECURITY_LEVEL_1 = "L1"; - final String WIDEVINE_SECURITY_LEVEL_2 = "L2"; - final String WIDEVINE_SECURITY_LEVEL_3 = "L3"; - final String SECURITY_LEVEL_PROPERTY = "securityLevel"; - - String securityProperty = null; - try { - MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID); - securityProperty = mediaDrm.getPropertyString(SECURITY_LEVEL_PROPERTY); - } catch (UnsupportedSchemeException e) { - e.printStackTrace(); - } - if (securityProperty == null) { - p.resolve(widevineLevel); - return; - } - - switch (securityProperty) { - case WIDEVINE_SECURITY_LEVEL_1: { - widevineLevel = 1; - break; - } - case WIDEVINE_SECURITY_LEVEL_2: { - widevineLevel = 2; - break; - } - case WIDEVINE_SECURITY_LEVEL_3: { - widevineLevel = 3; - break; - } - default: { - // widevineLevel 0 - break; - } - } - p.resolve(widevineLevel); - } - - @SuppressLint("ObsoleteSdkInt") - @ReactMethod - public void isCodecSupported(String mimeType, int width, int height, Promise p) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - p.resolve("unsupported"); - return; - } - - MediaCodecList mRegularCodecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height); - String codecName = mRegularCodecs.findDecoderForFormat(format); - - if (codecName == null) { - p.resolve("unsupported"); - return; - } - - // Fallback for android < 10 - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - p.resolve("software"); - return; - } - - boolean isHardwareAccelerated = false; - - for (MediaCodecInfo codecInfo : mRegularCodecs.getCodecInfos()) { - if (codecInfo.getName().equalsIgnoreCase(codecName)) { - isHardwareAccelerated = codecInfo.isHardwareAccelerated(); - break; - } - } - - p.resolve(isHardwareAccelerated ? "software" : "hardware"); - } - - - @ReactMethod - public void isHEVCSupported(Promise p) { - isCodecSupported("video/hevc", 1920, 1080, p); - } - - public VideoDecoderPropertiesModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } - -} diff --git a/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.kt b/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.kt new file mode 100644 index 0000000000..ab80bab149 --- /dev/null +++ b/android/src/main/java/com/brentvatne/react/VideoDecoderPropertiesModule.kt @@ -0,0 +1,72 @@ +package com.brentvatne.react + +import android.media.MediaCodecList +import android.media.MediaDrm +import android.media.MediaFormat +import android.media.UnsupportedSchemeException +import android.os.Build +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import java.util.UUID + +class VideoDecoderPropertiesModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) { + override fun getName(): String = REACT_CLASS + + @ReactMethod + fun getWidevineLevel(p: Promise) { + var widevineLevel = 0 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + p.resolve(widevineLevel) + return + } + try { + val mediaDrm = MediaDrm(WIDEVINE_UUID) + val securityProperty = mediaDrm.getPropertyString(SECURITY_LEVEL_PROPERTY) + widevineLevel = when (securityProperty) { + "L1" -> 1 + "L2" -> 2 + "L3" -> 3 + else -> 0 + } + } catch (e: UnsupportedSchemeException) { + e.printStackTrace() + } + p.resolve(widevineLevel) + } + + @ReactMethod + fun isCodecSupported(mimeType: String?, width: Int, height: Int, p: Promise) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + p.resolve("unsupported") + return + } + val mRegularCodecs = MediaCodecList(MediaCodecList.REGULAR_CODECS) + val format = MediaFormat.createVideoFormat(mimeType!!, width, height) + val codecName = mRegularCodecs.findDecoderForFormat(format) + if (codecName == null) { + p.resolve("unsupported") + return + } + + // Fallback for android < 10 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + p.resolve("software") + return + } + val isHardwareAccelerated = mRegularCodecs.codecInfos.any { + it.name.equals(codecName, ignoreCase = true) && it.isHardwareAccelerated + } + p.resolve(if (isHardwareAccelerated) "software" else "hardware") + } + + @ReactMethod + fun isHEVCSupported(p: Promise) = isCodecSupported("video/hevc", 1920, 1080, p) + + companion object { + private val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) + private const val SECURITY_LEVEL_PROPERTY = "securityLevel" + private const val REACT_CLASS = "VideoDecoderProperties" + } +} diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index 3ffbb61902..7d69f59388 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -888,6 +888,9 @@ To customize the notification controls you can use `metadata` property in the `s ### `useSecureView` +> [!WARNING] +> deprecated, use viewType instead + Force the output to a SurfaceView and enables the secure surface. @@ -899,8 +902,13 @@ SurfaceView is is the only one that can be labeled as secure. - **true** - Use security - **false (default)** - Do not use security + + ### `useTextureView` +> [!WARNING] +> deprecated, use viewType instead + Controls whether to output to a TextureView or SurfaceView. @@ -915,6 +923,18 @@ useTextureView can only be set at same time you're setting the source. - **true (default)** - Use a TextureView - **false** - Use a SurfaceView +### `viewType` + + + +Allow to explicitly specify view type. +This flag replace `useSecureView` and `useTextureView` fields. +There are 3 available values: +- 'textureView': The video is rendered in a texture view. it allows mapping the view on a texture (useful for 3D). +DRM playback is not supported on textureView, if drm prop is provided, the suface will be transformed to a SurfaceView. +- 'surfaceView' (default): The video is rendered in a surface. take less resources to be rendered. +- 'secureView': The video is rendered in a surface which disallow taking screenshot of the video + ### `volume` diff --git a/docs/pages/other/_meta.json b/docs/pages/other/_meta.json index 2280f45834..f980cb56ab 100644 --- a/docs/pages/other/_meta.json +++ b/docs/pages/other/_meta.json @@ -2,5 +2,6 @@ "caching": "Caching", "misc": "Misc", "debug": "Debugging", - "new-arch": "New Architecture" + "new-arch": "New Architecture", + "plugin": "Plugin (experimental)" } \ No newline at end of file diff --git a/docs/pages/other/debug.md b/docs/pages/other/debug.md index cd8605ae78..74c8bfd146 100644 --- a/docs/pages/other/debug.md +++ b/docs/pages/other/debug.md @@ -1,6 +1,6 @@ # Debugging -This page describe usefull tips for debugging and investigating issue in the package or in your application. +This page describe useful tips for debugging and investigating issue in the package or in your application. ## Using the sample app This repository contains multiple a sample implementation in example folder. @@ -56,6 +56,41 @@ With these tool you should be able to analyze what is going on with network. You Then try to compare exchanges with previous tests you made. +## Debug media3: build from media3 source + +If you need to use a specific exoplayer version or patch default behavior, you may want to build from media3 source code. + +Building from media3 source is possible. You need to add 2 or 3 things in your app: + +### Configure player path + +You need to add following lines in settings.gradle to configure your media3 source path: + +```gradle +gradle.ext.androidxMediaModulePrefix = 'media-' +apply from: file("../../../../media3/core_settings.gradle") +```` + +Of course, you should replace with media3 source path. Be carefull, you need to use the same version (or version with compatible api) that the package support. + +### Enable building from source +In your build.gradle file, add following setting: + +```gradle +buildscript { + ext { + ... + buildFromMedia3Source = true + ... + } +} +``` + +### Desugaring +to be able to link you may also need to enable coreLibraryDesugaringEnabled in your app. + +See: https://developer.android.com/studio/write/java8-support?hl=fr#library-desugaring for more informations. + ## It's still not working You can try to open a ticket now ! diff --git a/docs/pages/other/plugin.md b/docs/pages/other/plugin.md new file mode 100644 index 0000000000..dad1cf7c5b --- /dev/null +++ b/docs/pages/other/plugin.md @@ -0,0 +1,125 @@ +# Plugin (experimental) + +Since Version 6.4.0, it is possible to create plugins for analytics management and maybe much more. +A sample plugin is available in the repository in: example/react-native-video-plugin-sample. (important FIXME, put sample link) + +## Concept + +Most of the analytics system which tracks player information (bitrate, errors, ...) can be integrated directly with Exoplayer or AVPlayer handles. + +This plugin system allows none intrusive integration of analytics in the react-native-package. It shall be done in native language (kotlin/swift). + +The idea behind this system is to be able to plug an analytics package to react native video without doing any code change (ideally). + +Following documentation will show on how to create a new plugin for react native video + +## Warning and consideration +This is an experiental API, it is subject to change. The api with player is very simple but should be flexible enough to implement analytics system. If you need some metadata, you should implement setter in the new package you are creating. + +As api is flexible, it makes possible to missuse the system. It is necessary to consider the player handle as read-only. If you modify player behavior, we cannot garanty the good behavior of react-native-video package. + +## General + +First you need to create a new react native package: +````shell +npx create-react-native-library@latest react-native-video-custom-analytics +```` + +Both android and iOS implementation expose an interface `RNVPlugin`. +Your `react-native-video-custom-analytics` shall implement this interface and register itself as a plugin for react native video. + +## Android +There is no special requierement for gradle file. +You need two mandatory action to be able to receive player handle + +### 1/ Create the plugin + +First you should instanciate a class which extends `RNVPlugin`. + +The proposed integration implement `RNVPlugin` directly inside the Module file (`VideoPluginSampleModule`). + +The `RNVPlugin` interface only defines 2 functions, see description here under. + +```kotlin + /** + * Function called when a new player is created + * @param id: a random string identifying the player + * @param player: the instantiated player reference + */ + fun onInstanceCreated(id: String, player: Any) + /** + * Function called when a player should be destroyed + * when this callback is called, the plugin shall free all + * resources and release all reference to Player object + * @param id: a random string identifying the player + * @param player: the player to release + */ + fun onInstanceRemoved(id: String, player: Any) + ```` + +### 2/ register the plugin + +To register this allocated class in the main react native video package you should call following function: + +```kotlin +ReactNativeVideoManager.getInstance().registerPlugin(plugin) +``` +The proposed integration register the instanciated class in `createNativeModules` entry point. + +Your native module can now track Player updates directly from Player reference and report to backend. + +## ios + +### 1/ podspec integration + +Your new module shall be able to access to react-native-video package, then we must declare it as a dependency of the new module you are creating. + +```podfile + s.dependency "react-native-video" +```` + +### 2/ Create the plugin + +First you should instanciate a class which extends `RNVPlugin`. + +The proposed integration implement `RNVPlugin` directly inside the entry point of the module file (`VideoPluginSample`). + +The `RNVPlugin` interface only defines 2 functions, see description here under. + +```swift + /** + * Function called when a new player is created + * @param player: the instantiated player reference + */ + func onInstanceCreated(player: Any) + /** + * Function called when a player should be destroyed + * when this callback is called, the plugin shall free all + * resources and release all reference to Player object + * @param player: the player to release + */ + func onInstanceRemoved(player: Any) +``` + +### 3/ Register the plugin + +To register this allocated class in the main react native video package you should register it by calling this function: + +```swift +ReactNativeVideoManager.shared.registerPlugin(plugin: plugin) +``` + +The proposed integration register the instanciated class in file `VideoPluginSample` in the init function: + +```swift +import react_native_video + +... + +override init() { + super.init() + ReactNativeVideoManager.shared.registerPlugin(plugin: self) +} +``` + +Your native module can now track Player updates directly from Player reference and report to backend. diff --git a/examples/basic/android/app/build.gradle b/examples/basic/android/app/build.gradle index bd7dbe4e9a..fa4d074be3 100644 --- a/examples/basic/android/app/build.gradle +++ b/examples/basic/android/app/build.gradle @@ -81,12 +81,20 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion namespace "com.videoplayer" + + compileOptions { + // These options are necessary to be able to build fro source + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } defaultConfig { applicationId "com.videoplayer" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" + multiDexEnabled true } signingConfigs { debug { @@ -137,6 +145,7 @@ dependencies { } implementation project(':react-native-video') + implementation project(':react-native-video-plugin-sample') constraints { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") { @@ -146,6 +155,8 @@ dependencies { because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") } } + // coreLibraryDesugaring is mandatory to be able to build exoplayer from source + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/examples/basic/android/app/src/main/java/com/videoplayer/MainApplication.kt b/examples/basic/android/app/src/main/java/com/videoplayer/MainApplication.kt index 4166c3d137..c1eeea477e 100644 --- a/examples/basic/android/app/src/main/java/com/videoplayer/MainApplication.kt +++ b/examples/basic/android/app/src/main/java/com/videoplayer/MainApplication.kt @@ -14,6 +14,7 @@ import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.soloader.SoLoader import com.brentvatne.react.ReactVideoPackage +import com.videopluginsample.VideoPluginSamplePackage class MainApplication : Application(), ReactApplication { @@ -24,6 +25,7 @@ class MainApplication : Application(), ReactApplication { // Packages that cannot be autolinked yet can be added manually here, for example: // add(MyReactNativePackage()) add(ReactVideoPackage()) + add(VideoPluginSamplePackage()) } override fun getJSMainModuleName(): String = "src/index" diff --git a/examples/basic/android/build.gradle b/examples/basic/android/build.gradle index 3838f93303..924476662a 100644 --- a/examples/basic/android/build.gradle +++ b/examples/basic/android/build.gradle @@ -16,6 +16,9 @@ buildscript { // useExoplayerSmoothStreaming = false // useExoplayerDash = false // useExoplayerHls = false + + // uncomment this line to be able to build from media3 source code + // buildFromMedia3Source = true } repositories { google() diff --git a/examples/basic/android/settings.gradle b/examples/basic/android/settings.gradle index 60b7d9cbfe..e91a212225 100644 --- a/examples/basic/android/settings.gradle +++ b/examples/basic/android/settings.gradle @@ -2,9 +2,16 @@ rootProject.name = 'videoplayer' apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' +include ':react-native-video-plugin-sample' +project (':react-native-video-plugin-sample').projectDir = new File(rootProject.projectDir, '../../react-native-video-plugin-sample/android') + include ':react-native-video' project (':react-native-video').projectDir = new File(rootProject.projectDir, '../../../android') +// uncomment these lines to be able to build from media3 source code +// gradle.ext.androidxMediaModulePrefix = 'media-' +// apply from: file("../../../../media3/core_settings.gradle") + includeBuild('../node_modules/@react-native/gradle-plugin') apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle") diff --git a/examples/basic/ios/Podfile b/examples/basic/ios/Podfile index 08f3a493fa..f31e6f8a06 100644 --- a/examples/basic/ios/Podfile +++ b/examples/basic/ios/Podfile @@ -46,6 +46,7 @@ target 'videoplayer' do ) pod 'react-native-video', path: '../../..' + pod 'react-native-video-plugin-sample', path: '../../react-native-video-plugin-sample' target 'videoplayerTests' do inherit! :complete diff --git a/examples/basic/ios/Podfile.lock b/examples/basic/ios/Podfile.lock index 62ca951b49..f9eea80f17 100644 --- a/examples/basic/ios/Podfile.lock +++ b/examples/basic/ios/Podfile.lock @@ -1016,6 +1016,28 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-video-plugin-sample (0.0.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - react-native-video + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-video/Video (6.2.0): - DoubleConversion - glog @@ -1324,6 +1346,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - react-native-video (from `../../..`) + - react-native-video-plugin-sample (from `../../react-native-video-plugin-sample`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) @@ -1441,6 +1464,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" react-native-video: :path: "../../.." + react-native-video-plugin-sample: + :path: "../../react-native-video-plugin-sample" React-nativeconfig: :path: "../node_modules/react-native/ReactCommon" React-NativeModulesApple: @@ -1505,12 +1530,12 @@ SPEC CHECKSUMS: ExpoModulesCore: 2731dc119f8c1400636a994df4efbc19522defbd FBLazyVector: 4bc164e5b5e6cfc288d2b5ff28643ea15fa1a589 fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 - glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 + glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f hermes-engine: 01d3e052018c2a13937aca1860fbedbccd4a41b7 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 - RCT-Folly: 045d6ecaa59d826c5736dfba0b2f4083ff8d79df + RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 RCTDeprecation: b03c35057846b685b3ccadc9bfe43e349989cdb2 RCTRequired: 194626909cfa8d39ca6663138c417bc6c431648c RCTTypeSafety: 552aff5b8e8341660594db00e53ac889682bc120 @@ -1535,6 +1560,7 @@ SPEC CHECKSUMS: React-logger: 29fa3e048f5f67fe396bc08af7606426d9bd7b5d React-Mapbuffer: 86703e9e4f6522053568300827b436ccc01e1101 react-native-video: 59f262a2d87c998b747ca1f031efb6eba1c156b5 + react-native-video-plugin-sample: d3a93b7ad777cad7fa2c30473de75a2635ce5feb React-nativeconfig: 5d452e509d6fbedc1522e21b566451fc673ac6b7 React-NativeModulesApple: 6560431301ffdab8df6212cc8c8eff779396d8e0 React-perflogger: 32ed45d9cee02cf6639acae34251590dccd30994 @@ -1566,6 +1592,6 @@ SPEC CHECKSUMS: SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 45564236f670899c9739b1581a12b00ead5d391f -PODFILE CHECKSUM: dfb9633fc816e568fd6d90dde654d15acd66faa9 +PODFILE CHECKSUM: a73d485df51877001f2b04a5a4379cfa5a3ba8fa COCOAPODS: 1.15.2 diff --git a/examples/basic/ios/videoplayer.xcodeproj/project.pbxproj b/examples/basic/ios/videoplayer.xcodeproj/project.pbxproj index 2c6b3b56c5..41daeee513 100644 --- a/examples/basic/ios/videoplayer.xcodeproj/project.pbxproj +++ b/examples/basic/ios/videoplayer.xcodeproj/project.pbxproj @@ -8,15 +8,16 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* videoplayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* videoplayerTests.m */; }; - 11C6209C7B72C624AC36CAD1 /* Pods_videoplayer_videoplayerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 594390EE3512A597F6D2CFC8 /* Pods_videoplayer_videoplayerTests.framework */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 208BB513171FFCC3277F9E0F /* Pods_videoplayer_videoplayerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5C7FBD972AC23420C9CAEF6 /* Pods_videoplayer_videoplayerTests.framework */; }; 20E2D2234B216472515590E5 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B7A50CD29E62AE55CDBAC5 /* ExpoModulesProvider.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 8564D8A0ECE6B35EF7A78EDB /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF6DF30451E50AB13568EFC /* ExpoModulesProvider.swift */; }; - A64041D5CF85945B698F6FD0 /* Pods_videoplayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4610D42AC113C0F184AAF5BC /* Pods_videoplayer.framework */; }; + C57DB7DC75FFA5378D941129 /* Pods_videoplayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10FD7EDE51B5059CB0982AD2 /* Pods_videoplayer.framework */; }; DA6F026ACB11B4361D7006B9 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 06EB80F4634394ABC14C45DC /* PrivacyInfo.xcprivacy */; }; + EC73F7EE64DE3B7F743B618D /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -33,22 +34,22 @@ 00E356EE1AD99517003FC87E /* videoplayerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = videoplayerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* videoplayerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = videoplayerTests.m; sourceTree = ""; }; + 058979377AFD7ECE5B23DBEB /* Pods-videoplayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer.release.xcconfig"; path = "Target Support Files/Pods-videoplayer/Pods-videoplayer.release.xcconfig"; sourceTree = ""; }; 06EB80F4634394ABC14C45DC /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = videoplayer/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 10FD7EDE51B5059CB0982AD2 /* Pods_videoplayer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_videoplayer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* videoplayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = videoplayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = videoplayer/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = videoplayer/AppDelegate.mm; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = videoplayer/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = videoplayer/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = videoplayer/main.m; sourceTree = ""; }; - 327740C386721461467B91B2 /* Pods-videoplayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer.release.xcconfig"; path = "Target Support Files/Pods-videoplayer/Pods-videoplayer.release.xcconfig"; sourceTree = ""; }; - 4610D42AC113C0F184AAF5BC /* Pods_videoplayer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_videoplayer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5112808AA45F803BD0D3F411 /* Pods-videoplayer-videoplayerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer-videoplayerTests.release.xcconfig"; path = "Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests.release.xcconfig"; sourceTree = ""; }; - 594390EE3512A597F6D2CFC8 /* Pods_videoplayer_videoplayerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_videoplayer_videoplayerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6E6DE29C8B861F4D5A4BBDEB /* Pods-videoplayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer.debug.xcconfig"; path = "Target Support Files/Pods-videoplayer/Pods-videoplayer.debug.xcconfig"; sourceTree = ""; }; + 2F5C4E6DD1564FCB6C9B7B94 /* Pods-videoplayer-videoplayerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer-videoplayerTests.debug.xcconfig"; path = "Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests.debug.xcconfig"; sourceTree = ""; }; 7AF6DF30451E50AB13568EFC /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-videoplayer-videoplayerTests/ExpoModulesProvider.swift"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = videoplayer/LaunchScreen.storyboard; sourceTree = ""; }; + 9C018F4E223E0E71BA85ABC9 /* Pods-videoplayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer.debug.xcconfig"; path = "Target Support Files/Pods-videoplayer/Pods-videoplayer.debug.xcconfig"; sourceTree = ""; }; + A5C7FBD972AC23420C9CAEF6 /* Pods_videoplayer_videoplayerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_videoplayer_videoplayerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B9B7A50CD29E62AE55CDBAC5 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-videoplayer/ExpoModulesProvider.swift"; sourceTree = ""; }; - CBE734469FBE698BF938A7EF /* Pods-videoplayer-videoplayerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer-videoplayerTests.debug.xcconfig"; path = "Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests.debug.xcconfig"; sourceTree = ""; }; + CF1F0C5E1D8D8D557C4C7043 /* Pods-videoplayer-videoplayerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-videoplayer-videoplayerTests.release.xcconfig"; path = "Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -57,7 +58,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 11C6209C7B72C624AC36CAD1 /* Pods_videoplayer_videoplayerTests.framework in Frameworks */, + 208BB513171FFCC3277F9E0F /* Pods_videoplayer_videoplayerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -65,7 +66,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A64041D5CF85945B698F6FD0 /* Pods_videoplayer.framework in Frameworks */, + EC73F7EE64DE3B7F743B618D /* BuildFile in Frameworks */, + C57DB7DC75FFA5378D941129 /* Pods_videoplayer.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -107,8 +109,8 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 4610D42AC113C0F184AAF5BC /* Pods_videoplayer.framework */, - 594390EE3512A597F6D2CFC8 /* Pods_videoplayer_videoplayerTests.framework */, + 10FD7EDE51B5059CB0982AD2 /* Pods_videoplayer.framework */, + A5C7FBD972AC23420C9CAEF6 /* Pods_videoplayer_videoplayerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -165,10 +167,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 6E6DE29C8B861F4D5A4BBDEB /* Pods-videoplayer.debug.xcconfig */, - 327740C386721461467B91B2 /* Pods-videoplayer.release.xcconfig */, - CBE734469FBE698BF938A7EF /* Pods-videoplayer-videoplayerTests.debug.xcconfig */, - 5112808AA45F803BD0D3F411 /* Pods-videoplayer-videoplayerTests.release.xcconfig */, + 9C018F4E223E0E71BA85ABC9 /* Pods-videoplayer.debug.xcconfig */, + 058979377AFD7ECE5B23DBEB /* Pods-videoplayer.release.xcconfig */, + 2F5C4E6DD1564FCB6C9B7B94 /* Pods-videoplayer-videoplayerTests.debug.xcconfig */, + CF1F0C5E1D8D8D557C4C7043 /* Pods-videoplayer-videoplayerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -188,13 +190,13 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "videoplayerTests" */; buildPhases = ( - C0C880E0881EE98C1792E57D /* [CP] Check Pods Manifest.lock */, + 05848CC282AE20BB2B2AA52D /* [CP] Check Pods Manifest.lock */, E0436766C647BDEAF9FD5ED3 /* [Expo] Configure project */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - E3461B2529EDAA05BCA42787 /* [CP] Embed Pods Frameworks */, - 0765D094F5C79DBE6CC6386B /* [CP] Copy Pods Resources */, + 0F69B47FEB727B4EBBAC0C93 /* [CP] Embed Pods Frameworks */, + 8DEA7E188641F8CD9B4543DD /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -210,15 +212,15 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "videoplayer" */; buildPhases = ( - 66FA7E36D064B737784DDFB7 /* [CP] Check Pods Manifest.lock */, + 4BC7B73D9362CA23BDA1E909 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 43E82399B51FE7A2CADEE958 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 189A4605ED0B2F88CF1DCFA8 /* [CP] Embed Pods Frameworks */, - F467D0E16E9AD72917847B43 /* [CP] Copy Pods Resources */, + 5580EE7ED1DD133A3CB36FB0 /* [CP] Embed Pods Frameworks */, + 4617BBDCD64674510B35868A /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -302,38 +304,43 @@ shellPath = /bin/sh; shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n"; }; - 0765D094F5C79DBE6CC6386B /* [CP] Copy Pods Resources */ = { + 05848CC282AE20BB2B2AA52D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-videoplayer-videoplayerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 189A4605ED0B2F88CF1DCFA8 /* [CP] Embed Pods Frameworks */ = { + 0F69B47FEB727B4EBBAC0C93 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 43E82399B51FE7A2CADEE958 /* [Expo] Configure project */ = { @@ -355,29 +362,24 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-videoplayer/expo-configure-project.sh\"\n"; }; - 66FA7E36D064B737784DDFB7 /* [CP] Check Pods Manifest.lock */ = { + 4617BBDCD64674510B35868A /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-videoplayer-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources.sh\"\n"; showEnvVarsInLog = 0; }; - C0C880E0881EE98C1792E57D /* [CP] Check Pods Manifest.lock */ = { + 4BC7B73D9362CA23BDA1E909 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -392,65 +394,65 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-videoplayer-videoplayerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-videoplayer-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - E0436766C647BDEAF9FD5ED3 /* [Expo] Configure project */ = { + 5580EE7ED1DD133A3CB36FB0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); - name = "[Expo] Configure project"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-videoplayer-videoplayerTests/expo-configure-project.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - E3461B2529EDAA05BCA42787 /* [CP] Embed Pods Frameworks */ = { + 8DEA7E188641F8CD9B4543DD /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer-videoplayerTests/Pods-videoplayer-videoplayerTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - F467D0E16E9AD72917847B43 /* [CP] Copy Pods Resources */ = { + E0436766C647BDEAF9FD5ED3 /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + inputPaths = ( + ); + name = "[Expo] Configure project"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-videoplayer/Pods-videoplayer-resources.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-videoplayer-videoplayerTests/expo-configure-project.sh\"\n"; }; FD10A7F022414F080027D42C /* Start Packager */ = { isa = PBXShellScriptBuildPhase; @@ -506,7 +508,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CBE734469FBE698BF938A7EF /* Pods-videoplayer-videoplayerTests.debug.xcconfig */; + baseConfigurationReference = 2F5C4E6DD1564FCB6C9B7B94 /* Pods-videoplayer-videoplayerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -534,7 +536,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5112808AA45F803BD0D3F411 /* Pods-videoplayer-videoplayerTests.release.xcconfig */; + baseConfigurationReference = CF1F0C5E1D8D8D557C4C7043 /* Pods-videoplayer-videoplayerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; @@ -559,7 +561,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6E6DE29C8B861F4D5A4BBDEB /* Pods-videoplayer.debug.xcconfig */; + baseConfigurationReference = 9C018F4E223E0E71BA85ABC9 /* Pods-videoplayer.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -587,7 +589,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 327740C386721461467B91B2 /* Pods-videoplayer.release.xcconfig */; + baseConfigurationReference = 058979377AFD7ECE5B23DBEB /* Pods-videoplayer.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index ba12f40314..b6593302f3 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -1,16 +1,8 @@ 'use strict'; -import React, {Component} from 'react'; +import React, {FC, useRef, useState} from 'react'; -import { - Text, - TouchableOpacity, - View, - ActivityIndicator, - ToastAndroid, - Platform, - Alert, -} from 'react-native'; +import {TouchableOpacity, View} from 'react-native'; import Video, { AudioTrack, @@ -20,434 +12,169 @@ import Video, { OnTextTracksData, OnVideoAspectRatioData, TextTrack, - VideoDecoderProperties, OnBufferData, OnAudioFocusChangedData, OnVideoErrorData, VideoRef, - ResizeMode, - SelectedTrack, - DRMType, OnTextTrackDataChangedData, - TextTrackType, - ISO639_1, OnSeekData, OnPlaybackStateChangedData, OnPlaybackRateChangeData, OnVideoTracksData, - VideoTrack, SelectedVideoTrackType, - SelectedVideoTrack, BufferingStrategyType, ReactVideoSource, - Drm, - TextTracks, + SelectedTrackType, } from 'react-native-video'; -import ToggleControl from './ToggleControl'; -import MultiValueControl, { - MultiValueControlPropType, -} from './MultiValueControl'; import styles from './styles'; -import AudioTrackSelector from './components/AudioTracksSelector'; -import TextTrackSelector from './components/TextTracksSelector'; -import VideoTrackSelector from './components/VideoTracksSelector'; -import Seeker from './components/Seeker'; - -type AdditionnalSourceInfo = { - textTracks: TextTracks; - adTagUrl: string; - description: string; - drm: Drm; - noView: boolean; -}; - -type SampleVideoSource = ReactVideoSource | AdditionnalSourceInfo; - -interface StateType { - rate: number; - volume: number; - muted: boolean; - resizeMode: ResizeMode; - duration: number; - currentTime: number; - videoWidth: number; - videoHeight: number; - paused: boolean; - fullscreen: true; - decoration: true; - isLoading: boolean; - audioTracks: Array; - textTracks: Array; - videoTracks: Array; - selectedAudioTrack: SelectedTrack | undefined; - selectedTextTrack: SelectedTrack | undefined; - selectedVideoTrack: SelectedVideoTrack; - srcListId: number; - loop: boolean; - showRNVControls: boolean; - useCache: boolean; - poster?: string; - showNotificationControls: boolean; - isSeeking: boolean; -} - -class VideoPlayer extends Component { - state: StateType = { - rate: 1, - volume: 1, - muted: false, - resizeMode: ResizeMode.CONTAIN, - duration: 0.0, - currentTime: 0.0, - videoWidth: 0, - videoHeight: 0, - paused: false, - fullscreen: true, - decoration: true, - isLoading: false, - audioTracks: [], - textTracks: [], - videoTracks: [], - selectedAudioTrack: undefined, - selectedTextTrack: undefined, - selectedVideoTrack: { - type: SelectedVideoTrackType.AUTO, - }, - srcListId: 0, - loop: false, - showRNVControls: false, - useCache: false, - poster: undefined, - showNotificationControls: false, - isSeeking: false, - }; - - // internal usage change to index if you want to select tracks by index instead of lang - textTracksSelectionBy = 'index'; - - srcAllPlatformList = [ - { - description: 'local file landscape', - uri: require('./broadchurch.mp4'), - }, - { - description: 'local file landscape cropped', - uri: require('./broadchurch.mp4'), - cropStart: 3000, - cropEnd: 10000, - }, - { - description: 'local file portrait', - uri: require('./portrait.mp4'), - metadata: { - title: 'Test Title', - subtitle: 'Test Subtitle', - artist: 'Test Artist', - description: 'Test Description', - imageUri: - 'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png', - }, - }, - { - description: '(hls|live) red bull tv', - textTracksAllowChunklessPreparation: false, - uri: 'https://rbmn-live.akamaized.net/hls/live/590964/BoRB-AT/master_928.m3u8', - metadata: { - title: 'Custom Title', - subtitle: 'Custom Subtitle', - artist: 'Custom Artist', - description: 'Custom Description', - imageUri: - 'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png', - }, - }, - { - description: 'invalid URL', - uri: 'mmt://www.youtube.com', - type: 'mpd', - }, - {description: '(no url) Stopped playback', uri: undefined}, - { - description: '(no view) no View', - noView: true, - }, - { - description: 'Another live sample', - uri: 'https://live.forstreet.cl/live/livestream.m3u8', - }, - { - description: 'another bunny (can be saved)', - uri: 'https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4', - headers: {referer: 'www.github.com', 'User-Agent': 'react.native.video'}, - }, - { - description: 'sintel with subtitles', - uri: 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8', - }, - { - description: 'sintel starts at 20sec', - uri: 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8', - startPosition: 50000, - }, - { - description: 'BigBugBunny sideLoaded subtitles', - // sideloaded subtitles wont work for streaming like HLS on ios - // mp4 - uri: 'https://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4', - textTracks: [ - { - title: 'test', - language: 'en' as ISO639_1, - type: TextTrackType.VTT, - uri: 'https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_en.vtt', - }, - ], - }, - ]; - - srcIosList = []; - - srcAndroidList = [ - { - description: 'Another live sample', - uri: 'https://live.forstreet.cl/live/livestream.m3u8', - }, - { - description: 'asset file', - uri: 'asset:///broadchurch.mp4', - }, - { - description: '(dash) sintel subtitles', - uri: 'https://bitmovin-a.akamaihd.net/content/sintel/sintel.mpd', - }, - { - description: '(mp4) big buck bunny', - uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4', - }, - { - description: '(mp4|subtitles) demo with sintel Subtitles', - uri: 'http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0', - type: 'mpd', - }, - { - description: '(mp4) big buck bunny With Ads', - adTagUrl: - 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostoptimizedpodbumper&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=', - uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4', - }, - { - description: 'WV: Secure SD & HD (cbcs,MP4,H264)', - uri: 'https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd', - drm: { - type: DRMType.WIDEVINE, - licenseServer: - 'https://proxy.uat.widevine.com/proxy?provider=widevine_test', - }, - }, - { - description: 'Secure UHD (cenc)', - uri: 'https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_uhd.mpd', - drm: { - type: DRMType.WIDEVINE, - licenseServer: - 'https://proxy.uat.widevine.com/proxy?provider=widevine_test', - }, - }, - { - description: 'rtsp big bug bunny', - uri: 'rtsp://rtspstream:3cfa3c36a9c00f4aa38f3cd35816b287@zephyr.rtsp.stream/movie', - type: 'rtsp', - }, - ]; - - // poster which can be displayed - samplePoster = - 'https://upload.wikimedia.org/wikipedia/commons/1/18/React_Native_Logo.png'; - - srcList: SampleVideoSource[] = this.srcAllPlatformList.concat( - Platform.OS === 'android' ? this.srcAndroidList : this.srcIosList, - ); - - video?: VideoRef; - - popupInfo = () => { - VideoDecoderProperties.getWidevineLevel().then((widevineLevel: number) => { - VideoDecoderProperties.isHEVCSupported().then((hevc: string) => { - VideoDecoderProperties.isCodecSupported('video/avc', 1920, 1080).then( - (avc: string) => { - this.toast( - true, - 'Widevine level: ' + - widevineLevel + - '\n hevc: ' + - hevc + - '\n avc: ' + - avc, - ); - }, - ); - }); - }); - }; - - onLoad = (data: OnLoadData) => { - this.setState({duration: data.duration, loading: false}); - this.onAudioTracks(data); - this.onTextTracks(data); - this.onVideoTracks(data); - }; - - onProgress = (data: OnProgressData) => { - this.setState({currentTime: data.currentTime}); - }; - - onSeek = (data: OnSeekData) => { - this.setState({isSeeking: false}); - this.setState({currentTime: data.currentTime}); - }; - - onVideoLoadStart = () => { - console.log('onVideoLoadStart'); - this.setState({isLoading: true}); - }; - - onAudioTracks = (data: OnAudioTracksData) => { +import {AdditionalSourceInfo} from './types'; +import { + bufferConfig, + defaultValue, + srcList, + textTracksSelectionBy, +} from './constants'; +import {Overlay, toast} from './components'; + +type Props = NonNullable; + +const VideoPlayer: FC = ({}) => { + const [state, setState] = useState(defaultValue); + const videoRef = useRef(null); + const viewStyle = state.fullscreen ? styles.fullScreen : styles.halfScreen; + const currentSrc = srcList[state.srcListId]; + const additional = currentSrc as AdditionalSourceInfo; + + const onAudioTracks = (data: OnAudioTracksData) => { const selectedTrack = data.audioTracks?.find((x: AudioTrack) => { return x.selected; }); if (selectedTrack?.index) { - this.setState({ + setState({ + ...state, audioTracks: data.audioTracks, selectedAudioTrack: { - type: SelectedVideoTrackType.INDEX, + type: SelectedTrackType.INDEX, value: selectedTrack?.index, }, }); } else { - this.setState({ + setState({ + ...state, audioTracks: data.audioTracks, }); } }; - onVideoTracks = (data: OnVideoTracksData) => { + const onVideoTracks = (data: OnVideoTracksData) => { console.log('onVideoTracks', data.videoTracks); - this.setState({ + setState({ + ...state, videoTracks: data.videoTracks, }); }; - onTextTracks = (data: OnTextTracksData) => { + const onTextTracks = (data: OnTextTracksData) => { const selectedTrack = data.textTracks?.find((x: TextTrack) => { return x?.selected; }); if (selectedTrack?.language) { - this.setState({ + setState({ + ...state, textTracks: data.textTracks, selectedTextTrack: - this.textTracksSelectionBy === 'index' + textTracksSelectionBy === 'index' ? { - type: 'index', + type: SelectedTrackType.INDEX, value: selectedTrack?.index, } : { - type: 'language', + type: SelectedTrackType.LANGUAGE, value: selectedTrack?.language, }, }); } else { - this.setState({ + setState({ + ...state, textTracks: data.textTracks, }); } }; - onTextTrackDataChanged = (data: OnTextTrackDataChangedData) => { + const onLoad = (data: OnLoadData) => { + onAudioTracks(data); + onTextTracks(data); + onVideoTracks(data); + setState({...state, duration: data.duration}); + }; + + const onProgress = (data: OnProgressData) => { + setState({...state, currentTime: data.currentTime}); + }; + + const onSeek = (data: OnSeekData) => { + setState({...state, currentTime: data.currentTime, isSeeking: false}); + }; + + const onVideoLoadStart = () => { + console.log('onVideoLoadStart'); + setState({...state, isLoading: true}); + }; + + const onTextTrackDataChanged = (data: OnTextTrackDataChangedData) => { console.log(`Subtitles: ${JSON.stringify(data, null, 2)}`); }; - onAspectRatio = (data: OnVideoAspectRatioData) => { + const onAspectRatio = (data: OnVideoAspectRatioData) => { console.log('onAspectRadio called ' + JSON.stringify(data)); - this.setState({ + setState({ + ...state, videoWidth: data.width, videoHeight: data.height, }); }; - onVideoBuffer = (param: OnBufferData) => { + const onVideoBuffer = (param: OnBufferData) => { console.log('onVideoBuffer'); - this.setState({isLoading: param.isBuffering}); + setState({...state, isLoading: param.isBuffering}); }; - onReadyForDisplay = () => { + const onReadyForDisplay = () => { console.log('onReadyForDisplay'); - this.setState({isLoading: false}); - }; - - onAudioBecomingNoisy = () => { - this.setState({paused: true}); + setState({...state, isLoading: false}); }; - onAudioFocusChanged = (event: OnAudioFocusChangedData) => { - this.setState({paused: !event.hasAudioFocus}); + const onAudioBecomingNoisy = () => { + setState({...state, paused: true}); }; - toast = (visible: boolean, message: string) => { - if (visible) { - if (Platform.OS === 'android') { - ToastAndroid.showWithGravityAndOffset( - message, - ToastAndroid.LONG, - ToastAndroid.BOTTOM, - 25, - 50, - ); - } else { - Alert.alert(message, message); - } - } + const onAudioFocusChanged = (event: OnAudioFocusChangedData) => { + setState({...state, paused: !event.hasAudioFocus}); }; - onError = (err: OnVideoErrorData) => { + const onError = (err: OnVideoErrorData) => { console.log(JSON.stringify(err)); - this.toast(true, 'error: ' + JSON.stringify(err)); + toast(true, 'error: ' + JSON.stringify(err)); }; - onEnd = () => { - if (!this.state.loop) { - this.channelUp(); + const onEnd = () => { + if (!state.loop) { + channelUp(); } }; - onPlaybackRateChange = (data: OnPlaybackRateChangeData) => { + const onPlaybackRateChange = (data: OnPlaybackRateChangeData) => { console.log('onPlaybackRateChange', data); }; - onPlaybackStateChanged = (data: OnPlaybackStateChangedData) => { + const onPlaybackStateChanged = (data: OnPlaybackStateChangedData) => { console.log('onPlaybackStateChanged', data); }; - toggleFullscreen() { - this.setState({fullscreen: !this.state.fullscreen}); - } - toggleControls() { - this.setState({showRNVControls: !this.state.showRNVControls}); - } - - toggleDecoration() { - this.setState({decoration: !this.state.decoration}); - this.video?.setFullScreen(!this.state.decoration); - } - - toggleShowNotificationControls() { - this.setState({ - showNotificationControls: !this.state.showNotificationControls, - }); - } - - goToChannel(channel: number) { - this.setState({ + const goToChannel = (channel: number) => { + setState({ + ...state, srcListId: channel, duration: 0.0, currentTime: 0.0, @@ -462,370 +189,79 @@ class VideoPlayer extends Component { type: SelectedVideoTrackType.AUTO, }, }); - } - - channelUp() { - console.log('channel up'); - this.goToChannel((this.state.srcListId + 1) % this.srcList.length); - } - - channelDown() { - console.log('channel down'); - this.goToChannel( - (this.state.srcListId + this.srcList.length - 1) % this.srcList.length, - ); - } - - videoSeek(position: number) { - this.setState({isSeeking: true}); - this.video?.seek(position); - } - - renderSeekBar() { - return ( - this.videoSeek(prop)} - isUISeeking={this.state.isSeeking} - /> - ); - } - - IndicatorLoadingView() { - if (this.state.isLoading) { - return ( - - ); - } else { - return ; - } - } - - renderTopControl() { - return ( - - - {(this.srcList[this.state.srcListId] as AdditionnalSourceInfo) - ?.description || 'local file'} - - - { - this.toggleControls(); - }}> - - {this.state.showRNVControls ? 'Hide controls' : 'Show controls'} - - - - - ); - } - - onRateSelected = (value: MultiValueControlPropType) => { - this.setState({rate: value}); - }; - onVolumeSelected = (value: MultiValueControlPropType) => { - this.setState({volume: value}); - }; - onResizeModeSelected = (value: MultiValueControlPropType) => { - this.setState({resizeMode: value}); }; - onSelectedAudioTrackChange = (itemValue: string) => { - console.log('on audio value change ' + itemValue); - if (itemValue === 'none') { - this.setState({ - selectedAudioTrack: SelectedVideoTrackType.DISABLED, - }); - } else { - this.setState({ - selectedAudioTrack: { - type: SelectedVideoTrackType.INDEX, - value: itemValue, - }, - }); - } - }; - - onSelectedTextTrackChange = (itemValue: string) => { - console.log('on value change ' + itemValue); - this.setState({ - selectedTextTrack: { - type: this.textTracksSelectionBy === 'index' ? 'index' : 'language', - value: itemValue, - }, - }); + const channelUp = () => { + console.log('channel up'); + goToChannel((state.srcListId + 1) % srcList.length); }; - onSelectedVideoTrackChange = (itemValue: string) => { - console.log('on value change ' + itemValue); - if (itemValue === undefined || itemValue === 'auto') { - this.setState({ - selectedVideoTrack: { - type: SelectedVideoTrackType.AUTO, - }, - }); - } else { - this.setState({ - selectedVideoTrack: { - type: SelectedVideoTrackType.INDEX, - value: itemValue, - }, - }); - } + const channelDown = () => { + console.log('channel down'); + goToChannel((state.srcListId + srcList.length - 1) % srcList.length); }; - renderOverlay() { - return ( - <> - {this.IndicatorLoadingView()} - - - {this.renderTopControl()} - - - {!this.state.showRNVControls ? ( - <> - - { - this.channelDown(); - }} - text="ChDown" - /> - - - { - this.channelUp(); - }} - text="ChUp" - /> - - - - {Platform.OS === 'android' ? ( - - { - this.popupInfo(); - }} - text="decoderInfo" - /> - { - this.setState({useCache: !this.state.useCache}); - }} - selectedText="enable cache" - unselectedText="disable cache" - /> - - ) : null} - { - this.setState({paused: !this.state.paused}); - }} - selectedText="pause" - unselectedText="playing" - /> - { - this.setState({loop: !this.state.loop}); - }} - selectedText="loop enable" - unselectedText="loop disable" - /> - { - this.toggleFullscreen(); - }} - text="fullscreen" - /> - { - this.toggleDecoration(); - }} - text="decoration" - /> - { - this.setState({ - poster: this.state.poster ? undefined : this.samplePoster, - }); - }} - selectedText="poster" - unselectedText="no poster" - /> - { - this.toggleShowNotificationControls(); - }} - selectedText="hide notification controls" - unselectedText="show notification controls" - /> - - - {/* shall be replaced by slider */} - - {/* shall be replaced by slider */} - - - { - this.setState({muted: !this.state.muted}); - }} - text="muted" - /> - {Platform.OS === 'ios' ? ( - { - this.video - ?.save({}) - ?.then(response => { - console.log('Downloaded URI', response); - }) - .catch(error => { - console.log('error during save ', error); - }); - }} - text="save" - /> - ) : null} - - {this.renderSeekBar()} - - - - - - - - ) : null} - - ); - } - - renderVideoView() { - const viewStyle = this.state.fullscreen - ? styles.fullScreen - : styles.halfScreen; - - const currentSrc = this.srcList[this.state.srcListId]; - const additionnal = currentSrc as AdditionnalSourceInfo; - - return ( - - - ); - } - - render() { - return ( - - {(this.srcList[this.state.srcListId] as AdditionnalSourceInfo)?.noView - ? null - : this.renderVideoView()} - {this.renderOverlay()} - - ); - } -} + return ( + + {(srcList[state.srcListId] as AdditionalSourceInfo)?.noView ? null : ( + + + )} + + + ); +}; export default VideoPlayer; diff --git a/examples/basic/src/VideoPlayer.windows.tsx b/examples/basic/src/VideoPlayer.windows.tsx index f39f177b7f..5c66eebae2 100644 --- a/examples/basic/src/VideoPlayer.windows.tsx +++ b/examples/basic/src/VideoPlayer.windows.tsx @@ -108,7 +108,7 @@ class VideoPlayer extends Component { this.setState({paused: !this.state.paused}); }}>