Repository: TheWidlarzGroup/react-native-video Branch: master Commit: a01d203bcbaf Files: 616 Total size: 1.7 MB Directory structure: gitextract_c7gy42g0/ ├── .github/ │ ├── actions/ │ │ └── setup-bun/ │ │ └── action.yml │ └── workflows/ │ ├── deploy-docs.yml │ └── test-docs-build.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bunfig.toml ├── config/ │ ├── .editorconfig │ ├── .eslintrc.js │ └── tsconfig.json ├── docs/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── docs/ │ │ ├── fundamentals/ │ │ │ ├── _category_.json │ │ │ ├── configuration/ │ │ │ │ ├── _category_.json │ │ │ │ ├── with-expo.md │ │ │ │ └── without-expo.md │ │ │ ├── installation.md │ │ │ └── intro.mdx │ │ ├── offer.mdx │ │ ├── player/ │ │ │ ├── _category_.json │ │ │ ├── analytics/ │ │ │ │ ├── _category_.json │ │ │ │ ├── manual.md │ │ │ │ └── simple.md │ │ │ ├── downloading/ │ │ │ │ ├── _category_.json │ │ │ │ ├── downloading.md │ │ │ │ ├── drm-downloading.md │ │ │ │ ├── events-downloading.md │ │ │ │ ├── getting-started.md │ │ │ │ └── track-selection.md │ │ │ ├── drm.md │ │ │ ├── events.md │ │ │ ├── player.md │ │ │ ├── use-video-player.md │ │ │ └── video-player.md │ │ ├── plugins/ │ │ │ ├── _category_.json │ │ │ ├── ask-for-plugin.md │ │ │ ├── examples.md │ │ │ ├── interface.md │ │ │ ├── plugins.mdx │ │ │ ├── registry.md │ │ │ └── use-in-third-party-library.md │ │ ├── projects.md │ │ ├── updating.md │ │ └── video-view/ │ │ ├── _category_.json │ │ ├── chapters.md │ │ ├── events.md │ │ ├── methods.md │ │ ├── props.md │ │ └── video-view.md │ ├── docusaurus.config.ts │ ├── package.json │ ├── sidebars.ts │ ├── src/ │ │ ├── components/ │ │ │ ├── Homepage/ │ │ │ │ ├── Enterprise/ │ │ │ │ │ ├── Enterprise.module.css │ │ │ │ │ ├── Enterprise.tsx │ │ │ │ │ └── FeatureCard/ │ │ │ │ │ ├── FeatureCard.module.css │ │ │ │ │ └── FeatureCard.tsx │ │ │ │ ├── Features/ │ │ │ │ │ ├── Feature/ │ │ │ │ │ │ ├── Feature.module.css │ │ │ │ │ │ └── Feature.tsx │ │ │ │ │ ├── Features.module.css │ │ │ │ │ └── Features.tsx │ │ │ │ └── Hero/ │ │ │ │ ├── Badge/ │ │ │ │ │ ├── Badge.module.css │ │ │ │ │ └── Badge.tsx │ │ │ │ ├── Buttons/ │ │ │ │ │ ├── Buttons.module.css │ │ │ │ │ └── Buttons.tsx │ │ │ │ ├── Hero.module.css │ │ │ │ ├── Hero.tsx │ │ │ │ ├── ScrollIndicator/ │ │ │ │ │ ├── ScrollIndicator.module.css │ │ │ │ │ └── ScrollIndicator.tsx │ │ │ │ └── Stats/ │ │ │ │ ├── Stats.module.css │ │ │ │ └── Stats.tsx │ │ │ ├── Intro/ │ │ │ │ ├── V7ApiModel/ │ │ │ │ │ ├── V7ApiModel.module.css │ │ │ │ │ └── V7ApiModel.tsx │ │ │ │ ├── V7FeatureCards/ │ │ │ │ │ ├── V7FeatureCards.module.css │ │ │ │ │ └── V7FeatureCards.tsx │ │ │ │ ├── V7Lead/ │ │ │ │ │ ├── V7Lead.module.css │ │ │ │ │ └── V7Lead.tsx │ │ │ │ ├── V7NitroSection/ │ │ │ │ │ ├── V7NitroSection.module.css │ │ │ │ │ └── V7NitroSection.tsx │ │ │ │ ├── V7OpenSource/ │ │ │ │ │ ├── V7OpenSource.module.css │ │ │ │ │ └── V7OpenSource.tsx │ │ │ │ ├── V7ProPlugins/ │ │ │ │ │ ├── V7ProPlugins.module.css │ │ │ │ │ └── V7ProPlugins.tsx │ │ │ │ ├── V7StatusTimeline/ │ │ │ │ │ ├── V7StatusTimeline.module.css │ │ │ │ │ └── V7StatusTimeline.tsx │ │ │ │ └── index.ts │ │ │ ├── Offer/ │ │ │ │ ├── Contact/ │ │ │ │ │ ├── Contact.module.css │ │ │ │ │ └── Contact.tsx │ │ │ │ ├── DecisionTable/ │ │ │ │ │ ├── DecisionTable.module.css │ │ │ │ │ └── DecisionTable.tsx │ │ │ │ ├── Extensions/ │ │ │ │ │ ├── Extensions.module.css │ │ │ │ │ └── Extensions.tsx │ │ │ │ ├── Hero/ │ │ │ │ │ ├── IntroHero.module.css │ │ │ │ │ └── IntroHero.tsx │ │ │ │ ├── HowItWorks/ │ │ │ │ │ ├── HowItWorks.module.css │ │ │ │ │ └── HowItWorks.tsx │ │ │ │ ├── Migration/ │ │ │ │ │ ├── Migration.module.css │ │ │ │ │ └── Migration.tsx │ │ │ │ ├── OfferCards/ │ │ │ │ │ ├── OfferCards.module.css │ │ │ │ │ └── OfferCards.tsx │ │ │ │ ├── Services/ │ │ │ │ │ ├── Services.module.css │ │ │ │ │ └── Services.tsx │ │ │ │ └── index.ts │ │ │ └── PlatformsList/ │ │ │ ├── PlatformsList.module.css │ │ │ └── PlatformsList.tsx │ │ ├── css/ │ │ │ └── custom.css │ │ └── pages/ │ │ ├── index.module.css │ │ └── index.tsx │ ├── static/ │ │ └── .nojekyll │ ├── tsconfig.json │ ├── versioned_docs/ │ │ └── version-6.x/ │ │ ├── component/ │ │ │ ├── _category_.json │ │ │ ├── ads.md │ │ │ ├── drm.mdx │ │ │ ├── events.mdx │ │ │ ├── methods.mdx │ │ │ └── props.mdx │ │ ├── installation.md │ │ ├── intro.md │ │ ├── other/ │ │ │ ├── _category_.json │ │ │ ├── caching.md │ │ │ ├── debug.md │ │ │ ├── downloading.md │ │ │ ├── expo.md │ │ │ ├── misc.md │ │ │ ├── new-arch.md │ │ │ └── plugin.md │ │ ├── projects.md │ │ └── updating.md │ ├── versioned_sidebars/ │ │ └── version-6.x-sidebars.json │ └── versions.json ├── example/ │ ├── .bundle/ │ │ └── config │ ├── .eslintrc.js │ ├── .gitignore │ ├── .watchmanconfig │ ├── Gemfile │ ├── android/ │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ ├── debug.keystore │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── videoexample/ │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ └── rn_edit_text_material.xml │ │ │ └── values/ │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ ├── app.json │ ├── babel.config.js │ ├── index.js │ ├── ios/ │ │ ├── .xcode.env │ │ ├── Podfile │ │ ├── VideoExample/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Images.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ ├── LaunchScreen.storyboard │ │ │ └── PrivacyInfo.xcprivacy │ │ ├── VideoExample-Bridging-Header.h │ │ ├── VideoExample.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── VideoExample.xcscheme │ │ ├── VideoExample.xcworkspace/ │ │ │ └── contents.xcworkspacedata │ │ └── VideoExampleTests/ │ │ ├── Info.plist │ │ └── VideoExampleTests.m │ ├── metro.config.js │ ├── package.json │ ├── src/ │ │ ├── App.tsx │ │ ├── components/ │ │ │ ├── Controls.tsx │ │ │ └── TextTrackManager.tsx │ │ ├── styles.ts │ │ ├── types/ │ │ │ └── videoSettings.ts │ │ └── utils/ │ │ ├── time.ts │ │ └── videoSource.ts │ └── tsconfig.json ├── lefthook.yml ├── package.json ├── packages/ │ ├── drm-plugin/ │ │ ├── .eslintrc.js │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .watchmanconfig │ │ ├── README.md │ │ ├── ReactNativeVideoDrm.podspec │ │ ├── android/ │ │ │ ├── CMakeLists.txt │ │ │ ├── build.gradle │ │ │ ├── gradle.properties │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── cpp/ │ │ │ │ └── cpp-adapter.cpp │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── twg/ │ │ │ └── videodrm/ │ │ │ ├── DRMManager/ │ │ │ │ └── DRMManager.kt │ │ │ ├── DRMPlugin.kt │ │ │ ├── PluginManager.kt │ │ │ └── VideoDrmPackage.kt │ │ ├── babel.config.js │ │ ├── ios/ │ │ │ ├── DRMManager/ │ │ │ │ ├── DRMManager+AVContentKeySessionDelegate.swift │ │ │ │ └── DRMManager.swift │ │ │ ├── DRMPlugin.swift │ │ │ └── PluginManager.swift │ │ ├── nitro.json │ │ ├── nitrogen/ │ │ │ └── generated/ │ │ │ ├── .gitattributes │ │ │ ├── android/ │ │ │ │ ├── ReactNativeVideoDrm+autolinking.cmake │ │ │ │ ├── ReactNativeVideoDrm+autolinking.gradle │ │ │ │ ├── ReactNativeVideoDrmOnLoad.cpp │ │ │ │ ├── ReactNativeVideoDrmOnLoad.hpp │ │ │ │ ├── c++/ │ │ │ │ │ ├── JHybridPluginManagerSpec.cpp │ │ │ │ │ └── JHybridPluginManagerSpec.hpp │ │ │ │ └── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── margelo/ │ │ │ │ └── nitro/ │ │ │ │ └── videodrm/ │ │ │ │ ├── HybridPluginManagerSpec.kt │ │ │ │ └── ReactNativeVideoDrmOnLoad.kt │ │ │ ├── ios/ │ │ │ │ ├── ReactNativeVideoDrm+autolinking.rb │ │ │ │ ├── ReactNativeVideoDrm-Swift-Cxx-Bridge.cpp │ │ │ │ ├── ReactNativeVideoDrm-Swift-Cxx-Bridge.hpp │ │ │ │ ├── ReactNativeVideoDrm-Swift-Cxx-Umbrella.hpp │ │ │ │ ├── ReactNativeVideoDrmAutolinking.mm │ │ │ │ ├── ReactNativeVideoDrmAutolinking.swift │ │ │ │ ├── c++/ │ │ │ │ │ ├── HybridPluginManagerSpecSwift.cpp │ │ │ │ │ └── HybridPluginManagerSpecSwift.hpp │ │ │ │ └── swift/ │ │ │ │ ├── HybridPluginManagerSpec.swift │ │ │ │ └── HybridPluginManagerSpec_cxx.swift │ │ │ └── shared/ │ │ │ └── c++/ │ │ │ ├── HybridPluginManagerSpec.cpp │ │ │ └── HybridPluginManagerSpec.hpp │ │ ├── package.json │ │ ├── src/ │ │ │ ├── PluginManager.nitro.ts │ │ │ └── index.tsx │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── react-native-video/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .watchmanconfig │ ├── ReactNativeVideo.podspec │ ├── android/ │ │ ├── CMakeLists.txt │ │ ├── build.gradle │ │ ├── fix-prefab.gradle │ │ ├── gradle.properties │ │ └── src/ │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── AndroidManifestNew.xml │ │ │ ├── cpp/ │ │ │ │ └── cpp-adapter.cpp │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── twg/ │ │ │ │ └── video/ │ │ │ │ ├── core/ │ │ │ │ │ ├── AudioFocusManager.kt │ │ │ │ │ ├── VideoError.kt │ │ │ │ │ ├── VideoManager.kt │ │ │ │ │ ├── extensions/ │ │ │ │ │ │ ├── ResizeMode+AspectRatioFrameLayout.kt │ │ │ │ │ │ ├── SubtitleType+toString.kt │ │ │ │ │ │ └── VideoPlaybackService+ServiceManagment.kt │ │ │ │ │ ├── fragments/ │ │ │ │ │ │ ├── FullscreenVideoFragment.kt │ │ │ │ │ │ └── PictureInPictureHelperFragment.kt │ │ │ │ │ ├── player/ │ │ │ │ │ │ ├── DRMManagerSpec.kt │ │ │ │ │ │ ├── DataSourceFactoryUtils.kt │ │ │ │ │ │ ├── MediaItemUtils.kt │ │ │ │ │ │ ├── MediaSourceUtils.kt │ │ │ │ │ │ └── OnAudioFocusChangedListener.kt │ │ │ │ │ ├── plugins/ │ │ │ │ │ │ ├── PluginsRegistry.kt │ │ │ │ │ │ └── ReactNativeVideoPlugin.kt │ │ │ │ │ ├── recivers/ │ │ │ │ │ │ └── AudioBecomingNoisyReceiver.kt │ │ │ │ │ ├── services/ │ │ │ │ │ │ └── playback/ │ │ │ │ │ │ ├── CustomMediaNotificationProvider.kt │ │ │ │ │ │ ├── VideoPlaybackCallback.kt │ │ │ │ │ │ ├── VideoPlaybackService.kt │ │ │ │ │ │ └── VideoPlaybackServiceConnection.kt │ │ │ │ │ └── utils/ │ │ │ │ │ ├── PictureInPictureUtils.kt │ │ │ │ │ ├── SmallVideoPlayerOptimizer.kt │ │ │ │ │ ├── SourceLoader.kt │ │ │ │ │ ├── TextTrackUtils.kt │ │ │ │ │ ├── Threading.kt │ │ │ │ │ ├── VideoFileHelper.kt │ │ │ │ │ ├── VideoInformationUtils.kt │ │ │ │ │ └── VideoOrientationUtils.kt │ │ │ │ ├── hybrids/ │ │ │ │ │ ├── videoplayer/ │ │ │ │ │ │ ├── HybridVideoPlayer.kt │ │ │ │ │ │ └── HybridVideoPlayerFactory.kt │ │ │ │ │ ├── videoplayereventemitter/ │ │ │ │ │ │ └── HybridVideoPlayerEventEmitter.kt │ │ │ │ │ ├── videoplayersource/ │ │ │ │ │ │ ├── HybridVideoPlayerSource.kt │ │ │ │ │ │ └── HybridVideoPlayerSourceFactory.kt │ │ │ │ │ └── videoviewviewmanager/ │ │ │ │ │ ├── HybridVideoViewViewManager.kt │ │ │ │ │ └── HybridVideoViewViewManagerFactory.kt │ │ │ │ ├── react/ │ │ │ │ │ ├── VideoPackage.kt │ │ │ │ │ └── VideoViewViewManager.kt │ │ │ │ └── view/ │ │ │ │ └── VideoView.kt │ │ │ └── res/ │ │ │ └── layout/ │ │ │ ├── player_view_surface.xml │ │ │ └── player_view_texture.xml │ │ ├── paper/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── facebook/ │ │ │ └── react/ │ │ │ └── viewmanagers/ │ │ │ ├── RNCVideoViewManagerDelegate.java │ │ │ └── RNCVideoViewManagerInterface.java │ │ └── stubs/ │ │ ├── dash/ │ │ │ └── androidx/ │ │ │ └── media3/ │ │ │ └── exoplayer/ │ │ │ └── dash/ │ │ │ ├── DashMediaSource.kt │ │ │ ├── DashUtil.kt │ │ │ └── manifest/ │ │ │ ├── AdaptationSet.kt │ │ │ ├── DashManifest.kt │ │ │ ├── Period.kt │ │ │ └── Representation.kt │ │ └── hls/ │ │ └── androidx/ │ │ └── media3/ │ │ └── exoplayer/ │ │ └── hls/ │ │ └── HlsMediaSource.kt │ ├── app.plugin.js │ ├── babel.config.js │ ├── ios/ │ │ ├── Video-Bridging-Header.h │ │ ├── core/ │ │ │ ├── Extensions/ │ │ │ │ ├── AVAsset+estimatedMemoryUsage.swift │ │ │ │ ├── AVAssetTrack+orientation.swift │ │ │ │ ├── AVMetadataItem+make.swift │ │ │ │ ├── AVPlayerItem+externalSubtitles.swift │ │ │ │ ├── AVPlayerItem+getBufferedDurration.swift │ │ │ │ ├── AVPlayerItem+setBufferConfig.swift │ │ │ │ ├── AVPlayerViewController+Fullscreen.swift │ │ │ │ ├── AVPlayerViewController+PictureInPicture.swift │ │ │ │ ├── AVURLAsset+getAssetInformation.swift │ │ │ │ ├── NSObject+PerformIfResponds.swift │ │ │ │ └── ResizeMode+VideoGravity.swift │ │ │ ├── HLSSubtitleInjector.swift │ │ │ ├── NowPlayingInfoCenterManager.swift │ │ │ ├── Plugins/ │ │ │ │ ├── PluginsRegistry.swift │ │ │ │ └── ReactNativeVideoPlugin.swift │ │ │ ├── Spec/ │ │ │ │ ├── DRMManagerSpec.swift │ │ │ │ ├── NativeVideoPlayerSourceSpec.swift │ │ │ │ └── NativeVideoPlayerSpec.swift │ │ │ ├── Utils/ │ │ │ │ ├── ExternalSubtitlesUtils.swift │ │ │ │ ├── HLSManifestParser.swift │ │ │ │ └── Weak.swift │ │ │ ├── VideoError.swift │ │ │ ├── VideoFileHelper.swift │ │ │ ├── VideoManager.swift │ │ │ └── VideoPlayerObserver.swift │ │ ├── hybrids/ │ │ │ ├── VideoPlayer/ │ │ │ │ ├── HybridVideoPlayer+Events.swift │ │ │ │ ├── HybridVideoPlayer.swift │ │ │ │ └── HybridVideoPlayerFactory.swift │ │ │ ├── VideoPlayerEmitter/ │ │ │ │ └── HybridVideoPlayerEventEmitter.swift │ │ │ ├── VideoPlayerSource/ │ │ │ │ ├── HybridVideoPlayerSource.swift │ │ │ │ ├── HybridVideoPlayerSourceFactory.swift │ │ │ │ └── SourceLoader.swift │ │ │ └── VideoViewViewManager/ │ │ │ ├── HybridVideoViewViewManager.swift │ │ │ └── HybridVideoViewViewManagerFactory.swift │ │ └── view/ │ │ ├── VideoComponentView.swift │ │ ├── VideoComponentViewObserver.swift │ │ ├── fabric/ │ │ │ ├── RCTVideoViewComponentView.h │ │ │ ├── RCTVideoViewComponentView.mm │ │ │ └── RCTVideoViewViewManager.mm │ │ └── paper/ │ │ ├── RCTVideoViewComponentView.h │ │ ├── RCTVideoViewComponentView.mm │ │ └── RCTVideoViewViewManager.m │ ├── nitro.json │ ├── nitrogen/ │ │ └── generated/ │ │ ├── .gitattributes │ │ ├── android/ │ │ │ ├── ReactNativeVideo+autolinking.cmake │ │ │ ├── ReactNativeVideo+autolinking.gradle │ │ │ ├── ReactNativeVideoOnLoad.cpp │ │ │ ├── ReactNativeVideoOnLoad.hpp │ │ │ ├── c++/ │ │ │ │ ├── JBandwidthData.hpp │ │ │ │ ├── JBufferConfig.hpp │ │ │ │ ├── JCustomVideoMetadata.hpp │ │ │ │ ├── JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__string_____OnGetLicensePayload.hpp │ │ │ │ ├── JFunc_void.hpp │ │ │ │ ├── JFunc_void_BandwidthData.hpp │ │ │ │ ├── JFunc_void_TimedMetadata.hpp │ │ │ │ ├── JFunc_void_VideoPlayerStatus.hpp │ │ │ │ ├── JFunc_void_bool.hpp │ │ │ │ ├── JFunc_void_double.hpp │ │ │ │ ├── JFunc_void_onLoadData.hpp │ │ │ │ ├── JFunc_void_onLoadStartData.hpp │ │ │ │ ├── JFunc_void_onPlaybackStateChangeData.hpp │ │ │ │ ├── JFunc_void_onProgressData.hpp │ │ │ │ ├── JFunc_void_onVolumeChangeData.hpp │ │ │ │ ├── JFunc_void_std__optional_std__variant_nitro__NullType__TextTrack__.hpp │ │ │ │ ├── JFunc_void_std__vector_std__string_.hpp │ │ │ │ ├── JHybridVideoPlayerEventEmitterSpec.cpp │ │ │ │ ├── JHybridVideoPlayerEventEmitterSpec.hpp │ │ │ │ ├── JHybridVideoPlayerFactorySpec.cpp │ │ │ │ ├── JHybridVideoPlayerFactorySpec.hpp │ │ │ │ ├── JHybridVideoPlayerSourceFactorySpec.cpp │ │ │ │ ├── JHybridVideoPlayerSourceFactorySpec.hpp │ │ │ │ ├── JHybridVideoPlayerSourceSpec.cpp │ │ │ │ ├── JHybridVideoPlayerSourceSpec.hpp │ │ │ │ ├── JHybridVideoPlayerSpec.cpp │ │ │ │ ├── JHybridVideoPlayerSpec.hpp │ │ │ │ ├── JHybridVideoViewViewManagerFactorySpec.cpp │ │ │ │ ├── JHybridVideoViewViewManagerFactorySpec.hpp │ │ │ │ ├── JHybridVideoViewViewManagerSpec.cpp │ │ │ │ ├── JHybridVideoViewViewManagerSpec.hpp │ │ │ │ ├── JIgnoreSilentSwitchMode.hpp │ │ │ │ ├── JListenerSubscription.hpp │ │ │ │ ├── JLivePlaybackParams.hpp │ │ │ │ ├── JMixAudioMode.hpp │ │ │ │ ├── JNativeDrmParams.hpp │ │ │ │ ├── JNativeExternalSubtitle.hpp │ │ │ │ ├── JNativeVideoConfig.hpp │ │ │ │ ├── JOnGetLicensePayload.hpp │ │ │ │ ├── JResizeMode.hpp │ │ │ │ ├── JResolution.hpp │ │ │ │ ├── JSourceType.hpp │ │ │ │ ├── JSubtitleType.hpp │ │ │ │ ├── JSurfaceType.hpp │ │ │ │ ├── JTextTrack.hpp │ │ │ │ ├── JTimedMetadata.hpp │ │ │ │ ├── JTimedMetadataObject.hpp │ │ │ │ ├── JVariant_NullType_HybridVideoPlayerSourceSpec.cpp │ │ │ │ ├── JVariant_NullType_HybridVideoPlayerSourceSpec.hpp │ │ │ │ ├── JVariant_NullType_TextTrack.cpp │ │ │ │ ├── JVariant_NullType_TextTrack.hpp │ │ │ │ ├── JVideoInformation.hpp │ │ │ │ ├── JVideoOrientation.hpp │ │ │ │ ├── JVideoPlayerStatus.hpp │ │ │ │ ├── JonLoadData.hpp │ │ │ │ ├── JonLoadStartData.hpp │ │ │ │ ├── JonPlaybackStateChangeData.hpp │ │ │ │ ├── JonProgressData.hpp │ │ │ │ └── JonVolumeChangeData.hpp │ │ │ └── kotlin/ │ │ │ └── com/ │ │ │ └── margelo/ │ │ │ └── nitro/ │ │ │ └── video/ │ │ │ ├── BandwidthData.kt │ │ │ ├── BufferConfig.kt │ │ │ ├── CustomVideoMetadata.kt │ │ │ ├── Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__string_____OnGetLicensePayload.kt │ │ │ ├── Func_void.kt │ │ │ ├── Func_void_BandwidthData.kt │ │ │ ├── Func_void_TimedMetadata.kt │ │ │ ├── Func_void_VideoPlayerStatus.kt │ │ │ ├── Func_void_bool.kt │ │ │ ├── Func_void_double.kt │ │ │ ├── Func_void_onLoadData.kt │ │ │ ├── Func_void_onLoadStartData.kt │ │ │ ├── Func_void_onPlaybackStateChangeData.kt │ │ │ ├── Func_void_onProgressData.kt │ │ │ ├── Func_void_onVolumeChangeData.kt │ │ │ ├── Func_void_std__optional_std__variant_nitro__NullType__TextTrack__.kt │ │ │ ├── Func_void_std__vector_std__string_.kt │ │ │ ├── HybridVideoPlayerEventEmitterSpec.kt │ │ │ ├── HybridVideoPlayerFactorySpec.kt │ │ │ ├── HybridVideoPlayerSourceFactorySpec.kt │ │ │ ├── HybridVideoPlayerSourceSpec.kt │ │ │ ├── HybridVideoPlayerSpec.kt │ │ │ ├── HybridVideoViewViewManagerFactorySpec.kt │ │ │ ├── HybridVideoViewViewManagerSpec.kt │ │ │ ├── IgnoreSilentSwitchMode.kt │ │ │ ├── ListenerSubscription.kt │ │ │ ├── LivePlaybackParams.kt │ │ │ ├── MixAudioMode.kt │ │ │ ├── NativeDrmParams.kt │ │ │ ├── NativeExternalSubtitle.kt │ │ │ ├── NativeVideoConfig.kt │ │ │ ├── OnGetLicensePayload.kt │ │ │ ├── ReactNativeVideoOnLoad.kt │ │ │ ├── ResizeMode.kt │ │ │ ├── Resolution.kt │ │ │ ├── SourceType.kt │ │ │ ├── SubtitleType.kt │ │ │ ├── SurfaceType.kt │ │ │ ├── TextTrack.kt │ │ │ ├── TimedMetadata.kt │ │ │ ├── TimedMetadataObject.kt │ │ │ ├── Variant_NullType_HybridVideoPlayerSourceSpec.kt │ │ │ ├── Variant_NullType_TextTrack.kt │ │ │ ├── VideoInformation.kt │ │ │ ├── VideoOrientation.kt │ │ │ ├── VideoPlayerStatus.kt │ │ │ ├── onLoadData.kt │ │ │ ├── onLoadStartData.kt │ │ │ ├── onPlaybackStateChangeData.kt │ │ │ ├── onProgressData.kt │ │ │ └── onVolumeChangeData.kt │ │ ├── ios/ │ │ │ ├── ReactNativeVideo+autolinking.rb │ │ │ ├── ReactNativeVideo-Swift-Cxx-Bridge.cpp │ │ │ ├── ReactNativeVideo-Swift-Cxx-Bridge.hpp │ │ │ ├── ReactNativeVideo-Swift-Cxx-Umbrella.hpp │ │ │ ├── ReactNativeVideoAutolinking.mm │ │ │ ├── ReactNativeVideoAutolinking.swift │ │ │ ├── c++/ │ │ │ │ ├── HybridVideoPlayerEventEmitterSpecSwift.cpp │ │ │ │ ├── HybridVideoPlayerEventEmitterSpecSwift.hpp │ │ │ │ ├── HybridVideoPlayerFactorySpecSwift.cpp │ │ │ │ ├── HybridVideoPlayerFactorySpecSwift.hpp │ │ │ │ ├── HybridVideoPlayerSourceFactorySpecSwift.cpp │ │ │ │ ├── HybridVideoPlayerSourceFactorySpecSwift.hpp │ │ │ │ ├── HybridVideoPlayerSourceSpecSwift.cpp │ │ │ │ ├── HybridVideoPlayerSourceSpecSwift.hpp │ │ │ │ ├── HybridVideoPlayerSpecSwift.cpp │ │ │ │ ├── HybridVideoPlayerSpecSwift.hpp │ │ │ │ ├── HybridVideoViewViewManagerFactorySpecSwift.cpp │ │ │ │ ├── HybridVideoViewViewManagerFactorySpecSwift.hpp │ │ │ │ ├── HybridVideoViewViewManagerSpecSwift.cpp │ │ │ │ └── HybridVideoViewViewManagerSpecSwift.hpp │ │ │ └── swift/ │ │ │ ├── BandwidthData.swift │ │ │ ├── BufferConfig.swift │ │ │ ├── CustomVideoMetadata.swift │ │ │ ├── Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__string_____OnGetLicensePayload.swift │ │ │ ├── Func_void.swift │ │ │ ├── Func_void_BandwidthData.swift │ │ │ ├── Func_void_TimedMetadata.swift │ │ │ ├── Func_void_VideoInformation.swift │ │ │ ├── Func_void_VideoPlayerStatus.swift │ │ │ ├── Func_void_bool.swift │ │ │ ├── Func_void_double.swift │ │ │ ├── Func_void_onLoadData.swift │ │ │ ├── Func_void_onLoadStartData.swift │ │ │ ├── Func_void_onPlaybackStateChangeData.swift │ │ │ ├── Func_void_onProgressData.swift │ │ │ ├── Func_void_onVolumeChangeData.swift │ │ │ ├── Func_void_std__exception_ptr.swift │ │ │ ├── Func_void_std__optional_std__variant_nitro__NullType__TextTrack__.swift │ │ │ ├── Func_void_std__shared_ptr_Promise_std__string__.swift │ │ │ ├── Func_void_std__string.swift │ │ │ ├── Func_void_std__vector_std__string_.swift │ │ │ ├── HybridVideoPlayerEventEmitterSpec.swift │ │ │ ├── HybridVideoPlayerEventEmitterSpec_cxx.swift │ │ │ ├── HybridVideoPlayerFactorySpec.swift │ │ │ ├── HybridVideoPlayerFactorySpec_cxx.swift │ │ │ ├── HybridVideoPlayerSourceFactorySpec.swift │ │ │ ├── HybridVideoPlayerSourceFactorySpec_cxx.swift │ │ │ ├── HybridVideoPlayerSourceSpec.swift │ │ │ ├── HybridVideoPlayerSourceSpec_cxx.swift │ │ │ ├── HybridVideoPlayerSpec.swift │ │ │ ├── HybridVideoPlayerSpec_cxx.swift │ │ │ ├── HybridVideoViewViewManagerFactorySpec.swift │ │ │ ├── HybridVideoViewViewManagerFactorySpec_cxx.swift │ │ │ ├── HybridVideoViewViewManagerSpec.swift │ │ │ ├── HybridVideoViewViewManagerSpec_cxx.swift │ │ │ ├── IgnoreSilentSwitchMode.swift │ │ │ ├── ListenerSubscription.swift │ │ │ ├── LivePlaybackParams.swift │ │ │ ├── MixAudioMode.swift │ │ │ ├── NativeDrmParams.swift │ │ │ ├── NativeExternalSubtitle.swift │ │ │ ├── NativeVideoConfig.swift │ │ │ ├── OnGetLicensePayload.swift │ │ │ ├── ResizeMode.swift │ │ │ ├── Resolution.swift │ │ │ ├── SourceType.swift │ │ │ ├── SubtitleType.swift │ │ │ ├── SurfaceType.swift │ │ │ ├── TextTrack.swift │ │ │ ├── TimedMetadata.swift │ │ │ ├── TimedMetadataObject.swift │ │ │ ├── Variant_NullType_TextTrack.swift │ │ │ ├── Variant_NullType__any_HybridVideoPlayerSourceSpec_.swift │ │ │ ├── VideoInformation.swift │ │ │ ├── VideoOrientation.swift │ │ │ ├── VideoPlayerStatus.swift │ │ │ ├── onLoadData.swift │ │ │ ├── onLoadStartData.swift │ │ │ ├── onPlaybackStateChangeData.swift │ │ │ ├── onProgressData.swift │ │ │ └── onVolumeChangeData.swift │ │ └── shared/ │ │ └── c++/ │ │ ├── BandwidthData.hpp │ │ ├── BufferConfig.hpp │ │ ├── CustomVideoMetadata.hpp │ │ ├── HybridVideoPlayerEventEmitterSpec.cpp │ │ ├── HybridVideoPlayerEventEmitterSpec.hpp │ │ ├── HybridVideoPlayerFactorySpec.cpp │ │ ├── HybridVideoPlayerFactorySpec.hpp │ │ ├── HybridVideoPlayerSourceFactorySpec.cpp │ │ ├── HybridVideoPlayerSourceFactorySpec.hpp │ │ ├── HybridVideoPlayerSourceSpec.cpp │ │ ├── HybridVideoPlayerSourceSpec.hpp │ │ ├── HybridVideoPlayerSpec.cpp │ │ ├── HybridVideoPlayerSpec.hpp │ │ ├── HybridVideoViewViewManagerFactorySpec.cpp │ │ ├── HybridVideoViewViewManagerFactorySpec.hpp │ │ ├── HybridVideoViewViewManagerSpec.cpp │ │ ├── HybridVideoViewViewManagerSpec.hpp │ │ ├── IgnoreSilentSwitchMode.hpp │ │ ├── ListenerSubscription.hpp │ │ ├── LivePlaybackParams.hpp │ │ ├── MixAudioMode.hpp │ │ ├── NativeDrmParams.hpp │ │ ├── NativeExternalSubtitle.hpp │ │ ├── NativeVideoConfig.hpp │ │ ├── OnGetLicensePayload.hpp │ │ ├── ResizeMode.hpp │ │ ├── Resolution.hpp │ │ ├── SourceType.hpp │ │ ├── SubtitleType.hpp │ │ ├── SurfaceType.hpp │ │ ├── TextTrack.hpp │ │ ├── TimedMetadata.hpp │ │ ├── TimedMetadataObject.hpp │ │ ├── VideoInformation.hpp │ │ ├── VideoOrientation.hpp │ │ ├── VideoPlayerStatus.hpp │ │ ├── onLoadData.hpp │ │ ├── onLoadStartData.hpp │ │ ├── onPlaybackStateChangeData.hpp │ │ ├── onProgressData.hpp │ │ └── onVolumeChangeData.hpp │ ├── package.json │ ├── react-native.config.js │ ├── src/ │ │ ├── core/ │ │ │ ├── VideoPlayer.ts │ │ │ ├── VideoPlayerEvents.ts │ │ │ ├── hooks/ │ │ │ │ ├── useEvent.ts │ │ │ │ ├── useManagedInstance.ts │ │ │ │ └── useVideoPlayer.ts │ │ │ ├── types/ │ │ │ │ ├── BufferConfig.ts │ │ │ │ ├── DrmParams.ts │ │ │ │ ├── Events.ts │ │ │ │ ├── IgnoreSilentSwitchMode.ts │ │ │ │ ├── MixAudioMode.ts │ │ │ │ ├── ResizeMode.ts │ │ │ │ ├── TextTrack.ts │ │ │ │ ├── Utils.ts │ │ │ │ ├── VideoConfig.ts │ │ │ │ ├── VideoError.ts │ │ │ │ ├── VideoInformation.ts │ │ │ │ ├── VideoOrientation.ts │ │ │ │ ├── VideoPlayerBase.ts │ │ │ │ ├── VideoPlayerSourceBase.ts │ │ │ │ └── VideoPlayerStatus.ts │ │ │ ├── utils/ │ │ │ │ ├── playerFactory.ts │ │ │ │ └── sourceFactory.ts │ │ │ └── video-view/ │ │ │ ├── NativeVideoView.tsx │ │ │ └── VideoView.tsx │ │ ├── expo-plugins/ │ │ │ ├── @types.ts │ │ │ ├── getPackageInfo.ts │ │ │ ├── withAndroidExtensions.ts │ │ │ ├── withAndroidNotificationControls.ts │ │ │ ├── withAndroidPictureInPicture.ts │ │ │ ├── withBackgroundAudio.ts │ │ │ ├── withReactNativeVideo.ts │ │ │ └── writeToPodfile.ts │ │ ├── index.tsx │ │ └── spec/ │ │ ├── fabric/ │ │ │ └── VideoViewNativeComponent.ts │ │ └── nitro/ │ │ ├── VideoPlayer.nitro.ts │ │ ├── VideoPlayerEventEmitter.nitro.ts │ │ ├── VideoPlayerSource.nitro.ts │ │ └── VideoViewViewManager.nitro.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── scripts/ └── release.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/actions/setup-bun/action.yml ================================================ name: setup bun description: Setup bun and install dependencies inputs: working-directory: description: 'working directory for bun install' default: ./ required: false runs: using: composite steps: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.1 - name: Cache dependencies id: bun-cache uses: actions/cache@v4 with: path: | **/node_modules key: ${{ runner.os }}-v7-bun-${{ hashFiles('**/bun.lock') }}-${{ hashFiles('**/package.json') }} restore-keys: | ${{ runner.os }}-v7-bun-${{ hashFiles('**/bun.lock') }} ${{ runner.os }}-v7-bun- - name: Install dependencies working-directory: ${{ inputs.working-directory }} run: bun install shell: bash ================================================ FILE: .github/workflows/deploy-docs.yml ================================================ name: Deploy Documentation on: workflow_dispatch: push: branches: - master paths: # Update on workflow change - ".github/workflows/deploy-docs.yml" # Update on docs change - "docs/**" # Update on code change (Api Reference) - "packages/react-native-video/src/**" jobs: build: name: Build Documentation runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: ./.github/actions/setup-bun - name: Build Documentation run: bun run --cwd docs build - name: Upload Build Artifact uses: actions/upload-pages-artifact@v3 with: path: docs/build deploy: name: Deploy Documentation to GitHub Pages needs: build permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/test-docs-build.yml ================================================ name: Test Documentation Build on: pull_request: branches: - master paths: # Update on workflow change - ".github/workflows/test-docs-build.yml" # Update on docs change - "docs/**" # Update on code change (Api Reference) - "packages/react-native-video/src/**" jobs: test-docs-deploy: name: Test Documentation Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: ./.github/actions/setup-bun - name: Test build website run: bun run --cwd docs build ================================================ FILE: .gitignore ================================================ # OSX # .DS_Store **/.xcode.env.local # XDE .expo/ # VSCode .vscode/ jsconfig.json # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate project.xcworkspace # Android/IJ # .classpath .cxx .gradle .idea .project .settings local.properties android.iml # Cocoapods # example/ios/Pods # Ruby example/vendor/ # node.js # node_modules/ npm-debug.log yarn-debug.log yarn-error.log # Bun package-lock.json **/*.bun # BUCK buck-out/ \.buckd/ android/app/libs android/keystores/debug.keystore # Yarn .yarn/* !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions # Expo .expo/ # Turborepo .turbo/ # generated by bob lib/ # TypeScript tsconfig.tsbuildinfo ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Contributions are always welcome, no matter how large or small! We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md). ## Development workflow This project is a monorepo managed using [Bun workspaces](https://bun.sh/docs/install/workspaces). It contains the following packages: - An Library package in the `packages/react-native-video` directory. - An example app in the `example/` directory. To get started with the project, run `bun install` in the root directory to install the required dependencies for each package: ```sh bun install ``` > Since the project relies on Bun workspaces, you cannot use [`npm`](https://github.com/npm/cli) or [`yarn`](https://yarnpkg.com/) for development. The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make. It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app. If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/VideoExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-video`. To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-video` under `Android`. You can use various commands from the root directory to work with the project. To start the packager: ```sh bun example start ``` To run the example app on Android: ```sh bun example android ``` To run the example app on iOS: ```sh bun example ios ``` Make sure your code passes TypeScript and ESLint. Run the following to verify: ```sh bun typecheck bun lint ``` To fix formatting errors, run the following: ```sh bun lint --fix ``` ### Commit message convention We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: - `fix`: bug fixes, e.g. fix crash due to deprecated method. - `feat`: new features, e.g. add new method to the module. - `refactor`: code refactor, e.g. migrate from class components to hooks. - `docs`: changes into documentation, e.g. add usage example for the module.. - `test`: adding or updating tests, e.g. add integration tests using detox. - `chore`: tooling changes, e.g. change CI config. Our pre-commit hooks verify that your commit message matches this format when committing. ### Linting and tests [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/) We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. Our pre-commit hooks verify that the linter and tests pass when committing. ### Publishing to npm We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc. To publish new versions, run the following: ```sh bun release ``` ### Scripts The `package.json` file contains various scripts for common tasks: - `bun install`: setup project by installing dependencies. - `bun typecheck`: type-check files with TypeScript. - `bun lint`: lint files with ESLint. - `bun example start`: start the Metro server for the example app. - `bun example android`: run the example app on Android. - `bun example ios`: run the example app on iOS. ### Sending a pull request > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). When you're sending a pull request: - Prefer small pull requests focused on one change. - Verify that linters and tests are passing. - Review the documentation to make sure it looks good. - Follow the pull request template when opening a pull request. - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. ### License By contributing to this project, you agree that your contributions will be licensed under the [MIT License](LICENSE). ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2024-2025 TheWidlarzGroup Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![React Native Video Component](./docs/static/baners/rnv-banner.png)](https://thewidlarzgroup.com/?utm_source=rnv&utm_medium=readme&utm_id=banner) The most battle-tested open-source video player component for React Native with support for DRM, offline playback, HLS/DASH streaming, and more. > [!IMPORTANT] > This is a new version (v7) of `react-native-video` that is currently in active development. > You can expect breaking changes and missing features. > > If you have any questions, please contact us at [hi@thewidlarzgroup.com](mailto:hi@thewidlarzgroup.com). ## 🔍 Features | Feature | Status | |---------|--------| | 📱 Plays all video formats natively supported by iOS/Android | ✅ Available | | ▶️ Local and remote playback | ✅ Available | | 🔁 Streaming: HLS • DASH • SmoothStreaming | ✅ Available | | 🧩 Expo plugin support | ✅ Available | | 📴 Offline playback, video download, support for side-tracks and side-captions (via [optional SDK](https://docs.thewidlarzgroup.com/offline-video-sdk?utm_source=rnv&utm_medium=readme&utm_id=features-text)) | ✅ Available | | 📱 Picture in Picture | ✅ Available | | 🎚️ Fine-grained control over tracks, buffering & events | 🏗️ In Development | | 🧠 Advanced control over playback and buffering | ✅ Available | | 🔐 DRM: Widevine & FairPlay ([See free DRM stream example](https://www.thewidlarzgroup.com/services/free-drm-token-generator-for-video?utm_source=rnv&utm_medium=readme&utm_id=free-drm)) | ✅ Available | | 🌐 Basic Web Support | 📝 [TODO](https://github.com/TheWidlarzGroup/react-native-video/issues/4605) | | 📺 TV Support | 📝 [TODO](https://github.com/TheWidlarzGroup/react-native-video/issues/4607) | | 🥽 VisionOS Support | 📝 [TODO](https://github.com/TheWidlarzGroup/react-native-video/issues/4608) | ## ✨ Project Status | Version | State | Architecture | |---------|-------|--------------| | **v5 and lower** | ❌ End-of-life [Commercial Support Available](https://www.thewidlarzgroup.com/blog/react-native-video-upgrade-challenges-custom-maintenance-support#how-we-can-help?utm_source=rnv&utm_medium=readme&utm_id=upgradev5) | Old Architecture | | **v6** | 🛠 Maintained (community + TWG) | Old + New (Interop Layer) | | **v7** | 🚀 Active Development | Old + New (Full Support) | `react-native-video` v7 introduces full support for the new React Native architecture, unlocking better performance, improved consistency, and modern native modules. --- ## 📚 Documentation & Examples - 📖 [Documentation](https://docs.thewidlarzgroup.com/react-native-video/docs/v7/intro) - 📦 [Example: Basic Usage](https://github.com/TheWidlarzGroup/react-native-video/tree/v7/example) - 📦 [Example: Free DRM Stream](https://www.thewidlarzgroup.com/services/free-drm-token-generator-for-video?utm_source=rnv&utm_medium=readme&utm_id=free-drm) ## 🚀 Quick Start ### Requirements - React Native 0.75 or higher - `react-native-nitro-modules` (>=0.31.10) - Please see [nitro requirements](https://nitro.margelo.com/docs/minimum-requirements) ### Install `react-native-video` requires `react-native-nitro-modules` (>=0.31.10) in your project. ```bash npm install react-native-nitro-modules ``` Then install `react-native-video` ```bash # Install the beta version of react-native-video v7 npm install react-native-video@beta # Install pods cd ios && pod install ```
For react-native < 0.80 `react-native` < 0.80 have bug that prevents to properly handle errors by nitro modules on Android. We highly recommend to apply bellow patch for `react-native-nitro-modules` to fix this issue. You can apply it using `patch-package`. Without this patch you won't be able "recognize" errors, all will be thrown as unknown errors. see [installation guide](https://docs.thewidlarzgroup.com/react-native-video/docs/v7/installation#patch-for-react-native--080)
### Usage ```tsx import { useVideoPlayer, VideoView } from 'react-native-video'; export default () => ( const player = useVideoPlayer( 'https://www.w3schools.com/html/mov_bbb.mp4', (_player) => { _player.play(); } ); ); ``` --- ## :inbox_tray: We're building a Pro Player! Offline SDK Preview We see the need for a more feature-rich video player. There is a gap between open source and commercial players, and we want to fill that gap with plugins. **Are you using a commercial player just for 1-2 features?** Maybe you are paying for a license just to get **Caching**, **Ads**, or **Analytics**? Let us know. We want to identify these missing pieces and build them, so you can switch back to open source. **This is what we have already. Check out!** * [Offline Video](https://sdk.thewidlarzgroup.com/offline-video): Logic for downloading streams (HLS/DASH) and standard video files to enable offline playback. * [Background Uploader](https://sdk.thewidlarzgroup.com/background-uploader): Handles uploads even if the app is minimized (not strictly a player plugin, but super useful). * [Chapter Markers](https://sdk.thewidlarzgroup.com/chapters): Visual markers on the timeline to navigate content.

[-> Tell us what to build next ←](https://sdk.thewidlarzgroup.com/ask-for-plugin) or reach out to us sdk@thewidlarzgroup.com
--- ## 💼 TWG Services & Products | Offering | Description | |----------|-------------| | [**Professional Support Packages**](https://www.thewidlarzgroup.com/issue-boost?utm_source=rnv&utm_medium=readme&utm_campaign=professional-support-packages#Contact) | Priority bug-fixes, guaranteed SLAs, [roadmap influence](https://github.com/orgs/TheWidlarzGroup/projects/6) | | [**Issue Booster**](https://www.thewidlarzgroup.com/issue-boost?utm_source=rnv&utm_medium=readme) | Fast-track urgent fixes with a pay‑per‑issue model | | [**Offline Video SDK**](https://www.thewidlarzgroup.com/offline-video-sdk/?utm_source=rnv&utm_medium=readme&utm_campaign=downloading&utm_id=offline-video-sdk-link) | Plug‑and‑play secure download solution for iOS & Android | | [**Integration Support**](https://www.thewidlarzgroup.com/?utm_source=rnv&utm_medium=readme&utm_campaign=integration-support#Contact) | Hands‑on help integrating video, DRM & offline into your app | | [**Free DRM Token Generator**](https://www.thewidlarzgroup.com/services/free-drm-token-generator-for-video?utm_source=rnv&utm_medium=readme&utm_id=free-drm) | Generate Widevine / FairPlay tokens for testing | | [**Ready Boilerplates**](https://www.thewidlarzgroup.com/showcases?utm_source=rnv&utm_medium=readme) | Ready-to-use apps with offline HLS/DASH DRM, video frame scrubbing, TikTok-style video feed, background uploads, Skia-based frame processor (R&D phase), and more | | [**React Native Video Upgrade Guide**](https://www.thewidlarzgroup.com/blog/react-native-video-upgrade-challenges-custom-maintenance-support?utm_source=rnv&utm_medium=readme&utm_id=upgrade-blog&utm_campaign=v7) | Common upgrade pitfalls & how to solve them | *See how [TWG](https://www.thewidlarzgroup.com/?utm_source=rnv&utm_medium=readme&utm_id=services-text) helped **Learnn** ship a world‑class player in record time - [case study](https://gitnation.com/contents/a-4-year-retrospective-lessons-learned-from-building-a-video-player-from-scratch-with-react-native).* Contact us at [hi@thewidlarzgroup.com](mailto:hi@thewidlarzgroup.com) ## 🌍 Social - 🐦 **X / Twitter** - [follow product & release updates](https://x.com/TheWidlarzGroup) - 💬 **Discord** - [talk to the community and us](https://discord.gg/9WPq6Yx) - 💼 **LinkedIn** - [see TWG flexing](https://linkedin.com/company/the-widlarz-group) ## 📰 Community & Media - 🗽 **React Summit US** – How TWG helped Learnn boost video performance on React Native. [Watch the talk »](https://gitnation.com/contents/a-4-year-retrospective-lessons-learned-from-building-a-video-player-from-scratch-with-react-native) - 🧨 **v7 deep dive** – Why we’re building v7 with Nitro Modules [Watch on X »](https://x.com/krzysztof_moch/status/1854162551946478051) - 🛠️ **Well-maintained open-source library** - What does it truly mean? - Bart's talk for React Native Warsaw [Watch here »](https://www.youtube.com/watch?v=RAQQwGCQNqY) - 📺 **“Over the Top” Panel** - Building Streaming Apps for Mobile, Web, and Smart TVs - Bart giving his insights on the industry [Watch here »](https://youtu.be/j2b_bG-32JI) ================================================ FILE: bunfig.toml ================================================ [install] linker = "hoisted" ================================================ FILE: config/.editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true quote_type = single ================================================ FILE: config/.eslintrc.js ================================================ module.exports = { root: true, extends: ['@react-native', 'plugin:prettier/recommended'], ignorePatterns: [ '**/node_modules', '**/lib', '**/build', '**/.eslintrc.js', '**/.prettierrc.js', '**/jest.config.js', '**/babel.config.js', '**/metro.config.js', '**/react-native.config.js', '**/tsconfig.json', ], plugins: ['@typescript-eslint', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { project: true, tsconfigRootDir: __dirname, ecmaFeatures: { jsx: true, }, ecmaVersion: 2020, sourceType: 'module', }, rules: { 'prettier/prettier': [ 'error', { quoteProps: 'consistent', singleQuote: true, tabWidth: 2, trailingComma: 'es5', useTabs: false, }, ], quotes: [ 'error', 'single', { avoidEscape: true, allowTemplateLiterals: true }, ], '@react-native/no-deep-imports': 'off', }, }; ================================================ FILE: config/tsconfig.json ================================================ { "exclude": [ "**/node_modules", "**/lib", "**/.eslintrc.js", "**/.prettierrc.js", "**/jest.config.js", "**/babel.config.js", "**/metro.config.js", "**/react-native.config.js", "**/tsconfig.json" ], "compilerOptions": { "composite": true, "allowUnreachableCode": false, "allowUnusedLabels": false, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "lib": ["ESNext"], "module": "ESNext", "moduleResolution": "bundler", "noEmit": true, "noFallthroughCasesInSwitch": true, "noImplicitReturns": true, "noImplicitUseStrict": false, "noStrictGenericChecks": false, "noUncheckedIndexedAccess": true, "noUnusedLocals": true, "noUnusedParameters": true, "resolveJsonModule": true, "skipLibCheck": true, "strict": true, "target": "ESNext", "verbatimModuleSyntax": true } } ================================================ FILE: docs/.eslintrc.js ================================================ module.exports = { root: true, extends: ["../config/.eslintrc.js"], parserOptions: { tsconfigRootDir: __dirname, project: true, }, plugins: ['@widlarzgroup/docusaurus'], settings: { '@widlarzgroup/docusaurus': { extend: ['src/css/custom.css'], }, }, overrides: [ { files: ['**/*.css'], processor: '@widlarzgroup/docusaurus/.css', }, ], }; ================================================ FILE: docs/.gitignore ================================================ # Dependencies /node_modules # Production /build # Generated files .docusaurus .cache-loader # API Reference generated files docs/api-reference/ # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: docs/README.md ================================================ ### Instalation To install the dependencies, run at the root of the repository: ``` $ bun install ``` ### Development To start a local development server with hot-reloading, run: ``` $ bun run start ``` ### Deployment Deployment is handled via GitHub Actions. Upon pushing to the `master` branch content from the `docs` directory is automatically deployed to GitHub Pages. ### Custom Props Custom props are provided by the `@widlarzgroup/docusaurus-ui` package and allow you to display badges and other UI elements. Custom props can be defined in three places: #### 1. In `_category_.json` (for entire categories) ```json { "label": "Analytics", "position": 7, "customProps": { "badgeType": "new" } } ``` #### 2. In markdown frontmatter (for individual pages) ```md --- sidebar_position: 5 sidebar_label: Chapters customProps: plan: pro --- ``` #### 3. In `sidebars.ts` (for sidebar items) ```ts { type: 'doc', id: 'some-doc', customProps: { badgeType: 'new' } } ``` #### Available Custom Props | Prop | Values | Description | |------|--------|-------------| | `badgeType` | `"new"`, `"preview"` | Displays a "NEW" or "PREVIEW" badge next to the item | | `plan` | `"pro"` | Displays a "PRO" badge indicating premium/commercial feature | ================================================ FILE: docs/docs/fundamentals/_category_.json ================================================ { "label": "Fundamentals", "position": 1, "collapsed": false } ================================================ FILE: docs/docs/fundamentals/configuration/_category_.json ================================================ { "label": "Configuration", "position": 3, "collapsed": false } ================================================ FILE: docs/docs/fundamentals/configuration/with-expo.md ================================================ --- sidebar_position: 1 sidebar_label: With Expo description: Expo plugin for react-native-video configuration --- # Expo Plugin The `react-native-video` library provides an Expo plugin to simplify the integration and configuration of specific features into your Expo project. ## Installation To use the Expo plugin, you need to add it to your app's configuration file (`app.json` or `app.config.js`). ```json title="app.json" { "expo": { "plugins": [ [ "react-native-video", { "enableAndroidPictureInPicture": true, "enableBackgroundAudio": true, "androidExtensions": { "useExoplayerDash": true, "useExoplayerHls": true } } ] ] } } ``` ```javascript title="app.config.js" export default { plugins: [ [ 'react-native-video', { enableAndroidPictureInPicture: true, enableBackgroundAudio: true, androidExtensions: { useExoplayerDash: true, useExoplayerHls: true, }, }, ], ], }; ``` ## Configuration Options The plugin accepts an optional configuration object with the following properties: ### `enableAndroidPictureInPicture` (optional) - **Type:** `boolean` - **Default:** `false` - **Description:** Enables Picture-in-Picture (PiP) mode on Android. This will apply the necessary configurations to your Android project. ### `enableBackgroundAudio` (optional) - **Type:** `boolean` - **Default:** `false` - **Description:** Enables audio playback to continue when the app is in the background on Android. Ensure you have also configured the necessary background modes capabilities in your app if required by the operating system. ### `androidExtensions` (optional) - **Type:** `object` - **Default:** `{ useExoplayerDash: true, useExoplayerHls: true }` - **Description:** Allows you to specify which Android ExoPlayer extensions to include. This can help reduce the size of your app by only including the extensions you need. - `useExoplayerDash` (boolean, default: `true`): Whether to include ExoPlayer's Dash extension. - `useExoplayerHls` (boolean, default: `true`): Whether to include ExoPlayer's HLS extension. ### `reactNativeTestApp` (optional) - **Type:** `boolean` - **Default:** `false` - **Description:** Whether to use `react-native-test-app` compatible mode. ## Usage Once configured in your `app.json` or `app.config.js`, the plugin will automatically apply the necessary native project changes during the prebuild process (e.g., when running `npx expo prebuild`). No further manual setup is typically required for these features. ================================================ FILE: docs/docs/fundamentals/configuration/without-expo.md ================================================ --- sidebar_position: 2 sidebar_label: Without Expo description: Manual configuration of react-native-video --- # Manual Configuration If you prefer not to use the Expo plugin you can configure **react-native-video** manually by editing the native project files directly. The steps below show the exact changes performed by the plugin so you can reproduce them in a plain React Native or bare Expo project. --- ## iOS ### Enable Background Audio To allow video sound to continue when the app goes to the background add the `audio` mode to `Info.plist`: ```xml title="ios/YourApp/Info.plist" UIBackgroundModes audio ``` ## Android ### Configure ExoPlayer extensions By default the library enables DASH & HLS extensions. You can fine-tune this by adding properties to **gradle.properties**: ```properties title="android/gradle.properties" # Enable / disable ExoPlayer extensions used by react-native-video RNVideo_useExoplayerDash=true # DASH playback support RNVideo_useExoplayerHls=true # HLS playback support ``` Set a value to `false` to exclude the corresponding extension and reduce APK size. ### Enable Picture-in-Picture (PiP) Add the `android:supportsPictureInPicture` flag to your *main* activity in **AndroidManifest.xml**: ```xml title="android/app/src/main/AndroidManifest.xml" ``` PiP requires **API 26+** (Android 8.0). Make sure `minSdkVersion` is at least `26` when enabling this feature. ## Verification After the modifications: 1. **iOS** – run `cd ios && pod install` then build the app from Xcode or via `npx react-native run-ios` / `npx expo run:ios`. 2. **Android** – clean & rebuild the project: `./gradlew clean && ./gradlew :app:assembleDebug` or simply run `npx react-native run-android` / `npx expo run:android`. If the build succeeds your manual configuration is complete. --- ### Need an easier way? Use the [Expo plugin](./with-expo.md) to apply exactly the same changes automatically during `expo prebuild`. ================================================ FILE: docs/docs/fundamentals/installation.md ================================================ --- title: Installation sidebar_position: 2 description: React Native Video Installation Guide and Requirements --- # Installation React Native Video is a library that allows you to play various kinds of video in a React Native application. It is built on top of the [`react-native-nitro-modules`](https://nitro.margelo.com/docs/what-is-nitro) framework, giving it type-safety and blazing fast communication across Native and JavaScript threads. React Native Video supports both the New Architecture and the Old Architecture. ## Requirements ### System Requirements - iOS `15.0` or higher - Android `6.0` or higher ### Minimal Package Requirements - `react-native` `0.75.0` or higher - `react-native-nitro-modules` `0.35.0` or higher ## Installation 1. Install dependencies: ```bash npm install react-native-video@next react-native-nitro-modules ``` 2. Configure Library: You can configure the library in two ways: - [With Expo](./configuration/with-expo.md) - [Without Expo](./configuration/without-expo.md) 3. Run the project: If you are using Expo, you will need to generate native files: ```bash npx expo prebuild ``` And then run the project: ```bash npx expo run:ios # run on iOS npx expo run:android # run on Android ``` If you are using React Native CLI, you will need to install Pods for iOS: ```bash cd ios && pod install && cd .. ``` And then run the project: ```bash npx react-native run-ios # run on iOS npx react-native run-android # run on Android ``` ## Patch for react-native < 0.80 Versions of `react-native` < 0.80 have a bug that prevents them from properly handling errors thrown by nitro modules on Android. If working with these versions of `react-native`, we highly recommend you apply the `react-native-nitro-modules` patch below, to fix this issue. You can apply it using `patch-package`. :::warning Without this patch you won't be able "recognize" errors, all will be thrown as unknown errors. :::
Patch for `react-native-nitro-modules` ```diff diff --git a/node_modules/react-native-nitro-modules/cpp/core/HybridFunction.hpp b/node_modules/react-native-nitro-modules/cpp/core/HybridFunction.hpp index efcea05..ffad3f2 100644 --- a/node_modules/react-native-nitro-modules/cpp/core/HybridFunction.hpp +++ b/node_modules/react-native-nitro-modules/cpp/core/HybridFunction.hpp @@ -23,6 +23,10 @@ struct JSIConverter; #include #include +#ifdef ANDROID +#include +#endif + namespace margelo::nitro { using namespace facebook; @@ -109,6 +113,15 @@ public: std::string funcName = getHybridFuncFullName(kind, name, hybridInstance.get()); std::string message = exception.what(); throw jsi::JSError(runtime, funcName + ": " + message); +#ifdef ANDROID +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexceptions" + } catch (const jni::JniException& exception) { + std::string funcName = getHybridFuncFullName(kind, name, hybridInstance.get()); + std::string message = exception.what(); + throw jsi::JSError(runtime, funcName + ": " + message); +#pragma clang diagnostic pop +#endif } catch (...) { // Some unknown exception was thrown - add method name information and re-throw as `JSError`. std::string funcName = getHybridFuncFullName(kind, name, hybridInstance.get()); ``` see [raw](https://github.com/TheWidlarzGroup/react-native-video/blob/v7/example/patches/react-native-nitro-modules%2B0.27.2.patch)
## Usage ```tsx title="App.tsx" import { VideoView, useVideoPlayer } from 'react-native-video'; export default function App() { const player = useVideoPlayer({ source: { uri: 'https://www.w3schools.com/html/mov_bbb.mp4', }, }); return ; } ``` ================================================ FILE: docs/docs/fundamentals/intro.mdx ================================================ --- title: Intro sidebar_position: 1 description: Introduction to React Native Video v7 customProps: badgeType: new --- import { V7Lead, V7FeatureCards, V7ApiModel, V7NitroSection, V7StatusTimeline, V7OpenSource, V7ProPlugins, } from '@site/src/components/Intro'; ## What changed in v7 vs v6 ## New API model in practice ### New model: you control the player, the view is optional ## Why Nitro Modules? ### Nitro Modules as the foundation of v7 ## Status v7 ## Open-source + optional Pro features and services ## Pro plugins ================================================ FILE: docs/docs/offer.mdx ================================================ --- title: Offer description: Introduction to React Native Video library sidebar_class_name: hidden --- import { IntroHero, OfferCards, HowItWorks, Services, Extensions, DecisionTable, Contact, } from "@site/src/components/Offer"; ## What you can use Three ways to work with react-native-video, depending on your needs and timeline: ## How it works ## Services When you need guaranteed response times and direct maintainer involvement: ## Extensions Ready-to-use modules and templates for advanced use cases: ## Which option should I choose? Quick reference guide based on your situation: ## Contact Ready to discuss your project? Here's what to include: ================================================ FILE: docs/docs/player/_category_.json ================================================ { "label": "Player", "position": 2, "collapsed": false } ================================================ FILE: docs/docs/player/analytics/_category_.json ================================================ { "label": "Analytics", "position": 7, "collapsed": false, "customProps": { "badgeType": "new" } } ================================================ FILE: docs/docs/player/analytics/manual.md ================================================ --- sidebar_position: 2 sidebar_label: Manual (Native) description: Manual analytics integration guide using native plugins --- # Manual Analytics Integrate your own analytics system using native plugins. ## Overview Most analytics systems that track player data (e.g., bitrate, errors) can be integrated directly with ExoPlayer or AVPlayer. The [plugin system](../../plugins/plugins.mdx) allows for non-intrusive analytics integration with react-native-video. It should be implemented in native languages (Kotlin/Swift) to ensure efficiency. ## Architecture ``` ┌─────────────────────────────────────┐ │ React Native App │ │ ┌───────────────────────────────┐ │ │ │ react-native-video │ │ │ │ ┌─────────────────────────┐ │ │ │ │ │ Plugin System │ │ │ │ │ └─────────────────────────┘ │ │ │ └───────────────────────────────┘ │ └─────────────────────────────────────┘ │ │ │ onPlayerCreated │ onPlayerCreated ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ Android Plugin │ │ iOS Plugin │ │ (Kotlin) │ │ (Swift) │ │ │ │ │ │ ExoPlayer │ │ AVPlayer │ │ Analytics │ │ KVO Observers │ │ Listener │ │ │ └─────────────────┘ └─────────────────┘ ``` ## Implementation ### Android (Kotlin) ```kotlin import androidx.annotation.OptIn import androidx.media3.common.Format import androidx.media3.common.PlaybackException import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.analytics.AnalyticsListener import com.twg.video.core.plugins.NativeVideoPlayer import com.twg.video.core.plugins.ReactNativeVideoPlugin import java.lang.ref.WeakReference @OptIn(UnstableApi::class) class AnalyticsPlugin : ReactNativeVideoPlugin("MyAnalytics") { override fun onPlayerCreated(player: WeakReference) { val nativePlayer = player.get() ?: return val exoPlayer = nativePlayer.player exoPlayer.addAnalyticsListener(object : AnalyticsListener { override fun onBandwidthEstimate( eventTime: AnalyticsListener.EventTime, totalLoadTimeMs: Int, totalBytesLoaded: Long, bitrateEstimate: Long ) { trackMetric("bitrate", bitrateEstimate) } override fun onDroppedVideoFrames( eventTime: AnalyticsListener.EventTime, droppedFrames: Int, elapsedMs: Long ) { trackMetric("dropped_frames", droppedFrames) } override fun onVideoInputFormatChanged( eventTime: AnalyticsListener.EventTime, format: Format, decoderReuseDecision: Int? ) { trackEvent("quality_change", mapOf( "width" to format.width, "height" to format.height, "bitrate" to format.bitrate )) } override fun onPlaybackStateChanged( eventTime: AnalyticsListener.EventTime, state: Int ) { when (state) { Player.STATE_BUFFERING -> trackEvent("buffering_start") Player.STATE_READY -> trackEvent("buffering_end") Player.STATE_ENDED -> trackEvent("playback_complete") } } override fun onPlayerError( eventTime: AnalyticsListener.EventTime, error: PlaybackException ) { trackEvent("error", mapOf( "code" to error.errorCode, "message" to error.message )) } }) } override fun onPlayerDestroyed(player: WeakReference) { flushAnalytics() } private fun trackEvent(name: String, params: Map = emptyMap()) { // Send to your analytics backend } private fun trackMetric(name: String, value: Number) { // Send to your analytics backend } private fun flushAnalytics() { // Flush pending analytics } } ``` ### iOS (Swift) ```swift import AVFoundation class AnalyticsPlugin: ReactNativeVideoPlugin { // MARK: - Properties private weak var currentPlayer: AVPlayer? private var rateObserver: NSKeyValueObservation? private var statusObserver: NSKeyValueObservation? private var timeObserver: Any? // MARK: - Init init() { super.init(name: "MyAnalytics") } // MARK: - Plugin Lifecycle override func onPlayerCreated(player: Weak) { guard let nativePlayer = player.value else { return } currentPlayer = nativePlayer.player setupPlaybackObservers(for: nativePlayer.player) setupQualityTracking(for: nativePlayer.playerItem) } override func onPlayerDestroyed(player: Weak) { removeAllObservers() flushAnalytics() } // MARK: - Setup private func setupPlaybackObservers(for player: AVPlayer) { rateObserver = player.observe(\.rate) { [weak self] p, _ in self?.trackEvent(p.rate > 0 ? "play" : "pause") } statusObserver = player.observe(\.status) { [weak self] p, _ in if p.status == .readyToPlay { self?.trackEvent("ready") } else if p.status == .failed { self?.trackEvent("error", params: ["message": p.error?.localizedDescription ?? ""]) } } timeObserver = player.addPeriodicTimeObserver( forInterval: CMTime(seconds: 10, preferredTimescale: 1), queue: .main ) { [weak self] time in self?.trackMetric("position", value: time.seconds) } } private func setupQualityTracking(for playerItem: AVPlayerItem?) { NotificationCenter.default.addObserver( self, selector: #selector(handleAccessLog), name: .AVPlayerItemNewAccessLogEntry, object: playerItem ) } @objc private func handleAccessLog(_ notification: Notification) { guard let item = notification.object as? AVPlayerItem, let event = item.accessLog()?.events.last else { return } trackMetric("bitrate", value: event.indicatedBitrate) trackMetric("stalls", value: Double(event.numberOfStalls)) trackMetric("dropped_frames", value: Double(event.numberOfDroppedVideoFrames)) } private func removeAllObservers() { if let observer = timeObserver { currentPlayer?.removeTimeObserver(observer) } rateObserver?.invalidate() statusObserver?.invalidate() NotificationCenter.default.removeObserver(self) currentPlayer = nil timeObserver = nil } // MARK: - Analytics private func trackEvent(_ name: String, params: [String: Any] = [:]) { // Send to your analytics backend } private func trackMetric(_ name: String, value: Double) { // Send to your analytics backend } private func flushAnalytics() { // Flush pending analytics } } ``` ## Available Metrics ### Android (ExoPlayer AnalyticsListener) | Event | Method | Data | |-------|--------|------| | Bitrate | `onBandwidthEstimate` | `bitrateEstimate` | | Dropped frames | `onDroppedVideoFrames` | `droppedFrames`, `elapsedMs` | | Quality change | `onVideoInputFormatChanged` | `format.width`, `format.height` | | Buffering | `onPlaybackStateChanged` | `Player.STATE_BUFFERING` | | Stall | `onStallStart` / `onStallEnd` | duration | | Seek | `onSeekStarted` / `onSeekProcessed` | position | | Error | `onPlayerError` | `errorCode`, `message` | ### iOS (AVPlayer) | Metric | Source | Property | |--------|--------|----------| | Bitrate | `accessLog` | `indicatedBitrate` | | Stalls | `accessLog` | `numberOfStalls` | | Dropped frames | `accessLog` | `numberOfDroppedVideoFrames` | | Bytes | `accessLog` | `numberOfBytesTransferred` | | Playback status | KVO | `player.status` | | Play/Pause | KVO | `player.rate` | | Buffer | `playerItem` | `isPlaybackLikelyToKeepUp` | ## Registration Plugins auto-register when instantiated. Create your plugin early in app lifecycle: **Android** - `MainApplication.kt`: ```kotlin class MainApplication : Application() { override fun onCreate() { super.onCreate() AnalyticsPlugin() // Auto-registers via init block } } ``` **iOS** - `AppDelegate.swift`: ```swift @main class AppDelegate: UIResponder, UIApplicationDelegate { private var analyticsPlugin: AnalyticsPlugin? func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { analyticsPlugin = AnalyticsPlugin() // Auto-registers via init return true } } ``` ## See Also - [Plugins](../../plugins/plugins.mdx) - Full plugin system documentation - [Plugin Interface](../../plugins/interface.md) - Complete API reference ================================================ FILE: docs/docs/player/analytics/simple.md ================================================ --- sidebar_position: 0 sidebar_label: Simple (JS Events) description: Simple analytics using JavaScript events --- # Simple Analytics The easiest way to track video analytics is by using the JavaScript events provided by `react-native-video`. This approach requires no native code and works across all platforms. ## Overview Use the `useEvent` hook to subscribe to player events. For a complete list of available events and their data, see the [Events documentation](../events.md). ## Example ```tsx import { useVideoPlayer, useEvent, VideoView } from 'react-native-video'; function VideoPlayer() { const player = useVideoPlayer('https://example.com/video.mp4'); useEvent(player, 'onLoad', (data) => { analytics.track('video_loaded', { duration: data.duration }); }); useEvent(player, 'onProgress', (data) => { analytics.track('video_progress', { currentTime: data.currentTime }); }); useEvent(player, 'onError', (error) => { analytics.track('video_error', { error: error.error }); }); useEvent(player, 'onEnd', () => { analytics.track('video_completed'); }); return ; } ``` ## When to Use **Use JS Events when:** - You need basic playback tracking (play, pause, progress, errors) - You want a quick integration without native code - Your analytics needs are straightforward **Consider [Manual Analytics](./manual.md) when:** - You need low-level metrics (bitrate, dropped frames, buffer health) - You want to integrate with native analytics SDKs - You need the most accurate and detailed data ## Tips 1. **Throttle progress events** - `onProgress` fires frequently. Consider throttling before sending to your analytics backend. 2. **Track session data** - Store a session ID to group events from the same viewing session. 3. **Include video metadata** - Add video ID, title, or other metadata to your events for better reporting. ```tsx const sessionId = useRef(uuid()).current; const handleProgress = (data) => { // Throttle to every 10 seconds if (Math.floor(data.currentTime) % 10 === 0) { analytics.track('video_progress', { sessionId, videoId: 'video-123', currentTime: data.currentTime, }); } }; ``` ================================================ FILE: docs/docs/player/downloading/_category_.json ================================================ { "label": "Downloading", "position": 6, "link": null, "collapsed": false, "customProps": { "plan": "pro" } } ================================================ FILE: docs/docs/player/downloading/downloading.md ================================================ --- sidebar_position: 2 sidebar_label: Downloading description: Complete API reference for Offline Video SDK --- # Downloading ## Authorization ### `registerPlugin(apiKey: string): Promise` Registers and authorizes the plugin with the provided API key. Must be called before using any other functions. **Parameters:** - `apiKey`: Your API key obtained from [sdk.thewidlarzgroup.com](https://sdk.thewidlarzgroup.com?utm_source=rnv&utm_medium=docs&utm_campaign=downloading&utm_id=downloading_api-key-param) **Returns:** `Promise` - `true` if registration was successful, `false` otherwise ```tsx import { registerPlugin } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const success = await registerPlugin("YOUR_API_KEY"); ``` ### `disablePlugin(): void` Disables the plugin. After calling this, you'll need to call `registerPlugin` again to use the SDK. ```tsx import { disablePlugin } from "@TheWidlarzGroup/react-native-video-stream-downloader"; disablePlugin(); ``` ### `isRegistered(): boolean` Checks if the plugin is currently registered and authorized. **Returns:** `boolean` - `true` if registered, `false` otherwise ```tsx import { isRegistered } from "@TheWidlarzGroup/react-native-video-stream-downloader"; if (isRegistered()) { // Plugin is ready to use } ``` ## Configuration ### `setConfig(config: DownloadConfig): void` Sets global configuration for downloads. **Parameters:** - `config`: Configuration with the following properties: - `updateFrequencyMS?: number` - How often to update download progress (in milliseconds) - `maxParallelDownloads?: number` - Maximum number of simultaneous downloads ```tsx import { setConfig } from "@TheWidlarzGroup/react-native-video-stream-downloader"; setConfig({ updateFrequencyMS: 1000, maxParallelDownloads: 3, }); ``` ### `getConfig(): DownloadConfig` Gets the current global configuration. **Returns:** `DownloadConfig` with properties: - `updateFrequencyMS?: number` - `maxParallelDownloads?: number` ```tsx import { getConfig } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const config = getConfig(); ``` ## Download Management ### `downloadStream(url: string, options?: DownloadOptions): Promise` {#downloadstream} Downloads a video stream for offline playback. **Parameters:** - `url`: The URL of the video stream to download - `options`: Optional download configuration with the following properties: - `includeAllTracks?: boolean` - Whether to download all available tracks (default: `false`) - `tracks?: Track[]` - Array of specific tracks to download. Each track has: - `id: string` - Track identifier - `type: 'video' | 'audio' | 'text'` - Track type - `language?: string` - Language code (for audio/text tracks) - `expiresAt?: number` - Unix timestamp when the download should expire - `drm?: DRMConfig` - DRM configuration (see [DRM Downloading](./drm-downloading.md) for details) - `metadata?: Record` - Custom metadata to store with the download **Returns:** `Promise` - Status immediately after adding to queue (not after completion) **DownloadStatus properties:** - `id: string` - Unique download identifier - `url: string` - Source URL - `status: 'queued' | 'downloading' | 'paused' | 'completed' | 'failed' | 'cancelled'` - Current status - `progress: number` - Download progress (0-1) - `bytesDownloaded: number` - Number of bytes downloaded - `totalBytes: number` - Total bytes to download (may be `undefined` until known) ```tsx import { downloadStream } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const status = await downloadStream("https://example.com/video.m3u8", { tracks: [ { id: "video-1", type: "video" }, { id: "audio-en", type: "audio", language: "en" }, ], }); ``` ### `pauseDownload(id: string): Promise` Pauses an active download. **Parameters:** - `id`: The download identifier returned from `downloadStream` ```tsx import { pauseDownload } from "@TheWidlarzGroup/react-native-video-stream-downloader"; await pauseDownload(downloadId); ``` ### `resumeDownload(id: string): Promise` Resumes a paused download. **Parameters:** - `id`: The download identifier returned from `downloadStream` ```tsx import { resumeDownload } from "@TheWidlarzGroup/react-native-video-stream-downloader"; await resumeDownload(downloadId); ``` ### `cancelDownload(id: string): Promise` Cancels a download and removes it from the queue. Also deletes any partially downloaded files. **Parameters:** - `id`: The download identifier returned from `downloadStream` ```tsx import { cancelDownload } from "@TheWidlarzGroup/react-native-video-stream-downloader"; await cancelDownload(downloadId); ``` ### `cancelAllDownloads(): Promise` Cancels all active and queued downloads. ```tsx import { cancelAllDownloads } from "@TheWidlarzGroup/react-native-video-stream-downloader"; await cancelAllDownloads(); ``` ### `getDownloadStatus(id: string): Promise` Gets the current status of a specific download. **Parameters:** - `id`: The download identifier **Returns:** `Promise` - Current download status or `null` if not found ```tsx import { getDownloadStatus } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const status = await getDownloadStatus(downloadId); ``` ### `getDownloadsStatus(): Promise` Gets the status of all downloads (active, queued, completed, etc.). **Returns:** `Promise` - Array of all download statuses ```tsx import { getDownloadsStatus } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const allStatuses = await getDownloadsStatus(); ``` ## Track Inspection ### `getAvailableTracks(url: string): Promise` {#getavailabletracks} Retrieves information about available audio, video, and subtitle tracks in a stream. **Parameters:** - `url`: The URL of the video stream to inspect **Returns:** `Promise` with the following structure: - `video: VideoTrack[]` - Available video tracks, each with: - `id: string` - Track identifier - `width: number` - Video width in pixels - `height: number` - Video height in pixels - `bitrate: number` - Bitrate in bits per second - `audio: AudioTrack[]` - Available audio tracks, each with: - `id: string` - Track identifier - `language: string` - Language code (e.g., "en", "es") - `bitrate: number` - Bitrate in bits per second - `text: TextTrack[]` - Available subtitle tracks, each with: - `id: string` - Track identifier - `language: string` - Language code - `type: string` - Subtitle format (e.g., "text/vtt") ```tsx import { getAvailableTracks } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const tracks = await getAvailableTracks("https://example.com/video.m3u8"); // Use tracks.video, tracks.audio, tracks.text to let user select // Then pass selected tracks to downloadStream ``` For more details on track selection, see [Track Selection](./track-selection.md). ## Types ### `DownloadStatus` {#downloadstatus} Status information for a download. **Properties:** - `id: string` - Unique download identifier - `url: string` - Source URL - `status: 'queued' | 'downloading' | 'paused' | 'completed' | 'failed' | 'cancelled'` - Current status - `progress: number` - Download progress (0-1) - `bytesDownloaded: number` - Number of bytes downloaded - `totalBytes: number | undefined` - Total bytes to download (may be `undefined` until known) ### `DownloadOptions` Configuration options for `downloadStream`. **Properties:** - `includeAllTracks?: boolean` - Whether to download all available tracks (default: `false`) - `tracks?: Track[]` - Array of specific tracks to download - `expiresAt?: number` - Unix timestamp when the download should expire - `drm?: DRMConfig` - DRM configuration (see [DRM Downloading](./drm-downloading.md)) - `metadata?: Record` - Custom metadata to store with the download ### `Track` A track selection for download. **Properties:** - `id: string` - Track identifier - `type: 'video' | 'audio' | 'text'` - Track type - `language?: string` - Language code (for audio/text tracks) ### `AvailableTracks` Result from `getAvailableTracks` containing available tracks. **Properties:** - `video: VideoTrack[]` - Available video tracks - `audio: AudioTrack[]` - Available audio tracks - `text: TextTrack[]` - Available subtitle tracks ### `VideoTrack` Information about a video track. **Properties:** - `id: string` - Track identifier - `width: number` - Video width in pixels - `height: number` - Video height in pixels - `bitrate: number` - Bitrate in bits per second ### `AudioTrack` Information about an audio track. **Properties:** - `id: string` - Track identifier - `language: string` - Language code (e.g., "en", "es") - `bitrate: number` - Bitrate in bits per second ### `TextTrack` Information about a subtitle track. **Properties:** - `id: string` - Track identifier - `language: string` - Language code - `type: string` - Subtitle format (e.g., "text/vtt") ### `DownloadConfig` Global configuration for downloads. **Properties:** - `updateFrequencyMS?: number` - How often to update download progress (in milliseconds) - `maxParallelDownloads?: number` - Maximum number of simultaneous downloads ### `DRMConfig` DRM configuration for downloading protected content. **Properties:** - `licenseServer: string` - URL of the license server - `certificateUrl?: string` - Certificate URL (required for FairPlay on iOS) - `headers?: Record` - HTTP headers for license requests - `getLicense?: (spcData: ArrayBuffer) => Promise` - Custom license acquisition function (iOS only, FairPlay) ================================================ FILE: docs/docs/player/downloading/drm-downloading.md ================================================ --- sidebar_position: 5 sidebar_label: DRM Downloading description: Downloading and playing DRM-protected content offline --- # DRM Downloading The Offline Video SDK supports downloading and playing DRM-protected content offline, including support for persistent licenses. ## Prerequisites :::warning Critical Requirement Your DRM provider **must support persistent/offline licenses**. Not all DRM providers support this feature. Contact your DRM provider to confirm offline license support before implementing. ::: To download DRM-protected content, you need: - **Encrypted media**: Video content encrypted with DRM (Widevine, FairPlay, PlayReady) - **License server**: A license server that supports **persistent/offline licenses** - **Short-lived token**: Authentication token for license acquisition (if required) ## High-level Flow 1. **Content Preparation**: Media is encrypted and packaged with DRM 2. **License Server Configuration**: License server is configured to issue persistent licenses 3. **Download with DRM Config**: Call `downloadStream` with DRM configuration 4. **License Acquisition**: SDK automatically acquires and stores persistent license 5. **Offline Playback**: Downloaded content can be played offline using the stored license ## Configuration The `drm` property in `downloadStream` accepts a `DRMConfig` with the following properties: - `licenseServer: string` - URL of the license server - `certificateUrl?: string` - Certificate URL (required for FairPlay on iOS) - `headers?: Record` - HTTP headers for license requests - `getLicense?: (spcData: ArrayBuffer) => Promise` - Custom license acquisition function (iOS only, FairPlay) See [DRMConfig](./downloading.md#drmconfig) in API Reference for complete type definition. ### Basic DRM Configuration ```tsx import { downloadStream } from "@TheWidlarzGroup/react-native-video-stream-downloader"; await downloadStream(videoUrl, { drm: { licenseServer: "https://license.example.com/acquire", headers: { "Authorization": "Bearer YOUR_TOKEN", }, }, }); ``` ### FairPlay (iOS) For FairPlay on iOS, you also need to provide the certificate URL: ```tsx await downloadStream(videoUrl, { drm: { licenseServer: "https://license.example.com/acquire", certificateUrl: "https://license.example.com/certificate.cer", headers: { "Authorization": "Bearer YOUR_TOKEN", }, }, }); ``` ### Custom License Acquisition (iOS only) On iOS, you can provide a custom license acquisition function: ```tsx await downloadStream(videoUrl, { drm: { licenseServer: "https://license.example.com/acquire", certificateUrl: "https://license.example.com/certificate.cer", getLicense: async (spcData: ArrayBuffer) => { // Custom license acquisition logic const response = await fetch("https://license.example.com/acquire", { method: "POST", headers: { "Content-Type": "application/octet-stream", "Authorization": "Bearer YOUR_TOKEN", }, body: spcData, }); return await response.arrayBuffer(); }, }, }); ``` ## Platform-Specific Notes ### Android - Supports Widevine and PlayReady - License acquisition is handled automatically by the SDK - Ensure your license server supports persistent licenses ### iOS - Supports FairPlay - Requires `certificateUrl` for FairPlay - Custom license acquisition via `getLicense` is optional but available - FairPlay configuration should match your `react-native-video` DRM setup ## Testing :::tip Always test DRM downloading on a **real device**. Simulators/emulators may not properly handle DRM operations. ::: When testing: 1. Ensure your test content is properly encrypted 2. Verify your license server supports persistent licenses 3. Test on both iOS and Android devices 4. Verify offline playback works after download completes 5. Test license expiration scenarios ## FairPlay Notes The FairPlay configuration for offline downloading should match your `react-native-video` DRM configuration exactly. Use the same: - `licenseServer` URL - `certificateUrl` - `headers` (if applicable) - `getLicense` function (if using custom acquisition) This ensures consistency between online and offline playback. ================================================ FILE: docs/docs/player/downloading/events-downloading.md ================================================ --- sidebar_position: 3 sidebar_label: Events (downloading) description: Event system for tracking download progress and status --- # Downloading Events The Offline Video SDK emits events that allow you to monitor and react to changes in download status and progress. You can subscribe to these events using the `useEvent` hook. ## Available Events ### `onError` Fired when an error occurs during download. **Callback signature:** ```tsx (error: string) => void ``` **Parameters:** - `error`: Error message describing what went wrong ```tsx import { useEvent } from "@TheWidlarzGroup/react-native-video-stream-downloader"; useEvent("onError", (error: string) => { console.error("Download error:", error); }); ``` ### `onDownloadProgress` Fired periodically during downloads with current progress information. **Callback signature:** ```tsx (downloads: DownloadStatus[]) => void ``` **Parameters:** - `downloads`: Array of `DownloadStatus` - See [DownloadStatus structure](./downloading.md#downloadstatus) in API Reference for complete properties ```tsx import { useEvent } from "@TheWidlarzGroup/react-native-video-stream-downloader"; useEvent("onDownloadProgress", (downloads: DownloadStatus[]) => { downloads.forEach((download) => { console.log(`Download ${download.id}: ${(download.progress * 100).toFixed(1)}%`); }); }); ``` ### `onDownloadEnd` Fired when a download completes (successfully or with failure). **Callback signature:** ```tsx (download: DownloadStatus) => void ``` **Parameters:** - `download`: Final `DownloadStatus` - See [DownloadStatus structure](./downloading.md#downloadstatus) in API Reference for complete properties. When this event fires, `status` will be either `'completed'` or `'failed'`. ```tsx import { useEvent } from "@TheWidlarzGroup/react-native-video-stream-downloader"; useEvent("onDownloadEnd", (download: DownloadStatus) => { if (download.status === "completed") { console.log(`Download ${download.id} completed successfully`); } else { console.log(`Download ${download.id} failed`); } }); ``` ## Using Events Events are automatically typed and can be used with the `useEvent` hook: ```tsx import { useEvent } from "@TheWidlarzGroup/react-native-video-stream-downloader"; function DownloadManager() { useEvent("onDownloadProgress", (downloads) => { // Update UI with progress }); useEvent("onDownloadEnd", (download) => { // Handle completion }); useEvent("onError", (error) => { // Handle errors }); // ... rest of component } ``` The `useEvent` hook automatically manages cleanup when the component unmounts, preventing memory leaks. ================================================ FILE: docs/docs/player/downloading/getting-started.md ================================================ --- sidebar_position: 1 sidebar_label: Getting Started description: Installation and setup guide for Offline Video SDK --- # Getting Started This section covers the **Offline Video SDK** - a commercial extension for `react-native-video` that enables downloading and offline playback. The Offline Video SDK is a commercial add-on for `react-native-video` (versions 6 and 7) that enables secure offline playback of HLS/DASH streams or static videos like MP4, including support for DRM, multiple audio tracks, and subtitles. ## Requirements - **React Native Video**: version 6.15.0 or higher (versions 6 and 7 are supported) - **iOS**: 15.0 or higher - **Android**: 6.0 or higher - **Api key**: A valid api key for the Offline Video SDK is required. You can obtain this through the [SDK platform](https://sdk.thewidlarzgroup.com?utm_source=rnv&utm_medium=docs&utm_campaign=downloading&utm_id=getting-started_api-key-requirement). ## Features - **HLS/DASH/MP4 Downloading**: Download and store video streams for offline playback - **Asset Management**: Full control over downloaded assets - **Offline DRM**: Supports offline playback of DRM-protected content with proper rights enforcement and license handling - **Offline Playback**: Play downloaded content without internet connection - **Cross-platform**: Works on both iOS and Android ## Installation ### 1. Configure npm for Private Package Since this is a private package, configure npm to access GitHub Packages by adding the following to your `~/.npmrc` file: ``` @TheWidlarzGroup:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken= ``` Replace `` with your GitHub token. To obtain this token, please [contact us](https://www.thewidlarzgroup.com/?utm_source=rnv&utm_medium=docs&utm_campaign=downloading&utm_id=getting-started_github-token#Contact). :::note This token is different from the API key used for plugin authorization. ::: ### 2. Install the Package Install the package using npm: ```bash npm install @TheWidlarzGroup/react-native-video-stream-downloader ``` ### 3. Android Configuration Add the following line to your `./android/app/build.gradle` file in the `dependencies` block: ```groovy implementation fileTree(dir: "../../node_modules/@TheWidlarzGroup/react-native-video-stream-downloader/native-libs", include: ["*.aar"]) ``` Ensure the path matches your project's `node_modules` location. ### 4. iOS Configuration No additional configuration is required for iOS; the plugin will be automatically linked. ### 5. Plugin Authorization After installation, authorize the plugin with an API key: ```tsx import { registerPlugin } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const success = await registerPlugin("YOUR_API_KEY"); ``` Replace `"YOUR_API_KEY"` with your actual API key obtained from [sdk.thewidlarzgroup.com](https://sdk.thewidlarzgroup.com?utm_source=rnv&utm_medium=docs&utm_campaign=downloading&utm_id=getting-started_api-key-link). ## Supported Formats | Format | iOS | Android | |--------|-----|---------| | HLS | ✅ | ✅ | | MP4 | ✅ | ✅ | | MPEG-DASH | ❌ | ✅ | ## Licensing & Pricing The Offline Video SDK is distributed under a commercial license. You can evaluate it for free for 14 days without a credit card. For questions or assistance, contact [hi@thewidlarzgroup.com](mailto:hi@thewidlarzgroup.com). ================================================ FILE: docs/docs/player/downloading/track-selection.md ================================================ --- sidebar_position: 4 sidebar_label: Track Selection description: How to select specific tracks for download --- # Track Selection Track selection allows you to download only the specific audio, video, and subtitle tracks you need, optimizing storage usage and download time. ## Why Select Tracks? - **Storage Optimization**: Download only the tracks you need (e.g., specific language, resolution) - **Language Support**: Choose audio tracks in different languages - **Subtitle Selection**: Download only the subtitle languages your users need - **Bitrate Control**: Select video tracks with appropriate bitrates for your use case ## How It Works The track selection process follows a 2-step flow: 1. **Inspect Available Tracks**: Use `getAvailableTracks(url)` to retrieve all available tracks 2. **Select Tracks for Download**: Pass the selected track IDs to `downloadStream` in the `tracks` property ### Step 1: Get Available Tracks Use `getAvailableTracks` to retrieve all available tracks. See [getAvailableTracks](./downloading.md#getavailabletracks) in API Reference for complete details. ```tsx import { getAvailableTracks } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const tracks = await getAvailableTracks("https://example.com/video.m3u8"); // tracks.video, tracks.audio, tracks.text contain available tracks ``` ### Step 2: Download Selected Tracks ```tsx import { downloadStream } from "@TheWidlarzGroup/react-native-video-stream-downloader"; // Select specific tracks const selectedTracks = [ tracks.video[0].id, // First video track (usually highest quality) tracks.audio.find(t => t.language === "en")?.id, // English audio tracks.text.find(t => t.language === "en")?.id, // English subtitles ].filter(Boolean); // Remove undefined values await downloadStream("https://example.com/video.m3u8", { tracks: selectedTracks.map(id => ({ id, type: "video" })), // You need to specify type }); ``` ## Example: Multi-Language Selection ```tsx import { getAvailableTracks, downloadStream } from "@TheWidlarzGroup/react-native-video-stream-downloader"; const tracks = await getAvailableTracks(videoUrl); // Select video track (highest quality) const videoTrack = tracks.video[0]; // Select multiple audio tracks (English and Spanish) const audioTracks = tracks.audio.filter(t => t.language === "en" || t.language === "es" ); // Select subtitles for both languages const subtitleTracks = tracks.text.filter(t => t.language === "en" || t.language === "es" ); // Build track selection array const selectedTracks = [ { id: videoTrack.id, type: "video" as const }, ...audioTracks.map(t => ({ id: t.id, type: "audio" as const })), ...subtitleTracks.map(t => ({ id: t.id, type: "text" as const })), ]; await downloadStream(videoUrl, { tracks: selectedTracks, }); ``` ## Default Behavior If you don't specify `tracks` in `downloadStream`, or if `includeAllTracks` is `false`, only the default tracks are downloaded (typically the first video track and the first audio track). To download all available tracks, set `includeAllTracks: true`: ```tsx await downloadStream(videoUrl, { includeAllTracks: true, }); ``` ================================================ FILE: docs/docs/player/drm.md ================================================ --- sidebar_position: 4 sidebar_label: DRM --- # DRM ## What is DRM (Digital Rights Management)? DRM is a set of access control technologies that are used to protect copyrighted content from unauthorized use and distribution. It allows content owners to control how their digital media is used and distributed. ### When do you need it? If you are working with copyrighted content and want to prevent unauthorized access or distribution, you will need DRM. It is especially important for streaming services, e-learning platforms, and any application that delivers premium content that you want to protect from piracy. ### What next? This page explains how to play DRM‑protected content with React Native Video using the official DRM plugin. It covers installing and enabling the plugin, configuring sources with DRM, and platform‑specific notes for Android (Widevine) and iOS/visionOS (FairPlay). ## Install and enable the DRM plugin :::tip Pluginable Architecture React Native Video uses a plugin architecture. DRM support is provided by the `@react-native-video/drm` plugin and is not built into the core package. ::: 1) Install dependencies in your app: ```sh npm install @react-native-video/drm ``` 2) Enable the plugin at app startup (before creating any players): ```ts // App.tsx (or any place you want to initialize the plugin) import { enable } from '@react-native-video/drm'; enable(); ``` The plugin autolinks on both Android and iOS. Nitro Modules are required because the plugin uses Nitro under the hood. ## Quick start You pass DRM configuration via `VideoConfig.drm` when creating a player or using the `useVideoPlayer` hook. If `drm.type` is omitted, the default is inferred per platform (`widevine` on Android, `fairplay` on iOS/visionOS). ```tsx import { VideoView, useVideoPlayer } from 'react-native-video'; export function Player() { const player = useVideoPlayer({ source: { uri: 'https://example.com/manifest.mpd', // or HLS .m3u8 for FairPlay // On iOS these headers are also used for the default license request headers: { Authorization: 'Bearer ' }, drm: { // type: 'widevine' | 'fairplay' // optional; inferred by platform licenseUrl: 'https://license.example.com/widevine', }, }, }); return ; } ``` :::warning You shouldn't include your authorization token directly in the code. Instead, use a backend method to retrieve it at runtime. ::: ## DRM config reference All properties are optional unless marked otherwise for a platform. The table below describes each property, the expected type, platforms where it applies, whether it's required, and important notes. | Property | Type | Platform | Required | Notes | |---|---:|---|:---:|---| | `type` | `'widevine' \| 'fairplay'` | Android, iOS, visionOS | No (defaulted) | Default inferred per platform when `drm` is present and `type` omitted (Android → `widevine`, iOS/visionOS → `fairplay`). | | `licenseUrl` | `string` | Android, iOS, visionOS | Android: Yes; iOS/visionOS: Yes for default/custom flows | URL of the license (CKC) service. Required for license acquisition. | | `licenseHeaders` | ``Record`` | Android | No | Extra headers sent with the Widevine license request. (On iOS, use `source.headers` for license requests.) | | `multiSession` | `boolean` | Android | No | Whether to allow multiple Widevine sessions. | | `certificateUrl` | `string` | iOS, visionOS | Yes (for FairPlay) | URL to fetch the FairPlay application certificate (used to create the SPC). | | `contentId` | `string` | iOS, visionOS | No | If omitted, derived from the `skd://` key URL. Used when creating the SPC. | | `getLicense` | ``(payload) => Promise`` | iOS, visionOS | No | Optional hook for custom FairPlay license logic; must resolve to a base64‑encoded CKC string. | Payload shape passed to `getLicense` (iOS/visionOS): | Field | Type | Description | |---|---:|---| | `contentId` | `string` | Content identifier for the asset. If not provided the player will try to derive it from the `skd://` key URL. | | `licenseUrl` | `string` | The license server URL that should be used for license acquisition. | | `keyUrl` | `string` | The key URL/identifier received from the stream (typically an `skd://` URL). | | `spc` | `string` | The SPC (secure playback context) as a base64‑encoded string. Send raw SPC bytes to your license server (server side may expect raw bytes rather than base64). | ## Android: Widevine - Set `drm.type` to `'widevine'` or omit it (the library will default to Widevine on Android if `drm` is present). - `licenseUrl` is required; `licenseHeaders` and `multiSession` are optional. - Implementation details: - The plugin uses ExoPlayer’s `DefaultDrmSessionManager` and `HttpMediaDrmCallback` with your `licenseUrl` and `licenseHeaders`. - If a first attempt fails due to device security level issues, the plugin retries with `L3` security level. Example: ```tsx useVideoPlayer({ source: { uri: 'https://example.com/manifest.mpd', drm: { // type: 'widevine', // optional licenseUrl: 'https://license.example.com/widevine', licenseHeaders: { 'X-Custom-Header': 'value' }, multiSession: false, }, }, }); ``` ## iOS and visionOS: FairPlay Two ways to get the CKC (license): 1) Default flow (no `getLicense`): - Required: `certificateUrl`, `licenseUrl`. - The plugin requests the application certificate, generates the SPC, then POSTs the SPC to `licenseUrl`. - It uses `source.headers` (not `drm.licenseHeaders`) for the license request. 2) Custom flow (provide `getLicense`): - Required: `certificateUrl`, `licenseUrl`, and a `getLicense` implementation. - You receive `{ contentId, licenseUrl, keyUrl, spc }` and must return a base64‑encoded CKC string. Notes: - DRM isn’t supported in the iOS Simulator; the plugin returns `null` for DRM in Simulator builds. - If `contentId` isn’t provided, it is derived from the `skd://` key URL. Default flow example: ```tsx useVideoPlayer({ source: { uri: 'https://example.com/fairplay.m3u8', headers: { Authorization: 'Bearer ' }, // used for the license request drm: { // type: 'fairplay', // optional certificateUrl: 'https://license.example.com/fps-cert', licenseUrl: 'https://license.example.com/fps', // contentId: 'my-content-id', // optional }, }, }); ``` Custom `getLicense` example: :::tip This is example code for a custom `getLicense` implementation. it may differ from your actual implementation provided by your DRM provider ::: ```tsx useVideoPlayer({ source: { uri: 'https://example.com/fairplay.m3u8', drm: { certificateUrl: 'https://license.example.com/fps-cert', licenseUrl: 'https://license.example.com/fps', getLicense: async ({ contentId, licenseUrl, keyUrl, spc }) => { // Example: POST SPC to your license server and return base64 CKC const res = await fetch(licenseUrl, { method: 'POST', body: Buffer.from(spc, 'base64'), // server expects raw SPC bytes headers: { 'Content-Type': 'application/octet-stream', 'X-Content-ID': contentId, 'X-Asset-Id': keyUrl, }, }); if (!res.ok) throw new Error(`License request failed: ${res.status}`); const ckc = await res.arrayBuffer(); // return base64 CKC string return Buffer.from(ckc).toString('base64'); }, }, }, }); ``` ## Offline If you are looking for implementing offline playback with DRM, make sure to checkout our [Offline Video SDK](https://www.thewidlarzgroup.com/offline-video-sdk?utm_source=rnv&utm_medium=docs&utm_campaign=drm&utm_id=offline-sdk-link). It provides a comprehensive solution for downloading and playing Streams and DRM-protected content. ## Troubleshooting - DRMPluginNotFound: Ensure you installed `@react-native-video/drm`, imported it, and called `enable()` before creating any players. - iOS headers: The default FairPlay flow uses `source.headers` for license requests; `drm.licenseHeaders` are not used on iOS. - Invalid CKC: `getLicense` must return a base64 string. Returning raw bytes or JSON will fail. - 403/415 from license server: Verify required auth headers, content type, and whether the server expects raw SPC bytes vs base64. - Android security level issues: The plugin retries with Widevine L3 if the first attempt fails. - iOS Simulator: DRM isn’t supported in Simulator. Test on a real device. ## Notes and defaults - If `drm` is provided without `type`, the library sets a platform default: Android → Widevine, iOS/visionOS → FairPlay. - For custom DRM systems or advanced pipelines, you can implement your own plugin. See the Plugin Interface docs. ================================================ FILE: docs/docs/player/events.md ================================================ --- sidebar_label: Events sidebar_position: 5 --- # Handling Player Events The `VideoPlayer` emits a variety of events that allow you to monitor and react to changes in its state and playback. ## Using the `useEvent` Hook For React functional components, the `useEvent` hook provides a convenient way to subscribe to player events and automatically manage cleanup. ```typescript import { useVideoPlayer, useEvent } from 'react-native-video'; import { useEffect } from 'react'; const MyVideoComponent = () => { const player = useVideoPlayer('https://example.com/video.mp4', (_player) => { _player.play(); }); useEvent(player, 'onLoad', (data) => { console.log('Video loaded via useEvent! Duration:', data.duration); }); useEvent(player, 'onProgress', (data) => { console.log('Progress via useEvent:', data.currentTime); }); // For onError, which is a direct property on VideoPlayer, not from VideoPlayerEvents useEvent(player, 'onError', (error) => { console.error('Player Error via useEvent:', error.code, error.message); }); return ; }; ``` ## Available Events The `VideoPlayer` class, through `VideoPlayerEvents`, supports the following events. You can subscribe to these by assigning a callback function to the corresponding property on the `VideoPlayer` instance. | Event | Callback Signature | Description | |----------------------------|--------------------------------------------------------|---------------------------------------------------------------------------------------------| | `onAudioBecomingNoisy` | () => void | Fired when audio is about to become noisy (e.g., headphones unplugged). | | `onAudioFocusChange` | (hasAudioFocus: boolean) => void | Fired when the audio focus changes (e.g., another app starts playing audio). | | `onBandwidthUpdate` | (data: [BandwidthData](../api-reference/interfaces/BandwidthData.md)) => void | Fired with an estimate of the available bandwidth. | | `onBuffer` | (buffering: boolean) => void | Fired when the player starts or stops buffering data. | | `onControlsVisibleChange` | (visible: boolean) => void | Fired when the visibility of native controls changes. | | `onEnd` | () => void | Fired when the video playback reaches the end. | | `onExternalPlaybackChange` | (externalPlaybackActive: boolean) => void | Fired when the external playback status changes (e.g., AirPlay). | | `onLoad` | (data: [onLoadData](../api-reference/interfaces/onLoadData.md)) => void | Fired when the video has loaded and is ready to play. | | `onLoadStart` | (data: [onLoadStartData](../api-reference/interfaces/onLoadStartData.md)) => void | Fired when the video starts loading. | | `onPlaybackRateChange` | (rate: number) => void | Fired when the playback rate changes. | | `onPlaybackStateChange` | (data: [onPlaybackStateChangeData](../api-reference/interfaces/onPlaybackStateChangeData.md)) => void | Fired when the playback state changes (e.g., playing, paused, stopped). | | `onProgress` | (data: [onProgressData](../api-reference/interfaces/onProgressData.md)) => void | Fired periodically during playback with the current time. | | `onReadyToDisplay` | () => void | Fired when the player is ready to display the first frame of the video. | | `onSeek` | (seekTime: number) => void | Fired when a seek operation has completed. | | `onStatusChange` | (status: [VideoPlayerStatus](../api-reference/type-aliases/VideoPlayerStatus.md)) => void | Fired when the player status changes (detailed status updates). | | `onTextTrackDataChanged` | (texts: string[]) => void | Fired when text track data (e.g., subtitles) changes. | | `onTimedMetadata` | (metadata: [TimedMetadata](../api-reference/interfaces/TimedMetadata.md)) => void | Fired when timed metadata is encountered in the video stream. | | `onTrackChange` | (track: [TextTrack](../api-reference/interfaces/TextTrack.md) \| null) => void | Fired when the selected text track changes. | | `onVolumeChange` | (data: [onVolumeChangeData](../api-reference/interfaces/onVolumeChangeData.md)) => void | Fired when the volume changes. | Additionally, the `VideoPlayer` instance itself has an `onError` property: - `onError: (error: ` [VideoRuntimeError](../api-reference/interfaces/VideoRuntimeError.md) `) => void` - Fired when an error occurs. The callback receives the `VideoRuntimeError` object. **Benefits of `useEvent`**: - **Automatic Cleanup**: The event listener is automatically removed when the component unmounts or when the `player`, `event`, or `callback` dependencies change, preventing memory leaks. - **Type Safety**: Provides better type inference for event callback parameters. This hook is recommended for managing event subscriptions in a declarative React style. ### Initialization Timing and Events `onLoadStart` / `onLoad` will fire automatically after construction when `initializeOnCreation` (default `true`) is enabled. If you set `initializeOnCreation: false`, these events will not fire until you call `initialize()` or `preload()`. Attach your event handlers before invoking those methods to avoid missing early events. ## Subscribing to Events You can subscribe to an event by assigning a function to the player instance's corresponding property: ```typescript import { VideoPlayer } from 'react-native-video'; const player = new VideoPlayer('https://example.com/video.mp4'); player.addEventListener('onLoad', (data) => { console.log('Video loaded! Duration:', data.duration); }); player.addEventListener('onProgress', (data) => { console.log('Current time:', data.currentTime); }); player.addEventListener('onError', (error) => { console.error('Player Error:', error.code, error.message); }); player.play(); ``` ## Clearing Events - The `player.clearEvent(eventName)` method can be used to clear a specific native event handler. - When a player instance is no longer needed and `player.release()` is called, all event listeners are automatically cleared ================================================ FILE: docs/docs/player/player.md ================================================ --- sidebar_position: 1 sidebar_label: Player --- # Player The `VideoPlayer` class is the primary way to control video playback. It provides methods and properties to manage the video source, playback state, volume, and other aspects of the video. ## Initialization To use the `VideoPlayer`, you first need to create an instance of it with a video source. There are two ways to do this. By default the native media item is initialized asynchronously right after creation (unless you opt out with `initializeOnCreation: false`). ### Using `useVideoPlayer` hook ```tsx import { useVideoPlayer } from 'react-native-video'; const player = useVideoPlayer({ source: { uri: 'https://www.w3schools.com/html/mov_bbb.mp4', }, }); ``` :::info `useVideoPlayer` hook is recommended for most use cases. It automatically manages the player lifecycle between the component mount and unmount. ::: For detailed information about using the hook, see [useVideoPlayer](./use-video-player.md). ### Using `VideoPlayer` class constructor directly ```typescript import { VideoPlayer } from 'react-native-video'; // Using a URL string const player = new VideoPlayer('https://example.com/video.mp4'); // Using a VideoSource object const playerWithSource = new VideoPlayer({ uri: 'https://example.com/video.mp4' }); // Using a VideoConfig object const playerWithConfig = new VideoPlayer({ source: { uri: 'https://example.com/video.mp4' }, // other configurations }); ``` :::warning When using `VideoPlayer` class directly, you need to manually manage the player lifecycle. Once you no longer need the player, you need to call `release()` method to release the player's native resources. See [Player Lifecycle](./video-player.md#player-lifecycle) for more details. ::: For detailed information about using the class, see [VideoPlayer](./video-player.md). ================================================ FILE: docs/docs/player/use-video-player.md ================================================ --- sidebar_position: 2 sidebar_label: useVideoPlayer --- # useVideoPlayer The `useVideoPlayer` hook is the recommended way to create and manage a `VideoPlayer` instance in React components. It automatically handles the player lifecycle, ensuring resources are properly released when the component unmounts. ## Quick Start The simplest way to use the hook is to pass a video source: ```tsx import { useVideoPlayer } from 'react-native-video'; const player = useVideoPlayer('https://www.w3schools.com/html/mov_bbb.mp4'); ``` You can also pass a configuration object for more control: ```tsx const player = useVideoPlayer({ source: { uri: 'https://www.w3schools.com/html/mov_bbb.mp4', }, }); ``` ## Why useVideoPlayer? The `useVideoPlayer` hook provides several advantages over creating a `VideoPlayer` instance directly: - **Automatic lifecycle management**: The player is automatically released when the component unmounts - **React-friendly**: Works seamlessly with React's component lifecycle :::info For most React components, `useVideoPlayer` is the recommended approach. If you need more control over the player lifecycle, see [VideoPlayer class](./video-player.md) for direct instantiation. ::: ## Using the Player Instance The `useVideoPlayer` hook returns a `VideoPlayer` instance that you can use to control playback: ```tsx import { useVideoPlayer } from 'react-native-video'; const player = useVideoPlayer('https://example.com/video.mp4'); // Control playback player.play(); player.pause(); player.muted = true; ``` ### Playback Control | Method | Description | |--------|-------------| | `play()` | Starts or resumes video playback. | | `pause()` | Pauses video playback. | | `seekBy(time: number)` | Seeks the video forward or backward by the specified number of seconds. | | `seekTo(time: number)` | Seeks the video to a specific time in seconds. | | `replaceSourceAsync(source: VideoSource \| VideoConfig \| null)` | Replaces the current video source with a new one. Pass `null` to release the current source without replacing it. | | `initialize()` | Manually initialize the underlying native player item when `initializeOnCreation` was set to `false`. No-op if already initialized. | | `preload()` | Ensures the media source is set and prepared (buffering started) without starting playback. If not yet initialized it will initialize first. | | `release()` | Releases the player's native resources. The player is no longer usable after calling this method. **Note:** If you intend to reuse the player instance with a different source, use `replaceSourceAsync(null)` to clear resources instead of `release()`. | ### Properties | Property | Access | Type | Description | |----------|--------|------|-------------| | `source` | Read-only | `VideoPlayerSource` | Gets the current `VideoPlayerSource` object. | | `status` | Read-only | `VideoPlayerStatus` | Gets the current status (e.g., `playing`, `paused`, `buffering`). | | `duration` | Read-only | `number` | Gets the total duration of the video in seconds. Returns `NaN` until metadata is loaded. | | `volume` | Read/Write | `number` | Gets or sets the player volume (0.0 to 1.0). | | `currentTime` | Read/Write | `number` | Gets or sets the current playback time in seconds. | | `muted` | Read/Write | `boolean` | Gets or sets whether the video is muted. | | `loop` | Read/Write | `boolean` | Gets or sets whether the video should loop. | | `rate` | Read/Write | `number` | Gets or sets the playback rate (e.g., 1.0 for normal speed, 0.5 for half speed, 2.0 for double speed). | | `mixAudioMode` | Read/Write | `MixAudioMode` | Controls how this player's audio mixes with other audio sources (see [MixAudioMode](../api-reference/type-aliases/MixAudioMode.md)). | | `ignoreSilentSwitchMode` | Read/Write | `IgnoreSilentSwitchMode` | iOS-only. Determines how audio should behave when the hardware mute (silent) switch is on. | | `playInBackground` | Read/Write | `boolean` | Whether playback should continue when the app goes to the background. | | `playWhenInactive` | Read/Write | `boolean` | Whether playback should continue when the app is inactive (e.g., during a phone call). | | `isPlaying` | Read-only | `boolean` | Returns `true` if the video is currently playing. | | `selectedTrack` | Read-only | `TextTrack \| undefined` | Currently selected text track, or `undefined` when no track is selected. | ### Error Handling | Property | Type | Description | |----------|------|-------------| | `onError?` | `(error: VideoRuntimeError) => void` | A callback function that is invoked when a runtime error occurs in the player. You can use this to catch and handle errors gracefully. | ### Buffer Config You can fine‑tune buffering via `bufferConfig` on the `VideoConfig` you pass to `useVideoPlayer`/`VideoPlayer`. This controls how much data is buffered, live latency targets, and iOS network constraints. Example ```ts const player = useVideoPlayer({ source: { uri: 'https://example.com/stream.m3u8', bufferConfig: { // Android minBufferMs: 5000, maxBufferMs: 10000, // iOS preferredForwardBufferDurationMs: 3000, // Live (cross‑platform target) livePlayback: { targetOffsetMs: 500 }, }, }, }); ``` #### Android Properties below are Android‑only | Property | Type | Description | |----------|------|-------------| | `minBufferMs` | `number` | Minimum media duration the player attempts to keep buffered (ms). Default: 5000. | | `maxBufferMs` | `number` | Maximum media duration the player attempts to buffer (ms). Default: 10000. | | `bufferForPlaybackMs` | `number` | Media that must be buffered before playback can start or resume after user action (ms). Default: 1000. | | `bufferForPlaybackAfterRebufferMs` | `number` | Media that must be buffered to resume after a rebuffer (ms). Default: 2000. | | `backBufferDurationMs` | `number` | Duration kept behind the current position to allow instant rewind without rebuffer (ms). | | `livePlayback.minPlaybackSpeed` | `number` | Minimum playback speed used to maintain target live offset. | | `livePlayback.maxPlaybackSpeed` | `number` | Maximum playback speed used to catch up to target live offset. | | `livePlayback.minOffsetMs` | `number` | Minimum allowed live offset (ms). | | `livePlayback.maxOffsetMs` | `number` | Maximum allowed live offset (ms). | | `livePlayback.targetOffsetMs` | `number` | Target live offset the player tries to maintain (ms). | #### iOS, visionOS, tvOS Properties below are Apple platforms‑only | Property | Type | Description | |----------|------|-------------| | `preferredForwardBufferDurationMs` | `number` | Preferred duration the player attempts to retain ahead of the playhead (ms). | | `preferredPeakBitRate` | `number` | Desired limit of network bandwidth for loading the current item (bits per second). | | `preferredMaximumResolution` | `{ width: number; height: number }` | Preferred maximum video resolution. | | `preferredPeakBitRateForExpensiveNetworks` | `number` | Bandwidth limit for expensive networks (e.g., cellular), in bits per second. | | `preferredMaximumResolutionForExpensiveNetworks` | `{ width: number; height: number }` | Preferred maximum resolution on expensive networks. | | `livePlayback.targetOffsetMs` | `number` | Target live offset (ms) the player will try to maintain. | ## DRM Protected content is supported via a plugin. See the full DRM guide: [DRM](./drm.md). Quick notes: - Install and enable the official plugin `@react-native-video/drm` and call `enable()` at app startup before creating players. - Pass DRM configuration on the source using the `drm` property of `VideoConfig` (see the DRM guide for platform specifics and `getLicense` examples). - If you defer initialization (`initializeOnCreation: false`), be sure to call `await player.initialize()` (or `preload()`) before expecting DRM license acquisition events. ================================================ FILE: docs/docs/player/video-player.md ================================================ --- sidebar_position: 3 sidebar_label: VideoPlayer --- # VideoPlayer :::tip When to Use What - **[`useVideoPlayer`](./use-video-player.md) hook** - Recommended for most cases. Automatically manages player lifecycle (creation, cleanup on unmount, source changes). - **`VideoPlayer` class** - Use when you need manual lifecycle control, e.g., preloading videos before displaying them, managing multiple players with custom logic, or deferred initialization scenarios. ::: The `VideoPlayer` class is the primary way to control video playback. It provides methods and properties to manage the video source, playback state, volume, and other aspects of the video. ## Initialization To use the `VideoPlayer`, you first need to create an instance of it with a video source. By default the native media item is initialized asynchronously right after creation (unless you opt out with `initializeOnCreation: false`). ```typescript import { VideoPlayer } from 'react-native-video'; // Using a URL string const player = new VideoPlayer('https://example.com/video.mp4'); // Using a VideoSource object const playerWithSource = new VideoPlayer({ uri: 'https://example.com/video.mp4' }); // Using a VideoConfig object const playerWithConfig = new VideoPlayer({ source: { uri: 'https://example.com/video.mp4' }, // other configurations }); ``` :::warning When using `VideoPlayer` class directly, you need to manually manage the player lifecycle. Once you no longer need the player, you need to call `release()` method to release the player's native resources. See the [Player Lifecycle](#player-lifecycle) section below for more details. ::: ## Core Functionality The `VideoPlayer` class offers a comprehensive set of methods and properties to control video playback: ### Playback Control | Method | Description | |--------|-------------| | `play()` | Starts or resumes video playback. | | `pause()` | Pauses video playback. | | `seekBy(time: number)` | Seeks the video forward or backward by the specified number of seconds. | | `seekTo(time: number)` | Seeks the video to a specific time in seconds. | | `replaceSourceAsync(source: VideoSource \| VideoConfig \| null)` | Replaces the current video source with a new one. Pass `null` to release the current source without replacing it. | | `initialize()` | Manually initialize the underlying native player item when `initializeOnCreation` was set to `false`. No-op if already initialized. | | `preload()` | Ensures the media source is set and prepared (buffering started) without starting playback. If not yet initialized it will initialize first. | | `release()` | Releases the player's native resources. The player is no longer usable after calling this method. **Note:** If you intend to reuse the player instance with a different source, use `replaceSourceAsync(null)` to clear resources instead of `release()`. | ### Properties | Property | Access | Type | Description | |----------|--------|------|-------------| | `source` | Read-only | `VideoPlayerSource` | Gets the current `VideoPlayerSource` object. | | `status` | Read-only | `VideoPlayerStatus` | Gets the current status (e.g., `playing`, `paused`, `buffering`). | | `duration` | Read-only | `number` | Gets the total duration of the video in seconds. Returns `NaN` until metadata is loaded. | | `volume` | Read/Write | `number` | Gets or sets the player volume (0.0 to 1.0). | | `currentTime` | Read/Write | `number` | Gets or sets the current playback time in seconds. | | `muted` | Read/Write | `boolean` | Gets or sets whether the video is muted. | | `loop` | Read/Write | `boolean` | Gets or sets whether the video should loop. | | `rate` | Read/Write | `number` | Gets or sets the playback rate (e.g., 1.0 for normal speed, 0.5 for half speed, 2.0 for double speed). | | `mixAudioMode` | Read/Write | `MixAudioMode` | Controls how this player's audio mixes with other audio sources (see [MixAudioMode](../api-reference/type-aliases/MixAudioMode.md)). | | `ignoreSilentSwitchMode` | Read/Write | `IgnoreSilentSwitchMode` | iOS-only. Determines how audio should behave when the hardware mute (silent) switch is on. | | `playInBackground` | Read/Write | `boolean` | Whether playback should continue when the app goes to the background. | | `playWhenInactive` | Read/Write | `boolean` | Whether playback should continue when the app is inactive (e.g., during a phone call). | | `isPlaying` | Read-only | `boolean` | Returns `true` if the video is currently playing. | | `selectedTrack` | Read-only | `TextTrack \| undefined` | Currently selected text track, or `undefined` when no track is selected. | ### Error Handling | Property | Type | Description | |----------|------|-------------| | `onError?` | `(error: VideoRuntimeError) => void` | A callback function that is invoked when a runtime error occurs in the player. You can use this to catch and handle errors gracefully. | ### Buffer Config You can fine‑tune buffering via `bufferConfig` on the `VideoConfig` you pass to `useVideoPlayer`/`VideoPlayer`. This controls how much data is buffered, live latency targets, and iOS network constraints. Example ```ts const player = new VideoPlayer({ source: { uri: 'https://example.com/stream.m3u8', bufferConfig: { // Android minBufferMs: 5000, maxBufferMs: 10000, // iOS preferredForwardBufferDurationMs: 3000, // Live (cross‑platform target) livePlayback: { targetOffsetMs: 500 }, }, }, }); ``` #### Android Properties below are Android‑only | Property | Type | Description | |----------|------|-------------| | `minBufferMs` | `number` | Minimum media duration the player attempts to keep buffered (ms). Default: 5000. | | `maxBufferMs` | `number` | Maximum media duration the player attempts to buffer (ms). Default: 10000. | | `bufferForPlaybackMs` | `number` | Media that must be buffered before playback can start or resume after user action (ms). Default: 1000. | | `bufferForPlaybackAfterRebufferMs` | `number` | Media that must be buffered to resume after a rebuffer (ms). Default: 2000. | | `backBufferDurationMs` | `number` | Duration kept behind the current position to allow instant rewind without rebuffer (ms). | | `livePlayback.minPlaybackSpeed` | `number` | Minimum playback speed used to maintain target live offset. | | `livePlayback.maxPlaybackSpeed` | `number` | Maximum playback speed used to catch up to target live offset. | | `livePlayback.minOffsetMs` | `number` | Minimum allowed live offset (ms). | | `livePlayback.maxOffsetMs` | `number` | Maximum allowed live offset (ms). | | `livePlayback.targetOffsetMs` | `number` | Target live offset the player tries to maintain (ms). | #### iOS, visionOS, tvOS Properties below are Apple platforms‑only | Property | Type | Description | |----------|------|-------------| | `preferredForwardBufferDurationMs` | `number` | Preferred duration the player attempts to retain ahead of the playhead (ms). | | `preferredPeakBitRate` | `number` | Desired limit of network bandwidth for loading the current item (bits per second). | | `preferredMaximumResolution` | `{ width: number; height: number }` | Preferred maximum video resolution. | | `preferredPeakBitRateForExpensiveNetworks` | `number` | Bandwidth limit for expensive networks (e.g., cellular), in bits per second. | | `preferredMaximumResolutionForExpensiveNetworks` | `{ width: number; height: number }` | Preferred maximum resolution on expensive networks. | | `livePlayback.targetOffsetMs` | `number` | Target live offset (ms) the player will try to maintain. | ## DRM Protected content is supported via a plugin. See the full DRM guide: [DRM](./drm.md). Quick notes: - Install and enable the official plugin `@react-native-video/drm` and call `enable()` at app startup before creating players. - Pass DRM configuration on the source using the `drm` property of `VideoConfig` (see the DRM guide for platform specifics and `getLicense` examples). - If you defer initialization (`initializeOnCreation: false`), be sure to call `await player.initialize()` (or `preload()`) before expecting DRM license acquisition events. ## Player Lifecycle Understanding the lifecycle of the `VideoPlayer` is crucial for managing resources effectively and ensuring a smooth user experience. ### Creation and Initialization 1. **Instantiation**: A `VideoPlayer` instance is created by calling its constructor with a video source (URL, `VideoSource`, or `VideoConfig`). ```typescript const player = new VideoPlayer('https://example.com/video.mp4'); ``` 2. **Native Player Allocation**: A lightweight native player object is allocated immediately. 3. **Asset Initialization**: By default (unless you opt out) the underlying media item is prepared **asynchronously right after creation**. You can control this with `initializeOnCreation` inside `VideoConfig`. #### Deferred Initialization (Advanced) If you pass a `VideoConfig` with `{ initializeOnCreation: false }`, the player will skip preparing the media item automatically. This is useful when: - You need to batch‑create many players without incurring immediate decoding / network cost - You want to attach event handlers before any network requests happen - You want explicit control over when buffering begins (e.g. on user interaction) To initialize later, call: ```ts await player.initialize(); // or preload if you also want it prepared & ready await player.preload(); ``` #### Initialization Methods Comparison | Method | When to use | What it does | |--------|-------------|--------------| | `initialize()` | You deferred initialization and now want to create the native player item / media source | Creates & attaches the underlying player item / media source without starting playback | | `preload()` | You want the player item prepared (buffering kicked off) ahead of an upcoming `play()` call | Ensures the media source is set and prepared; resolves once preparation started (may already be initialized) | | Implicit (default) | `initializeOnCreation` not set or `true` | Automatically schedules initialization after JS construction | :::info By default, the player initializes automatically after construction. If you need to defer initialization, set `initializeOnCreation: false` in the config. You can then call `player.initialize()` or `player.preload()` later to start the player. ::: ### Playing a Video 1. **Loading**: When the player (auto) initializes, `preload()` is called, or after `replaceSourceAsync()`, the player starts loading the video metadata and buffering content. - `onLoadStart`: Fired when the video starts loading. - `onLoad`: Fired when the video metadata is loaded and the player is ready to play (duration, dimensions, etc., are available). - `onBuffer`: Fired when buffering starts or ends. 2. **Playback**: Once enough data is buffered, playback begins. - `onPlaybackStateChange`: Fired when the playback state changes (e.g., from `buffering` to `playing`). - `onProgress`: Fired periodically with the current playback time. - `onReadyToDisplay`: Fired when the first frame is ready to be displayed. ### Controlling Playback - `pause()`: Pauses playback. `status` changes to `paused`. - `seekTo(time)`, `seekBy(time)`: Changes the current playback position. `onSeek` is fired when the seek operation completes. - `set volume(value)`, `set muted(value)`, `set loop(value)`, `set rate(value)`: Modify player properties. Corresponding events like `onVolumeChange` or `onPlaybackRateChange` might be fired. ### Changing Source - `replaceSourceAsync(newSource)`: This method allows you to change the video source dynamically. 1. The current native player resources associated with the old source are released (similar to `release()` but specifically for the source). 2. A new native player instance (or reconfigured existing one) is prepared for the `newSource`. 3. The loading lifecycle events (`onLoadStart`, `onLoad`, etc.) will fire for the new source. - `replaceSourceAsync(null)`: This effectively unloads the current video and releases its associated resources without loading a new one. This is useful for freeing up memory if the player is temporarily not needed but might be used again later. ### Releasing Resources There are two main ways to release resources: 1. **`replaceSourceAsync(null)`**: This is a less destructive way to free resources related *only* to the current video source. - The `VideoPlayer` instance itself remains usable. - You can later call `replaceSourceAsync(newSource)` to load and play a new video. 2. **`release()`**: This is a destructive operation. :::danger After calling `release()`, the player instance becomes unusable. Any subsequent calls to its methods or property access will result in errors. ::: :::tip It is recommended to use `replaceSourceAsync(null)` when you want to free resources related to the current video source. You should call `release()` only when you are 100% sure that you don't need the player instance anymore. Anyway garbage collector will release the player instance when it is no longer needed. ::: ### Error Handling - The `onError` callback, if provided, will be called when a `VideoRuntimeError` occurs. This allows you to handle issues like network errors, invalid source, or platform-specific playback problems. - If `onError` is not provided, errors might be thrown as exceptions. ### Using with Hooks (`useVideoPlayer`) The `useVideoPlayer` hook simplifies managing the `VideoPlayer` lifecycle within React components. ```typescript import { useVideoPlayer } from 'react-native-video'; const MyComponent = () => { const player = useVideoPlayer('https://example.com/video.mp4', (playerInstance) => { // Optional setup function: configure the player instance after creation playerInstance.loop = true; }); // ... use player ... return ; }; ``` - **Automatic Creation**: `useVideoPlayer` creates a `VideoPlayer` instance when the component mounts or when the source dependency changes. - **Automatic Cleanup**: It automatically cleanup resources when the component unmounts or before recreating the player due to a source change. This prevents resource leaks. - **Dependency Management**: If the `source` prop passed to `useVideoPlayer` changes, the hook will clean up the old player instance and create a new one with the new source. :::tip Using `useVideoPlayer` is the recommended way to manage `VideoPlayer` instances in functional components to ensure proper lifecycle management and resource cleanup. It will also respect `initializeOnCreation` (defaults to `true`). If you need deferred initialization with the hook: ```tsx const player = useVideoPlayer({ source: { uri: 'https://example.com/video.mp4' }, initializeOnCreation: false, }, (instance) => { // Attach listeners first instance.onLoad = () => console.log('Loaded'); }); // Later (e.g. on user tap) await player.initialize(); // or player.preload() player.play(); ``` ::: ================================================ FILE: docs/docs/plugins/_category_.json ================================================ { "label": "Plugins", "position": 4, "collapsed": false } ================================================ FILE: docs/docs/plugins/ask-for-plugin.md ================================================ --- title: Ask for Plugin description: Request a custom plugin for your React Native Video application sidebar_position: 7 --- # Ask for Plugin Need a custom plugin for your React Native Video application? We can build it for you. ## What We Build Our team specializes in creating production-ready plugins for `react-native-video`. Whatever video functionality you need - we can build it. **Examples of plugins we can build:** - **Analytics integrations** - Connect with any analytics provider (Mux, Conviva, custom solutions) - **Live streaming features** - Real-time chat overlays, live reactions, viewer counts - **Interactive video** - Clickable hotspots, branching narratives, shoppable videos - **Accessibility enhancements** - Audio descriptions, sign language overlays, custom captions styling - **AI-powered features** - Auto-generated thumbnails, content moderation, speech-to-text - **Social features** - Watch parties, synchronized playback, clip sharing - **Custom UI components** - Branded controls, gesture handlers, thumbnail previews on seek ## Why Choose Us? - **Deep expertise** - We maintain `react-native-video` and know the codebase inside out - **Production-ready** - Battle-tested code with proper error handling and edge cases covered - **Native performance** - Plugins implemented in Kotlin/Swift for optimal performance - **Ongoing support** - We maintain and update plugins as the library evolves ## Our Existing Plugins We've already built several production-ready plugins: | Plugin | Description | |--------|-------------| | [Offline Video SDK](https://www.thewidlarzgroup.com/offline-video-sdk?utm_source=rnv&utm_medium=docs&utm_campaign=ask-for-plugin&utm_id=offline-video-sdk) | Download and play HLS, MPEG-DASH, MP4 streams offline with DRM support | | [Background Uploader](https://sdk.thewidlarzgroup.com/background-uploader?utm_source=rnv&utm_medium=docs&utm_campaign=ask-for-plugin&utm_id=background-uploader) | Upload files in the background with retry support and progress tracking | | [Chapters Plugin](https://sdk.thewidlarzgroup.com/chapters?utm_source=rnv&utm_medium=docs&utm_campaign=ask-for-plugin&utm_id=chapters-plugin) | Chapter markers and navigation on the seekbar | ## Get Started Ready to discuss your plugin needs? ➡️ [**Request a Plugin**](https://sdk.thewidlarzgroup.com/ask-for-plugin?utm_source=rnv&utm_medium=docs&utm_campaign=ask-for-plugin&utm_id=request-plugin-button) We'll review your requirements and get back to you with a proposal. ================================================ FILE: docs/docs/plugins/examples.md ================================================ --- title: Plugin Usage Examples description: Simple examples of implementing common plugin scenarios for React Native Video sidebar_position: 3 --- # Plugin Usage Examples This document provides practical examples of implementing common plugin scenarios for React Native Video. ## Basic Plugin Template ```kotlin title="Android" class MyPlugin : ReactNativeVideoPlugin("MyPlugin") { override fun onPlayerCreated(player: WeakReference) { Log.d("MyPlugin", "Player created with uri ${player.get()?.source.uri}") } override fun onPlayerDestroyed(player: WeakReference) { Log.d("MyPlugin", "Player destroyed") } override fun overrideSource(source: NativeVideoPlayerSource): NativeVideoPlayerSource { Log.d("MyPlugin", "Overriding source with uri ${source.uri}") return source } } // Usage val plugin = MyPlugin() // Automatically registered ``` ```swift title="iOS" class MyPlugin: ReactNativeVideoPlugin { init() { super.init(name: "MyPlugin") } override func onPlayerCreated(player: Weak) { // Custom logic when player is created } override func onPlayerDestroyed(player: Weak) { // Custom cleanup when player is destroyed } override func overrideSource(source: NativeVideoPlayerSource) async -> NativeVideoPlayerSource { // Modify source if needed return source } } // Usage let plugin = MyPlugin() // Automatically registered ``` ## DRM Plugin Implement custom DRM handling for protected content. :::warning DRM plugins are not supported yet in React Native Video. `getDRMManager` is not implemented yet and will have no effect. ::: ```kotlin title="Android" class CustomDRMPlugin : ReactNativeVideoPlugin("CustomDRM") { override fun getDRMManager(source: NativeVideoPlayerSource): Any? { if (source.isDRMProtected() && source.drmType == "custom") { return CustomDRMManager( licenseUrl = source.drmLicenseUrl, certificateUrl = source.drmCertificateUrl, keyId = source.drmKeyId ) } return null } } ``` ```swift title="iOS" class CustomDRMPlugin: ReactNativeVideoPlugin { init() { super.init(name: "CustomDRM") } override func getDRMManager(source: NativeVideoPlayerSource) async -> Any? { guard source.isDRMProtected() && source.drmType == "custom" else { return nil } return CustomDRMManager( licenseUrl: source.drmLicenseUrl, certificateUrl: source.drmCertificateUrl, keyId: source.drmKeyId ) } } ``` ================================================ FILE: docs/docs/plugins/interface.md ================================================ --- title: Plugin Interface description: Reference for the React Native Video plugin interface sidebar_position: 2 --- # Plugin Interface Reference This document provides a complete reference for the `ReactNativeVideoPluginSpec` interface and the base `ReactNativeVideoPlugin` implementation. ## ReactNativeVideoPluginSpec Interface ### Required Properties #### id: String Unique identifier for the plugin. Must be unique across all registered plugins. ```kotlin // Android override val id: String = "my_unique_plugin_id" ``` ```swift // iOS var id: String { "my_unique_plugin_id" } ``` #### name: String Human-readable name for the plugin, used in debug logging. ```kotlin // Android override val name: String = "My Custom Plugin" ``` ```swift // iOS var name: String { "My Custom Plugin" } ``` ## Lifecycle Methods ### Player Lifecycle #### onPlayerCreated Called when a new player instance is created. ```kotlin // Android @UnstableApi fun onPlayerCreated(player: WeakReference) ``` ```swift // iOS func onPlayerCreated(player: Weak) ``` **Parameters:** - `player`: Weak reference to the newly created player instance **Use Cases:** - Initialize player-specific resources - Set up player event listeners - Configure player settings #### onPlayerDestroyed Called when a player instance is being destroyed. ```kotlin // Android @UnstableApi fun onPlayerDestroyed(player: WeakReference) ``` ```swift // iOS func onPlayerDestroyed(player: Weak) ``` **Parameters:** - `player`: Weak reference to the player instance being destroyed **Use Cases:** - Clean up player-specific resources - Remove event listeners - Save state or analytics data ### Video View Lifecycle #### onVideoViewCreated Called when a new video view is created. ```kotlin // Android @UnstableApi fun onVideoViewCreated(view: WeakReference) ``` ```swift // iOS func onVideoViewCreated(view: Weak) ``` **Parameters:** - `view`: Weak reference to the newly created video view **Use Cases:** - Configure view-specific settings - Set up UI event handlers - Initialize view overlays #### onVideoViewDestroyed Called when a video view is being destroyed. ```kotlin // Android @UnstableApi fun onVideoViewDestroyed(view: WeakReference) ``` ```swift // iOS func onVideoViewDestroyed(view: Weak) ``` **Parameters:** - `view`: Weak reference to the video view being destroyed **Use Cases:** - Clean up view-specific resources - Remove UI event handlers - Save view state ## Content Modification Methods ### overrideSource Modify the video source before it's processed by the player. ```kotlin // Android fun overrideSource(source: NativeVideoPlayerSource): NativeVideoPlayerSource ``` ```swift // iOS func overrideSource(source: NativeVideoPlayerSource) async -> NativeVideoPlayerSource ``` **Parameters:** - `source`: The original video source **Returns:** - Modified video source (can be the same instance if no changes needed) **Use Cases:** - Add authentication headers - Modify URLs (e.g., CDN switching) - Add tracking parameters - Transform source format ### getDRMManager Provide a custom DRM manager for protected content. ```kotlin // Android fun getDRMManager(source: NativeVideoPlayerSource): Any? ``` ```swift // iOS func getDRMManager(source: NativeVideoPlayerSource) async -> Any? ``` **Parameters:** - `source`: The video source that may require DRM **Returns:** - DRM manager instance, or `null` if this plugin doesn't handle DRM for this source **Use Cases:** - Widevine DRM implementation - FairPlay DRM implementation - Custom DRM solutions - License acquisition logic ## Android-Specific Methods ### getMediaDataSourceFactory Override the data source factory used by ExoPlayer. ```kotlin fun getMediaDataSourceFactory( source: NativeVideoPlayerSource, mediaDataSourceFactory: DataSource.Factory ): DataSource.Factory? ``` **Parameters:** - `source`: The video source - `mediaDataSourceFactory`: The default data source factory **Returns:** - Custom data source factory, or `null` to use the default **Use Cases:** - Custom caching strategies - Network optimization - Custom authentication - Analytics data collection ### getMediaSourceFactory Override the media source factory used by ExoPlayer. ```kotlin fun getMediaSourceFactory( source: NativeVideoPlayerSource, mediaSourceFactory: MediaSource.Factory, mediaDataSourceFactory: DataSource.Factory ): MediaSource.Factory? ``` **Parameters:** - `source`: The video source - `mediaSourceFactory`: The default media source factory - `mediaDataSourceFactory`: The data source factory **Returns:** - Custom media source factory, or `null` to use the default **Use Cases:** - Custom media format support - Advanced ExoPlayer configuration - Source-specific optimizations ### getMediaItemBuilder Override the media item builder used by ExoPlayer. ```kotlin fun getMediaItemBuilder( source: NativeVideoPlayerSource, mediaItemBuilder: MediaItem.Builder ): MediaItem.Builder? ``` **Parameters:** - `source`: The video source - `mediaItemBuilder`: The default media item builder **Returns:** - Modified media item builder, or `null` to use the default **Use Cases:** - Add custom metadata - Configure subtitles - Set playback preferences - Configure DRM settings ### shouldDisableCache Control whether caching should be disabled for a source. ```kotlin fun shouldDisableCache(source: NativeVideoPlayerSource): Boolean ``` **Parameters:** - `source`: The video source **Returns:** - `true` to disable caching, `false` to allow caching **Use Cases:** - Disable caching for live streams - Disable caching for DRM content - Custom caching policies ## Base Implementation: ReactNativeVideoPlugin The base class provides default implementations for all methods: ### Automatic Registration ```kotlin // Android init { PluginsRegistry.shared.register(this) } ``` ```swift // iOS public init(name: String) { self.name = name self.id = "RNV_Plugin_\(name)" PluginsRegistry.shared.register(plugin: self) } ``` ### Automatic Cleanup (iOS only) ```swift deinit { PluginsRegistry.shared.unregister(plugin: self) } ``` ### Default Implementations All methods have sensible defaults: - Lifecycle methods: No-op implementations - `overrideSource`: Returns the original source unchanged - `getDRMManager`: Returns `null` - Factory methods (Android): Return `null` (use defaults) - `shouldDisableCache`: Returns `false` ## Method Calling Order ### Source Processing Flow 1. `overrideSource` - Called for each registered plugin in order 2. `getDRMManager` - Called for each plugin until one returns non-null 3. Factory methods (Android) - Called for each plugin until one returns non-null ### Lifecycle Flow 1. View/Player creation methods called for all plugins 2. Source processing happens during playback 3. View/Player destruction methods called for all plugins ## Error Handling ### DRM Plugin Not Found If no plugin provides a DRM manager when required: ```kotlin // Android throw LibraryError.DRMPluginNotFound ``` ```swift // iOS throw LibraryError.DRMPluginNotFound.error() ``` ### Best Practices - Return `null` from optional methods when not providing custom behavior - Handle weak reference nullability properly - Use appropriate error handling in async methods (iOS) - Log meaningful debug information ## Platform Differences Summary | Feature | Android | iOS | |---------|---------|-----| | Async Support | No | Yes (async/await) | | Media Factories | Full ExoPlayer support | Limited AVFoundation | | Cache Control | Yes | No | | Auto Cleanup | Manual | Automatic (deinit) | | Weak References | `WeakReference` | `Weak` | ================================================ FILE: docs/docs/plugins/plugins.mdx ================================================ --- title: Plugins description: Overview of the React Native Video plugin system sidebar_position: 6 --- # Plugins Overview The **React Native Video** library offers a robust plugin system to extend and customize video functionality on Android and iOS. With plugins, you can override source handling, implement custom DRM, modify media factories, and respond to player lifecycle events. :::tip Use in Third Party Library Guide If you are looking how to import React Native Video in your native code to use plugins, you can checkout [Use in Third Party Library](./use-in-third-party-library) page. ::: ## What Are Plugins? Plugins are modular extensions that hook into the video player's lifecycle and processing pipeline. They allow you to: - **Customize video sources** before playback - **Implement custom DRM** for protected content - **Override media factories** (Android only) - **React to player lifecycle events** for analytics, logging, or cleanup - **Control caching behavior** for performance ## Architecture The plugin system consists of three main components: | Component | Description | |:-----------------------------|:-------------------------------------------------------------------------------------------| | `PluginsRegistry` | Singleton that manages plugin registration and coordinates plugin interactions | | `ReactNativeVideoPluginSpec` | Interface/protocol defining the contract all plugins must implement | | `ReactNativeVideoPlugin` | Base implementation with convenient defaults; override only the methods you care about | :::tip Plugins are automatically registered when you instantiate a class that extends `ReactNativeVideoPlugin`. ::: ## Core Concepts ### Plugin Lifecycle Plugins receive notifications for key events: - **Player creation/destruction** - react to player instance lifecycle - **Video view creation/destruction** - handle UI component lifecycle - **Source processing** - modify video sources before playback - **DRM requests** - provide custom DRM handling - **Media factory overrides** - customize ExoPlayer components (Android) :::caution All player and view references passed to plugins are **weak references** to prevent memory leaks. ::: ## Platform Differences | Feature | Android (Kotlin / ExoPlayer) | iOS (Swift / AVFoundation) | |:-------------------------------|:-----------------------------------------------|:-----------------------------------| | Underlying player | ExoPlayer | AVFoundation | | Media factory customization | Extensive | Limited | | Cache control | Supported | Limited | | Plugin method style | Synchronous | Async/await | | Memory management | Manual cleanup | Manual cleanup | ## Getting Started ### Example: Android Plugin ```kotlin // Android class MyPlugin : ReactNativeVideoPlugin("MyPluginName") { override fun onPlayerCreated(player: WeakReference) { // Custom logic here } } ``` ### Example: iOS Plugin ```swift // iOS class MyPlugin: ReactNativeVideoPlugin { init() { super.init(name: "MyPluginName") } override func onPlayerCreated(player: Weak) { // Custom logic here } } ``` ## Next Steps - [**Plugin Registry**](./registry) - Learn about plugin management and registration - [**Plugin Interface**](./interface) - Complete API reference for plugin methods - [**Usage Examples**](./examples) - Practical implementations and common patterns - [**Ask for Plugin**](./ask-for-plugin) - Request a custom plugin built by our team ================================================ FILE: docs/docs/plugins/registry.md ================================================ --- title: Plugin Registry description: Overview of the React Native Video plugin registry and how to use it sidebar_position: 1 --- # Plugin Registry The `PluginsRegistry` is a singleton that manages all plugin instances and coordinates their interactions with the video player system. It handles registration, unregistration, and notification distribution to all active plugins. ## Singleton Pattern Both Android and iOS use a shared singleton: ```kotlin title="Android" // Register PluginsRegistry.shared.register(plugin) // Unregister PluginsRegistry.shared.unregister(plugin) ``` ```swift title="iOS" // Register PluginsRegistry.shared.register(plugin: plugin) // Unregister PluginsRegistry.shared.unregister(plugin: plugin) ``` :::tip Plugin Ordering Plugins are processed in registration order. Later plugins can override earlier ones. ::: ## Registration Methods ### Automatic Registration You can use the base class `ReactNativeVideoPlugin` to automatically register your plugin. This will also mock all the methods that are not implemented in your plugin. :::danger You still need to unregister your plugin manually when you are done with it. Otherwise, you will have a memory leak. ::: ```kotlin title="Android" class MyPlugin : ReactNativeVideoPlugin("MyPlugin") { // ... } val plugin = MyPlugin() // Auto-registered ``` ```swift title="iOS" class MyPlugin: ReactNativeVideoPlugin { init() { super.init(name: "MyPlugin") } // Auto-unregistered in deinit } let plugin = MyPlugin() // Auto-registered ``` ### Manual Registration You can also manually register your plugin. This is useful if you want to implement a plugin that is not a subclass of `ReactNativeVideoPlugin`. You will need to implement the `ReactNativeVideoPluginSpec` interface. This is a protocol that defines the methods and properties that a plugin must implement. ```kotlin title="Android" class MyCustomPlugin : ReactNativeVideoPluginSpec { override val id = "my_custom_id" override val name = "MyCustomPlugin" // ... } val plugin = MyCustomPlugin() PluginsRegistry.shared.register(plugin) ``` ```swift title="iOS" class MyCustomPlugin: ReactNativeVideoPluginSpec { let id = "my_custom_id" let name = "MyCustomPlugin" // ... } let plugin = MyCustomPlugin() PluginsRegistry.shared.register(plugin: plugin) ``` ## Plugin ID Generation When using the base class, IDs are auto-generated: ```kotlin title="Android" ID Format: "RNV_Plugin_{name}" Example: "RNV_Plugin_MyCustomDRM" ``` ```swift title="iOS" ID Format: "RNV_Plugin_{name}" Example: "RNV_Plugin_MyCustomDRM" ``` ## Plugin Internals Bellow are the internals of the plugin registry, that shows logic for certain methods. ### Source Processing The registry coordinates source modifications: ```kotlin title="Android" internal fun overrideSource(source: NativeVideoPlayerSource): NativeVideoPlayerSource { var overriddenSource = source for (plugin in plugins.values) { overriddenSource = plugin.overrideSource(overriddenSource) } return overriddenSource } ``` ```swift title="iOS" internal func overrideSource(source: NativeVideoPlayerSource) async -> NativeVideoPlayerSource { var overriddenSource = source for plugin in plugins.values { overriddenSource = await plugin.overrideSource(source: source) } return overriddenSource } ``` ### DRM Manager Resolution Finds the first plugin that can provide a DRM manager: ```kotlin title="Android" internal fun getDRMManager(source: NativeVideoPlayerSource): Any { for (plugin in plugins.values) { val manager = plugin.getDRMManager(source) if (manager != null) return manager } throw LibraryError.DRMPluginNotFound } ``` ```swift title="iOS" internal func getDrmManager(source: NativeVideoPlayerSource) async throws -> Any? { for plugin in plugins.values { if let drmManager = await plugin.getDRMManager(source: source) { return drmManager } } throw LibraryError.DRMPluginNotFound.error() } ``` ## Android-Specific Registry Methods | Method Name | Purpose | |:----------------------------------|:----------------------------------------------------------| | `overrideMediaDataSourceFactory` | Override data source factory for custom ExoPlayer sources | | `overrideMediaSourceFactory` | Override media source factory | | `overrideMediaItemBuilder` | Customize the media item builder | | `shouldDisableCache` | Control caching behavior | Example signatures: ```kotlin internal fun overrideMediaDataSourceFactory( source: NativeVideoPlayerSource, mediaDataSourceFactory: DataSource.Factory ): DataSource.Factory internal fun overrideMediaSourceFactory( source: NativeVideoPlayerSource, mediaSourceFactory: MediaSource.Factory, mediaDataSourceFactory: DataSource.Factory ): MediaSource.Factory internal fun overrideMediaItemBuilder( source: NativeVideoPlayerSource, mediaItemBuilder: MediaItem.Builder ): MediaItem.Builder internal fun shouldDisableCache(source: NativeVideoPlayerSource): Boolean ``` ## Best Practices - **Memory management**: Registry holds strong references to plugins; plugins get weak references to players/views. - **Unregister plugins**: Use unregistration to prevent memory leaks. - **Performance**: Minimize work in notification handlers. Cache expensive operations. Be mindful of plugin order. :::tip Need a Custom Plugin? If you need functionality that the library doesn't currently offer, we can build it for you. Check out [Ask for Plugin](./ask-for-plugin.md) to request a custom plugin built by our team. ::: ================================================ FILE: docs/docs/plugins/use-in-third-party-library.md ================================================ --- title: Use in Third Party Library description: How to use React Native Video in a third party library sidebar_position: 4 --- # Use in Third Party Library You can use React Native Video in your third party library either as a dependency if you want to have specific version of the library or as a peer dependency if you want to version selection to be handled by the consumer of the library. ## In JS Add `react-native-video` as a dependency or peer dependency. ```json title="package.json" { "dependencies": { "react-native-video": "latest" } // OR "peerDependencies": { "react-native-video": "*" } } ``` And then you can import it in your code. ```ts import { VideoPlayer } from 'react-native-video'; const player = new VideoPlayer({ uri: 'https://www.example.com/video.mp4' }); player.play(); ``` ## In Native ### iOS Add `ReactNativeVideo` as a dependency in your `*.podspec` file. ```ruby title="*.podspec" Pod::Spec.new do |s| // ... s.dependency 'ReactNativeVideo' end ``` ### Android Add `:react-native-video` and `:react-native-nitro-modules` as a dependency in your `build.gradle` file. Also you will need to add `androidx.media3` dependencies. to use player and source in your library. ```groovy title="build.gradle" // ... dependencies { // ... implementation project(':react-native-video') implementation project(':react-native-nitro-modules') implementation "androidx.media3:media3-common:1.4.1" implementation "androidx.media3:media3-exoplayer:1.4.1" } ``` ================================================ FILE: docs/docs/projects.md ================================================ --- title: Useful Projects description: React Native Video Useful Projects sidebar_class_name: hidden --- # Useful Projects This page lists open-source projects that can be helpful for your player implementation.
If you have a project that could benefit other users, feel free to open a PR to add it here. ## Our (TheWidlarzGroup) Libraries - [react-native-video-player](https://github.com/TheWidlarzGroup/react-native-video-player): Our video player UI library. - [Offline Video SDK](https://www.thewidlarzgroup.com/offline-video-sdk?utm_source=rnv&utm_medium=docs&utm_campaign=projects&utm_id=offline-video-sdk): If you're building an app that needs **offline playback** (e.g., downloading HLS videos, subtitles, audio tracks, or DRM-protected content), check out our commercial Offline Video SDK. It integrates with `react-native-video` and is available with a [free trial](https://sdk.thewidlarzgroup.com/signup?utm_source=rnv&utm_medium=docs&utm_campaign=projects&utm_id=start-trial-offline-video-sdk). To get started quickly, you can clone our [Offline Video Starter Project](https://github.com/TheWidlarzGroup/react-native-offline-video-starter?utm_source=rnv&utm_medium=docs&utm_campaign=projects&utm_id=offline-video-starter), which includes a ready-to-run example app demonstrating offline playback, multi-audio, subtitles, and DRM setup. ## Community Libraries - [react-native-corner-video](https://github.com/Lg0gs/react-native-corner-video): A floating video player. - [react-native-track-player](https://github.com/doublesymmetry/react-native-track-player): A toolbox for audio playback. - [react-native-video-controls](https://github.com/itsnubix/react-native-video-controls): A video player UI. - [react-native-media-console](https://github.com/criszz77/react-native-media-console): An updated version of react-native-video-controls, rewritten in TypeScript. ================================================ FILE: docs/docs/updating.md ================================================ --- title: Updating description: React Native Video Updating Guide sidebar_class_name: hidden --- ## Upgrading from react-native-video v6 to v7 Version 7 of `react-native-video` introduces a significant architectural shift, separating the video player logic from the UI rendering. This change unlocks new capabilities like video preloading and a more intuitive, hook-based API. This guide will walk you through the necessary steps to migrate your application from v6 to v7. ### Key Changes in v7 The most substantial change in v7 is the move from a monolithic `