From 2f70c02cdcf6488338df197feb61eeb10ed3281f Mon Sep 17 00:00:00 2001 From: Igor Malgrab Date: Wed, 17 Jul 2024 12:29:38 +0200 Subject: [PATCH 01/95] feat(android): set originalFitsSystemWindows on fullscreen open (#4013) --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 4841d5c0..33df071a 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -202,6 +202,7 @@ public class ReactExoplayerView extends FrameLayout implements private long resumePosition; private boolean loadVideoStarted; private boolean isFullscreen; + private boolean originalFitsSystemWindows; private boolean isInBackground; private boolean isPaused; private boolean isBuffering; @@ -2238,6 +2239,7 @@ public class ReactExoplayerView extends FrameLayout implements fullScreenPlayerView.show(); } UiThreadUtil.runOnUiThread(() -> { + originalFitsSystemWindows = window.getDecorView().getFitsSystemWindows(); WindowCompat.setDecorFitsSystemWindows(window, false); controller.hide(WindowInsetsCompat.Type.systemBars()); controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); @@ -2251,7 +2253,7 @@ public class ReactExoplayerView extends FrameLayout implements setControls(controls); } UiThreadUtil.runOnUiThread(() -> { - WindowCompat.setDecorFitsSystemWindows(window, true); + WindowCompat.setDecorFitsSystemWindows(window, originalFitsSystemWindows); controller.show(WindowInsetsCompat.Type.systemBars()); eventEmitter.onVideoFullscreenPlayerDidDismiss.invoke(); }); From 76d5e93c175a753aa454c89bb29298f1ebca3b99 Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Thu, 18 Jul 2024 10:41:07 +0330 Subject: [PATCH 02/95] refactor(android): migrate BecomingNoisyListener interface to Kotlin (#3996) * Rename .java to .kt * refactor(android): migrate BecomingNoisyListener interface to Kotlin * remove: java file (AudioBecomingNoisyReceiver.java) --- .../brentvatne/receiver/BecomingNoisyListener.java | 13 ------------- .../brentvatne/receiver/BecomingNoisyListener.kt | 12 ++++++++++++ 2 files changed, 12 insertions(+), 13 deletions(-) delete mode 100644 android/src/main/java/com/brentvatne/receiver/BecomingNoisyListener.java create mode 100644 android/src/main/java/com/brentvatne/receiver/BecomingNoisyListener.kt diff --git a/android/src/main/java/com/brentvatne/receiver/BecomingNoisyListener.java b/android/src/main/java/com/brentvatne/receiver/BecomingNoisyListener.java deleted file mode 100644 index 6a5d37d8..00000000 --- a/android/src/main/java/com/brentvatne/receiver/BecomingNoisyListener.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.brentvatne.receiver; - -public interface BecomingNoisyListener { - - BecomingNoisyListener NO_OP = new BecomingNoisyListener() { - @Override public void onAudioBecomingNoisy() { - // NO_OP - } - }; - - void onAudioBecomingNoisy(); - -} diff --git a/android/src/main/java/com/brentvatne/receiver/BecomingNoisyListener.kt b/android/src/main/java/com/brentvatne/receiver/BecomingNoisyListener.kt new file mode 100644 index 00000000..d967fc9d --- /dev/null +++ b/android/src/main/java/com/brentvatne/receiver/BecomingNoisyListener.kt @@ -0,0 +1,12 @@ +package com.brentvatne.receiver + +interface BecomingNoisyListener { + companion object { + val NO_OP = object : BecomingNoisyListener { + override fun onAudioBecomingNoisy() { + // NO_OP + } + } + } + fun onAudioBecomingNoisy() +} From ab7e02e4538c97340f13fb052b1cad94408b48fa Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Thu, 18 Jul 2024 10:51:57 +0200 Subject: [PATCH 03/95] fix(android): fix backward compatibility (#4020) --- .../main/java/com/brentvatne/exoplayer/ExoPlayerView.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index d1ac5227..a57b91f9 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -238,12 +238,11 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { // There are weird cases when video height and width did not change with rotation so we need change aspect ration to fix it switch (format.rotationDegrees) { // update aspect ratio ! - case 90, 270 -> { + case 90: + case 270: layout.setVideoAspectRatio(format.width == 0 ? 1 : (format.height * format.pixelWidthHeightRatio) / format.width); - } - default -> { + default: layout.setVideoAspectRatio(format.height == 0 ? 1 : (format.width * format.pixelWidthHeightRatio) / format.height); - } } return; } From 9f382163d83c3331518e2784b335da28179ac91d Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Thu, 18 Jul 2024 01:53:50 -0700 Subject: [PATCH 04/95] fix(android): resize mode cover calculation (#4010) --- .../java/com/brentvatne/exoplayer/AspectRatioFrameLayout.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/AspectRatioFrameLayout.kt b/android/src/main/java/com/brentvatne/exoplayer/AspectRatioFrameLayout.kt index 834e5e0c..c26745fe 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/AspectRatioFrameLayout.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/AspectRatioFrameLayout.kt @@ -75,9 +75,9 @@ class AspectRatioFrameLayout(context: Context) : FrameLayout(context) { // Scale video if it doesn't fill the measuredWidth if (width < measuredWidth) { - val scaleFactor: Int = measuredWidth / width - width *= scaleFactor - height = measuredHeight * scaleFactor + val scaleFactor: Float = measuredWidth.toFloat() / width + width = (scaleFactor * width).toInt() + height = (scaleFactor * height).toInt() } } From ffbc977ff90248aeb270626620f5c3553f955617 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Thu, 18 Jul 2024 22:22:58 +0200 Subject: [PATCH 05/95] fix(sample): boot failure on emulator (#4016) * fix: disable coreLibraryDesugaringEnabled by default --- examples/basic/android/app/build.gradle | 6 ++++-- examples/basic/src/VideoPlayer.tsx | 8 -------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/examples/basic/android/app/build.gradle b/examples/basic/android/app/build.gradle index fa4d074b..8d8826d0 100644 --- a/examples/basic/android/app/build.gradle +++ b/examples/basic/android/app/build.gradle @@ -83,8 +83,10 @@ android { namespace "com.videoplayer" compileOptions { - // These options are necessary to be able to build fro source - coreLibraryDesugaringEnabled true + // These options are necessary to be able to build from source + // coreLibraryDesugaringEnabled is mandatory to be able to build exoplayer from source + // uncomment this line if you want to build from source + // coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index 76d7c65c..b67af9cd 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -26,7 +26,6 @@ import Video, { type OnPlaybackRateChangeData, type OnVideoTracksData, type ReactVideoSource, - type TextTracks, type VideoTrack, type SelectedTrack, type SelectedVideoTrack, @@ -37,13 +36,6 @@ import {type AdditionalSourceInfo} from './types'; import {bufferConfig, srcList, textTracksSelectionBy} from './constants'; import {Overlay, toast} from './components'; -type AdditionnalSourceInfo = { - textTracks: TextTracks; - adTagUrl: string; - description: string; - noView: boolean; -}; - type Props = NonNullable; const VideoPlayer: FC = ({}) => { From 1ee5811c8e0ecfc2486f5120b575b57c6396e0f8 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:56:58 +0200 Subject: [PATCH 06/95] fix(android): app crash at boot with old arch (#4022) * perf: ensure we do not provide callback to native if no callback provided from app * chore: rework bufferConfig to make it more generic and reduce ReactExoplayerView code size * chore: improve issue template * fix(android): avoid video view flickering at playback startup * fix: ensure player doesn't start when view is unmounted * Fix/ensure view drop stop playback startup (#3875) * fix: ensure player doesn't start when view is unmounted * chore: fix app boot due to missing registered events * chore: fix linter * chore: fix build * fix: handle code review feedbacks * chore: revert previous change to handle only necessary fix --- .../main/java/com/brentvatne/common/react/VideoEventEmitter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt index 8f29779a..b227bfc1 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt @@ -48,7 +48,7 @@ enum class EventTypes(val eventName: String) { fun toMap() = mutableMapOf().apply { EventTypes.values().toList().forEach { eventType -> - put("top${eventType.eventName.removePrefix("on")}", mapOf("registrationName" to eventType.eventName)) + put("top${eventType.eventName.removePrefix("on")}", hashMapOf("registrationName" to eventType.eventName)) } } } From adbd06e2df557b22b8a3a19073a2de1cbb964833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Moska=C5=82a?= <91079590+moskalakamil@users.noreply.github.com> Date: Mon, 22 Jul 2024 22:38:35 +0200 Subject: [PATCH 07/95] feat: add ability to define `poster` props as Image type and render poster as custom component (#3972) --- docs/pages/component/props.mdx | 33 ++++- examples/basic/src/VideoPlayer.tsx | 11 +- examples/basic/src/components/Indicator.tsx | 22 +--- examples/basic/src/components/Overlay.tsx | 26 ++-- examples/basic/src/components/VideoLoader.tsx | 15 +++ examples/basic/src/components/index.ts | 1 + examples/basic/src/constants/general.ts | 4 - examples/basic/src/styles.tsx | 10 +- src/Video.tsx | 113 ++++++++++++++---- src/types/video.ts | 21 +++- 10 files changed, 184 insertions(+), 72 deletions(-) create mode 100644 examples/basic/src/components/VideoLoader.tsx diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index ef999202..c1603b8d 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -437,13 +437,26 @@ Determine whether the media should continue playing when notifications or the Co ### `poster` +> [!WARNING] +> Value: string with a URL for the poster is deprecated, use `poster` as object instead An image to display while the video is loading -Value: string with a URL for the poster, e.g. "https://baconmockup.com/300/200/" +Value: Props for the `Image` component. The poster is visible when the source attribute is provided. + +```javascript + +```` ### `posterResizeMode` - +> [!WARNING] +> deprecated, use `poster` with `resizeMode` key instead Determines how to resize the poster image when the frame doesn't match the raw video dimensions. @@ -489,6 +502,22 @@ Speed at which the media should play. - **1.0** - Play at normal speed (default) - **Other values** - Slow down or speed up playback +### `renderLoader` + + + +Allows you to create custom components to display while the video is loading. If `renderLoader` is provided, `poster` and `posterResizeMode` will be ignored. + +```javascript + +```` + ### `repeat` diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index b67af9cd..243d2fa8 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -34,7 +34,7 @@ import Video, { import styles from './styles'; import {type AdditionalSourceInfo} from './types'; import {bufferConfig, srcList, textTracksSelectionBy} from './constants'; -import {Overlay, toast} from './components'; +import {Overlay, toast, VideoLoader} from './components'; type Props = NonNullable; @@ -68,7 +68,7 @@ const VideoPlayer: FC = ({}) => { const [repeat, setRepeat] = useState(false); const [controls, setControls] = useState(false); const [useCache, setUseCache] = useState(false); - const [poster, setPoster] = useState(undefined); + const [showPoster, setShowPoster] = useState(false); const [showNotificationControls, setShowNotificationControls] = useState(false); const [isSeeking, setIsSeeking] = useState(false); @@ -234,7 +234,6 @@ const VideoPlayer: FC = ({}) => { paused={paused} volume={volume} muted={muted} - fullscreen={fullscreen} controls={controls} resizeMode={resizeMode} onFullscreenPlayerWillDismiss={onFullScreenExit} @@ -264,7 +263,7 @@ const VideoPlayer: FC = ({}) => { cacheSizeMB: useCache ? 200 : 0, }} preventsDisplaySleepDuringVideoPlayback={true} - poster={poster} + renderLoader={showPoster ? : undefined} onPlaybackRateChange={onPlaybackRateChange} onPlaybackStateChanged={onPlaybackStateChanged} bufferingStrategy={BufferingStrategyType.DEFAULT} @@ -294,7 +293,7 @@ const VideoPlayer: FC = ({}) => { paused={paused} volume={volume} setControls={setControls} - poster={poster} + showPoster={showPoster} rate={rate} setFullscreen={setFullscreen} setPaused={setPaused} @@ -303,7 +302,7 @@ const VideoPlayer: FC = ({}) => { setIsSeeking={setIsSeeking} repeat={repeat} setRepeat={setRepeat} - setPoster={setPoster} + setShowPoster={setShowPoster} setRate={setRate} setResizeMode={setResizeMode} setShowNotificationControls={setShowNotificationControls} diff --git a/examples/basic/src/components/Indicator.tsx b/examples/basic/src/components/Indicator.tsx index 50b781a2..68691672 100644 --- a/examples/basic/src/components/Indicator.tsx +++ b/examples/basic/src/components/Indicator.tsx @@ -1,22 +1,8 @@ -import React, {FC, memo} from 'react'; -import {ActivityIndicator, View} from 'react-native'; -import styles from '../styles.tsx'; +import React, {memo} from 'react'; +import {ActivityIndicator} from 'react-native'; -type Props = { - isLoading: boolean; -}; - -const _Indicator: FC = ({isLoading}) => { - if (!isLoading) { - return ; - } - return ( - - ); +const _Indicator = () => { + return ; }; export const Indicator = memo(_Indicator); diff --git a/examples/basic/src/components/Overlay.tsx b/examples/basic/src/components/Overlay.tsx index d8c4da02..8d64baf7 100644 --- a/examples/basic/src/components/Overlay.tsx +++ b/examples/basic/src/components/Overlay.tsx @@ -5,19 +5,11 @@ import React, { type Dispatch, type SetStateAction, } from 'react'; -import {Indicator} from './Indicator.tsx'; import {View} from 'react-native'; import styles from '../styles.tsx'; import ToggleControl from '../ToggleControl.tsx'; -import { - isAndroid, - isIos, - samplePoster, - textTracksSelectionBy, -} from '../constants'; -import MultiValueControl, { - type MultiValueControlPropType, -} from '../MultiValueControl.tsx'; +import {isAndroid, isIos, textTracksSelectionBy} from '../constants'; +import MultiValueControl from '../MultiValueControl.tsx'; import { ResizeMode, VideoRef, @@ -69,8 +61,8 @@ type Props = { setPaused: Dispatch>; repeat: boolean; setRepeat: Dispatch>; - poster: string | undefined; - setPoster: Dispatch>; + showPoster: boolean; + setShowPoster: Dispatch>; muted: boolean; setMuted: Dispatch>; currentTime: number; @@ -108,8 +100,8 @@ const _Overlay = forwardRef((props, ref) => { setPaused, setRepeat, repeat, - setPoster, - poster, + setShowPoster, + showPoster, setMuted, muted, duration, @@ -217,14 +209,12 @@ const _Overlay = forwardRef((props, ref) => { const toggleRepeat = () => setRepeat(prev => !prev); - const togglePoster = () => - setPoster(prev => (prev ? undefined : samplePoster)); + const togglePoster = () => setShowPoster(prev => !prev); const toggleMuted = () => setMuted(prev => !prev); return ( <> - ((props, ref) => { { + return ( + + Loading... + + + ); +}; + +export const VideoLoader = memo(_VideoLoader); diff --git a/examples/basic/src/components/index.ts b/examples/basic/src/components/index.ts index c4ac83b5..71c9d731 100644 --- a/examples/basic/src/components/index.ts +++ b/examples/basic/src/components/index.ts @@ -1,3 +1,4 @@ +export * from './VideoLoader'; export * from './Indicator'; export * from './Seeker'; export * from './AudioTracksSelector'; diff --git a/examples/basic/src/constants/general.ts b/examples/basic/src/constants/general.ts index 02c39b90..6331d8a8 100644 --- a/examples/basic/src/constants/general.ts +++ b/examples/basic/src/constants/general.ts @@ -149,10 +149,6 @@ export const srcAndroidList = [ }, ]; -// poster which can be displayed -export const samplePoster = - 'https://upload.wikimedia.org/wikipedia/commons/1/18/React_Native_Logo.png'; - export const srcList: SampleVideoSource[] = srcAllPlatformList.concat( isAndroid ? srcAndroidList : srcIosList, ); diff --git a/examples/basic/src/styles.tsx b/examples/basic/src/styles.tsx index 0dd72390..61453b77 100644 --- a/examples/basic/src/styles.tsx +++ b/examples/basic/src/styles.tsx @@ -102,9 +102,15 @@ const styles = StyleSheet.create({ borderWidth: 1, borderColor: 'red', }, - IndicatorStyle: { - flex: 1, + indicatorContainer: { justifyContent: 'center', + alignItems: 'center', + gap: 10, + width: '100%', + height: '100%', + }, + indicatorText: { + color: 'white', }, seekbarContainer: { flex: 1, diff --git a/src/Video.tsx b/src/Video.tsx index f2f91f3b..49ca1e99 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -8,7 +8,13 @@ import React, { } from 'react'; import type {ElementRef} from 'react'; import {View, StyleSheet, Image, Platform, processColor} from 'react-native'; -import type {StyleProp, ImageStyle, NativeSyntheticEvent} from 'react-native'; +import type { + StyleProp, + ImageStyle, + NativeSyntheticEvent, + ViewStyle, + ImageResizeMode, +} from 'react-native'; import NativeVideoComponent from './specs/VideoNativeComponent'; import type { @@ -67,8 +73,9 @@ const Video = forwardRef( source, style, resizeMode, - posterResizeMode, poster, + posterResizeMode, + renderLoader, drm, textTracks, selectedVideoTrack, @@ -113,25 +120,28 @@ const Video = forwardRef( ref, ) => { const nativeRef = useRef>(null); - const [showPoster, setShowPoster] = useState(!!poster); + + const isPosterDeprecated = typeof poster === 'string'; + + const hasPoster = useMemo(() => { + if (renderLoader) { + return true; + } + + if (isPosterDeprecated) { + return !!poster; + } + + return !!poster?.source; + }, [isPosterDeprecated, poster, renderLoader]); + + const [showPoster, setShowPoster] = useState(hasPoster); + const [ _restoreUserInterfaceForPIPStopCompletionHandler, setRestoreUserInterfaceForPIPStopCompletionHandler, ] = useState(); - const hasPoster = !!poster; - - const posterStyle = useMemo>( - () => ({ - ...StyleSheet.absoluteFillObject, - resizeMode: - posterResizeMode && posterResizeMode !== 'none' - ? posterResizeMode - : 'contain', - }), - [posterResizeMode], - ); - const src = useMemo(() => { if (!source) { return undefined; @@ -598,13 +608,78 @@ const Video = forwardRef( : ViewType.SURFACE; }, [drm, useSecureView, useTextureView, viewType]); + const _renderPoster = useCallback(() => { + if (!hasPoster || !showPoster) { + return null; + } + + // poster resize mode + let _posterResizeMode: ImageResizeMode = 'contain'; + + if (!isPosterDeprecated && poster?.resizeMode) { + _posterResizeMode = poster.resizeMode; + } else if (posterResizeMode && posterResizeMode !== 'none') { + _posterResizeMode = posterResizeMode; + } + + // poster style + const baseStyle: StyleProp = { + ...StyleSheet.absoluteFillObject, + resizeMode: _posterResizeMode, + }; + + let posterStyle: StyleProp = baseStyle; + + if (!isPosterDeprecated && poster?.style) { + const styles = Array.isArray(poster.style) + ? poster.style + : [poster.style]; + posterStyle = [baseStyle, ...styles]; + } + + // render poster + if (renderLoader && (poster || posterResizeMode)) { + console.warn( + 'You provided both `renderLoader` and `poster` or `posterResizeMode` props. `renderLoader` will be used.', + ); + } + + // render loader + if (renderLoader) { + return {renderLoader}; + } + + return ( + + ); + }, [ + hasPoster, + isPosterDeprecated, + poster, + posterResizeMode, + renderLoader, + showPoster, + ]); + + const _style: StyleProp = useMemo( + () => ({ + ...StyleSheet.absoluteFillObject, + ...(showPoster ? {display: 'none'} : {}), + }), + [showPoster], + ); + return ( ( } viewType={_viewType} /> - {hasPoster && showPoster ? ( - - ) : null} + {_renderPoster()} ); }, diff --git a/src/types/video.ts b/src/types/video.ts index 1dbdc9f1..eb075f9c 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -1,6 +1,14 @@ import type {ISO639_1} from './language'; import type {ReactVideoEvents} from './events'; -import type {StyleProp, ViewProps, ViewStyle} from 'react-native'; +import type { + ImageProps, + StyleProp, + ViewProps, + ViewStyle, + ImageRequireSource, + ImageURISource, +} from 'react-native'; +import type {ReactNode} from 'react'; import type VideoResizeMode from './ResizeMode'; import type FilterType from './FilterType'; import type ViewType from './ViewType'; @@ -34,6 +42,13 @@ export type ReactVideoSource = Readonly< } >; +export type ReactVideoPosterSource = ImageURISource | ImageRequireSource; + +export type ReactVideoPoster = Omit & { + // prevents giving source in the array + source?: ReactVideoPosterSource; +}; + export type VideoMetadata = Readonly<{ title?: string; subtitle?: string; @@ -243,12 +258,14 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps { pictureInPicture?: boolean; // iOS playInBackground?: boolean; playWhenInactive?: boolean; // iOS - poster?: string; + poster?: string | ReactVideoPoster; // string is deprecated + /** @deprecated use **resizeMode** key in **poster** props instead */ posterResizeMode?: EnumValues; preferredForwardBufferDuration?: number; // iOS preventsDisplaySleepDuringVideoPlayback?: boolean; progressUpdateInterval?: number; rate?: number; + renderLoader?: ReactNode; repeat?: boolean; reportBandwidth?: boolean; //Android resizeMode?: EnumValues; From 6189080c9aac89aa3d2a4e60049999d8880bc971 Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Wed, 24 Jul 2024 11:37:19 +0330 Subject: [PATCH 08/95] feat(android): add error handling for Kotlin version (#4018) * feat(android): add error handling for Kotlin version mismatch * fix: lint error * refactor: use variables from gradle file * chore: downgrade required Kotlin version * refactor: check Kotlin version * refactor: kotlin variables in build.gradle * refactor: kotlin variables in build.gradle * chore(doc): update document * chore: add dependency to build.gradle for a specific version of react-native * fix: remove additional dependency --- android/build.gradle | 31 ++++++++++++++++--- android/gradle.properties | 2 +- .../exoplayer/FullScreenPlayerView.kt | 5 ++- docs/pages/installation.md | 6 ++-- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 7920bf65..5492c667 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,6 +5,21 @@ apply plugin: 'kotlin-android' buildscript { def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['RNVideo_kotlinVersion'] + def requiredKotlinVersion = project.properties['RNVideo_kotlinVersion'] + + def isVersionAtLeast = { version, requiredVersion -> + def (v1, v2) = [version, requiredVersion].collect { it.tokenize('.')*.toInteger() } + for (int i = 0; i < Math.max(v1.size(), v2.size()); i++) { + int val1 = i < v1.size() ? v1[i] : 0 + int val2 = i < v2.size() ? v2[i] : 0 + if (val1 < val2) { + return false + } else if (val1 > val2) { + return true + } + } + return true + } repositories { mavenCentral() @@ -13,9 +28,17 @@ buildscript { dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") } + + ext { + if (!isVersionAtLeast(kotlin_version, requiredKotlinVersion)) { + throw new GradleException("Kotlin version mismatch: Project is using Kotlin version $kotlin_version, but it must be at least $requiredKotlinVersion. Please update the Kotlin version.") + } else { + println("Kotlin version is correct: $kotlin_version") + } + } } -// This looks funny but it's necessary to keep backwards compatibility (: +// This looks funny but it's necessary to keep backwards compatibility (: def safeExtGet(prop) { return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : rootProject.ext.has("RNVideo_" + prop) ? @@ -184,7 +207,6 @@ repositories { } def media3_version = safeExtGet('media3Version') -def kotlin_version = safeExtGet('kotlinVersion') def androidxCore_version = safeExtGet('androidxCoreVersion') def androidxActivity_version = safeExtGet('androidxActivityVersion') @@ -239,7 +261,7 @@ dependencies { implementation "androidx.media3:media3-exoplayer-rtsp:$media3_version" } } - + // For ad insertion using the Interactive Media Ads SDK with ExoPlayer if (ExoplayerDependencies["useExoplayerIMA"]) { if (media3_buildFromSource) { @@ -280,5 +302,4 @@ dependencies { // Common functionality used across multiple media libraries implementation "androidx.media3:media3-common:$media3_version" } - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" -} +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index d4b23166..46daceb2 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ -RNVideo_kotlinVersion=1.7.0 +RNVideo_kotlinVersion=1.8.0 RNVideo_minSdkVersion=23 RNVideo_targetSdkVersion=34 RNVideo_compileSdkVersion=34 diff --git a/android/src/main/java/com/brentvatne/exoplayer/FullScreenPlayerView.kt b/android/src/main/java/com/brentvatne/exoplayer/FullScreenPlayerView.kt index b66c1e88..00f93975 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/FullScreenPlayerView.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/FullScreenPlayerView.kt @@ -91,13 +91,12 @@ class FullScreenPlayerView( parent = null } - private fun getFullscreenIconResource(isFullscreen: Boolean): Int { - return if (isFullscreen) { + private fun getFullscreenIconResource(isFullscreen: Boolean): Int = + if (isFullscreen) { androidx.media3.ui.R.drawable.exo_icon_fullscreen_exit } else { androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter } - } private fun updateFullscreenButton(playerControlView: LegacyPlayerControlView, isFullscreen: Boolean) { val imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen) diff --git a/docs/pages/installation.md b/docs/pages/installation.md index e09aa972..26e1207c 100644 --- a/docs/pages/installation.md +++ b/docs/pages/installation.md @@ -57,12 +57,14 @@ $RNVideoUseGoogleIMA=true ## Android -From version >= 6.0.0, your application needs to have kotlin version >= 1.7.0 +From version >= 6.0.0, your application needs to have kotlin version >= 1.8.0 ```: buildscript { ... - ext.kotlinVersion = '1.7.0' + ext.kotlinVersion = '1.8.0', + ext.compileSdkVersion = 34 + ext.targetSdkVersion = 34 ... } ``` From fb1d6bdef7210d43f9e34f673691a9a17b95424e Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Wed, 24 Jul 2024 11:38:37 +0330 Subject: [PATCH 09/95] fix: index of the selected track (#4012) --- .../main/java/com/brentvatne/exoplayer/ReactExoplayerView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 33df071a..8ebba8c5 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -1591,7 +1591,7 @@ public class ReactExoplayerView extends FrameLayout implements if (format.sampleMimeType != null) track.setMimeType(format.sampleMimeType); if (format.language != null) track.setLanguage(format.language); if (format.label != null) track.setTitle(format.label); - track.setSelected(isTrackSelected(selection, group, 0)); + track.setSelected(isTrackSelected(selection, group, trackIndex)); return track; } From 5abfb0d44831dd2834d98c15dcb70fc1a1169db8 Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Wed, 24 Jul 2024 10:12:58 +0200 Subject: [PATCH 10/95] chore: release v6.4.3 --- CHANGELOG.md | 18 ++++++++++++++++++ package.json | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23c99389..5655a328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ +## [6.4.3](https://github.com/TheWidlarzGroup/react-native-video/compare/v6.4.2...v6.4.3) (2024-07-24) + + +### Bug Fixes + +* **android:** app crash at boot with old arch ([#4022](https://github.com/TheWidlarzGroup/react-native-video/issues/4022)) ([1ee5811](https://github.com/TheWidlarzGroup/react-native-video/commit/1ee5811c8e0ecfc2486f5120b575b57c6396e0f8)), closes [#3875](https://github.com/TheWidlarzGroup/react-native-video/issues/3875) +* **android:** fix backward compatibility ([#4020](https://github.com/TheWidlarzGroup/react-native-video/issues/4020)) ([ab7e02e](https://github.com/TheWidlarzGroup/react-native-video/commit/ab7e02e4538c97340f13fb052b1cad94408b48fa)) +* **android:** resize mode cover calculation ([#4010](https://github.com/TheWidlarzGroup/react-native-video/issues/4010)) ([9f38216](https://github.com/TheWidlarzGroup/react-native-video/commit/9f382163d83c3331518e2784b335da28179ac91d)) +* index of the selected track ([#4012](https://github.com/TheWidlarzGroup/react-native-video/issues/4012)) ([fb1d6bd](https://github.com/TheWidlarzGroup/react-native-video/commit/fb1d6bdef7210d43f9e34f673691a9a17b95424e)) +* **sample:** boot failure on emulator ([#4016](https://github.com/TheWidlarzGroup/react-native-video/issues/4016)) ([ffbc977](https://github.com/TheWidlarzGroup/react-native-video/commit/ffbc977ff90248aeb270626620f5c3553f955617)) + + +### Features + +* add ability to define `poster` props as Image type and render poster as custom component ([#3972](https://github.com/TheWidlarzGroup/react-native-video/issues/3972)) ([adbd06e](https://github.com/TheWidlarzGroup/react-native-video/commit/adbd06e2df557b22b8a3a19073a2de1cbb964833)) +* **android:** add error handling for Kotlin version ([#4018](https://github.com/TheWidlarzGroup/react-native-video/issues/4018)) ([6189080](https://github.com/TheWidlarzGroup/react-native-video/commit/6189080c9aac89aa3d2a4e60049999d8880bc971)) +* **android:** set originalFitsSystemWindows on fullscreen open ([#4013](https://github.com/TheWidlarzGroup/react-native-video/issues/4013)) ([2f70c02](https://github.com/TheWidlarzGroup/react-native-video/commit/2f70c02cdcf6488338df197feb61eeb10ed3281f)) + ## [6.4.2](https://github.com/TheWidlarzGroup/react-native-video/compare/v6.4.1...v6.4.2) (2024-07-15) diff --git a/package.json b/package.json index 494296bb..42cecf76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "6.4.2", + "version": "6.4.3", "description": "A An unknown DRM error occurred + + Settings + + Playback Speed + + Select Playback Speed diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index 8395a300..d636dd63 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -157,6 +157,7 @@ Adjust the control styles. This prop is need only if `controls={true}` and is an | hideDuration | boolean | The default value is `false`, allowing you to hide the duration. | | hideNavigationBarOnFullScreenMode | boolean | The default value is `true`, allowing you to hide the navigation bar on full-screen mode. | | hideNotificationBarOnFullScreenMode | boolean | The default value is `true`, allowing you to hide the notification bar on full-screen mode. | +| hideSettingButton | boolean | The default value is `true`, allowing you to hide the setting button. | | seekIncrementMS | number | The default value is `10000`. You can change the value to increment forward and rewind. | | liveLabel | string | Allowing you to set a label for live video. | @@ -175,6 +176,7 @@ controlsStyles={{ hideDuration: false, hideNavigationBarOnFullScreenMode: true, hideNotificationBarOnFullScreenMode: true, + hideSettingButton: true, seekIncrementMS: 10000, liveLabel: "LIVE" }} diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index e9e69dfa..800f05d2 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -308,6 +308,7 @@ type ControlsStyles = Readonly<{ hideDuration?: WithDefault; hideNavigationBarOnFullScreenMode?: WithDefault; hideNotificationBarOnFullScreenMode?: WithDefault; + hideSettingButton?: WithDefault; seekIncrementMS?: Int32; liveLabel?: string; }>; diff --git a/src/types/video.ts b/src/types/video.ts index ab71d69f..1d6e9077 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -260,6 +260,7 @@ export type ControlsStyles = { hideFullscreen?: boolean; hideNavigationBarOnFullScreenMode?: boolean; hideNotificationBarOnFullScreenMode?: boolean; + hideSettingButton?: boolean; seekIncrementMS?: number; liveLabel?: string; }; From 2c19a4770df73179436a9e23a5e55ad0699fcfcc Mon Sep 17 00:00:00 2001 From: Wojciech Ogrodowczyk Date: Mon, 7 Oct 2024 14:32:35 +0200 Subject: [PATCH 89/95] fix(iOS): pause video on end reached & don't remove listeners (#4218) This fixes an issue when seeking back to the beginning results in no `onProgress` events being fired. --- ios/Video/RCTVideo.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 751c7b4a..92cea601 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -1631,7 +1631,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } ) } else { - _playerObserver.removePlayerTimeObserver() + _player?.pause() + _player?.rate = 0.0 } } From 4c9db2845bb7313d6c93aa3bc2a78b306f7e7cca Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Thu, 10 Oct 2024 07:24:11 +0200 Subject: [PATCH 90/95] chore: Add react compiler workaround (#4227) --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 575ac839..fc88496d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,4 +2,5 @@ import Video from './Video'; export {VideoDecoderProperties} from './VideoDecoderProperties'; export * from './types'; export type {VideoRef} from './Video'; +export {Video}; export default Video; From 9a3fcda3b8ca4689c9131a12a8375fc43d442f80 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Thu, 10 Oct 2024 22:59:41 +0200 Subject: [PATCH 91/95] feat: add setSource API function fix ads playback (#4185) * feat: add setSource API function fix ads playback --- .../brentvatne/exoplayer/ExoPlayerView.java | 9 +- .../exoplayer/ReactExoplayerView.java | 73 +++---- .../exoplayer/ReactExoplayerViewManager.kt | 7 +- .../brentvatne/react/VideoManagerModule.kt | 9 + docs/pages/component/methods.mdx | 10 + examples/basic/src/VideoPlayer.tsx | 6 +- ios/Video/RCTVideoManager.m | 1 + ios/Video/RCTVideoManager.swift | 7 + src/Video.tsx | 199 ++++++++++-------- src/specs/NativeVideoManager.ts | 1 + 10 files changed, 189 insertions(+), 133 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index e8dc92c9..78d15763 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -93,6 +93,13 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { } } + public void showAds() { + adOverlayFrameLayout.setVisibility(View.GONE); + } + public void hideAds() { + adOverlayFrameLayout.setVisibility(View.VISIBLE); + } + private void clearVideoView() { if (surfaceView instanceof TextureView) { player.clearVideoTextureView((TextureView) surfaceView); @@ -189,7 +196,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { surfaceView.setAlpha(0); } - private void updateShutterViewVisibility() { + public void updateShutterViewVisibility() { if (this.hideShutterView) { hideShutterView(); } else { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index aec2d692..c340e149 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -735,22 +735,28 @@ public class ReactExoplayerView extends FrameLayout implements ReactExoplayerView self = this; Activity activity = themedReactContext.getCurrentActivity(); // This ensures all props have been settled, to avoid async racing conditions. + Source runningSource = source; mainRunnable = () -> { - if (viewHasDropped) { + if (viewHasDropped && runningSource == source) { return; } try { + if (runningSource.getUri() == null) { + return; + } if (player == null) { // Initialize core configuration and listeners initializePlayerCore(self); } - if (playerNeedsSource && source.getUri() != null) { + if (playerNeedsSource) { + // Will force display of shutter view if needed + exoPlayerView.updateShutterViewVisibility(); exoPlayerView.invalidateAspectRatio(); // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread ExecutorService es = Executors.newSingleThreadExecutor(); es.execute(() -> { // DRM initialization must run on a different thread - if (viewHasDropped) { + if (viewHasDropped && runningSource == source) { return; } if (activity == null) { @@ -761,12 +767,12 @@ public class ReactExoplayerView extends FrameLayout implements // Initialize handler to run on the main thread activity.runOnUiThread(() -> { - if (viewHasDropped) { + if (viewHasDropped && runningSource == source) { return; } try { // Source initialization must run on the main thread - initializePlayerSource(); + initializePlayerSource(runningSource); } catch (Exception ex) { self.playerNeedsSource = true; DebugLog.e(TAG, "Failed to initialize Player! 1"); @@ -776,8 +782,8 @@ public class ReactExoplayerView extends FrameLayout implements } }); }); - } else if (source.getUri() != null) { - initializePlayerSource(); + } else if (runningSource == source) { + initializePlayerSource(runningSource); } } catch (Exception ex) { self.playerNeedsSource = true; @@ -816,6 +822,11 @@ public class ReactExoplayerView extends FrameLayout implements .setEnableDecoderFallback(true) .forceEnableMediaCodecAsynchronousQueueing(); + DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory); + if (useCache) { + mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); + } + ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); imaSdkSettings.setLanguage(adLanguage); @@ -826,14 +837,7 @@ public class ReactExoplayerView extends FrameLayout implements .setAdEventListener(this) .setAdErrorListener(this) .build(); - DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory); - if (useCache) { - mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); - } - - if (adsLoader != null) { - mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); - } + mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); player = new ExoPlayer.Builder(getContext(), renderersFactory) .setTrackSelector(self.trackSelector) @@ -846,6 +850,7 @@ public class ReactExoplayerView extends FrameLayout implements player.addListener(self); player.setVolume(muted ? 0.f : audioVolume * 1); exoPlayerView.setPlayer(player); + if (adsLoader != null) { adsLoader.setPlayer(player); } @@ -884,31 +889,28 @@ public class ReactExoplayerView extends FrameLayout implements return drmSessionManager; } - private void initializePlayerSource() { - if (source.getUri() == null) { + private void initializePlayerSource(Source runningSource) { + if (runningSource.getUri() == null) { return; } /// init DRM DrmSessionManager drmSessionManager = initializePlayerDrm(); - if (drmSessionManager == null && source.getDrmProps() != null && source.getDrmProps().getDrmType() != null) { + if (drmSessionManager == null && runningSource.getDrmProps() != null && runningSource.getDrmProps().getDrmType() != null) { // Failed to initialize DRM session manager - cannot continue DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!"); return; } // init source to manage ads and external text tracks ArrayList mediaSourceList = buildTextSources(); - MediaSource videoSource = buildMediaSource(source.getUri(), source.getExtension(), drmSessionManager, source.getCropStartMs(), source.getCropEndMs()); + MediaSource videoSource = buildMediaSource(runningSource.getUri(), runningSource.getExtension(), drmSessionManager, runningSource.getCropStartMs(), runningSource.getCropEndMs()); MediaSource mediaSourceWithAds = null; - if (adTagUrl != null && adsLoader != null) { + if (adTagUrl != null && BuildConfig.USE_EXOPLAYER_IMA) { DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); DataSpec adTagDataSpec = new DataSpec(adTagUrl); - mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(source.getUri(), adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView); - } else { - if (adTagUrl == null && adsLoader != null) { - adsLoader.release(); - adsLoader = null; - } + DebugLog.w(TAG, "ads " + adTagUrl); + mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(runningSource.getUri(), adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView); + exoPlayerView.showAds(); } MediaSource mediaSource; if (mediaSourceList.isEmpty()) { @@ -943,8 +945,8 @@ public class ReactExoplayerView extends FrameLayout implements if (haveResumePosition) { player.seekTo(resumeWindow, resumePosition); player.setMediaSource(mediaSource, false); - } else if (source.getStartPositionMs() > 0) { - player.setMediaSource(mediaSource, source.getStartPositionMs()); + } else if (runningSource.getStartPositionMs() > 0) { + player.setMediaSource(mediaSource, runningSource.getStartPositionMs()); } else { player.setMediaSource(mediaSource, true); } @@ -1243,10 +1245,6 @@ public class ReactExoplayerView extends FrameLayout implements private void releasePlayer() { if (player != null) { - if (adsLoader != null) { - adsLoader.setPlayer(null); - } - if(playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); themedReactContext.unbindService(playbackServiceConnection); @@ -1903,19 +1901,21 @@ public class ReactExoplayerView extends FrameLayout implements if (!isSourceEqual) { reloadSource(); } + } else { + clearSrc(); } } - public void clearSrc() { if (source.getUri() != null) { if (player != null) { player.stop(); player.clearMediaItems(); } - this.source = new Source(); - this.mediaDataSourceFactory = null; - clearResumePosition(); } + exoPlayerView.hideAds(); + this.source = new Source(); + this.mediaDataSourceFactory = null; + clearResumePosition(); } public void setProgressUpdateInterval(final float progressUpdateInterval) { @@ -1927,6 +1927,7 @@ public class ReactExoplayerView extends FrameLayout implements } public void setAdTagUrl(final Uri uri) { + DebugLog.w(TAG, "setAdTagUrl" + uri); adTagUrl = uri; } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt index a21b29a3..9921ca03 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt @@ -89,12 +89,7 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View @ReactProp(name = PROP_SRC) fun setSrc(videoView: ReactExoplayerView, src: ReadableMap?) { val context = videoView.context.applicationContext - val source = Source.parse(src, context) - if (source.uri == null) { - videoView.clearSrc() - } else { - videoView.setSrc(source) - } + videoView.setSrc(Source.parse(src, context)) } @ReactProp(name = PROP_AD_TAG_URL) diff --git a/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt b/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt index f6c0affe..d3968276 100644 --- a/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt +++ b/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt @@ -1,10 +1,12 @@ package com.brentvatne.react +import com.brentvatne.common.api.Source import com.brentvatne.exoplayer.ReactExoplayerView 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 com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.common.UIManagerType @@ -63,6 +65,13 @@ class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextB } } + @ReactMethod + fun setSourceCmd(reactTag: Int, source: ReadableMap?) { + performOnPlayerView(reactTag) { + it?.setSrc(Source.parse(source, reactApplicationContext)) + } + } + @ReactMethod fun getCurrentPosition(reactTag: Int, promise: Promise) { performOnPlayerView(reactTag) { diff --git a/docs/pages/component/methods.mdx b/docs/pages/component/methods.mdx index 7e66040b..b3b0ebc0 100644 --- a/docs/pages/component/methods.mdx +++ b/docs/pages/component/methods.mdx @@ -115,6 +115,16 @@ This function will change the volume exactly like [volume](./props#volume) prope This function retrieves and returns the precise current position of the video playback, measured in seconds. This function will throw an error if player is not initialized. + +### `setSource` + + + +`setSource(source: ReactVideoSource): Promise` + +This function will change the source exactly like [source](./props#source) property. +Changing source with this function will overide source provided as props. + ### `setFullScreen` diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index 5956124d..feb4db90 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -252,6 +252,10 @@ const VideoPlayer: FC = ({}) => { cacheSizeMB: useCache ? 200 : 0, }; + useEffect(() => { + videoRef.current?.setSource(currentSrc) + }, [currentSrc]) + return (