refactor: internal refactor for prepare new arch (#3980)

* chore(js): fix typo

* refactor(js): refactor type code for codegen

* refactor(js): refactor Video component

- parse shutterColor value within JS
- remove internal fullscreen state

* chore(js): add deprecation warning comment

* fix(js): fix return type

* fix(js): fix import path

* refactor(android): apply changed API for new arch

* refactor(ios): apply changed API for new arch

* fix(ios): fix wrong name

* refactor: refactor VideoDecoderProperties

- rename and add wrapper

* refactor(android): Code fixes for backward compatibility with Kotlin
This commit is contained in:
YangJH 2024-07-12 17:27:42 +09:00 committed by GitHub
parent de8ade0620
commit c2084c2ace
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 284 additions and 299 deletions

View File

@ -47,7 +47,7 @@ enum class EventTypes(val eventName: String) {
companion object { companion object {
fun toMap() = fun toMap() =
mutableMapOf<String, Any>().apply { mutableMapOf<String, Any>().apply {
EventTypes.entries.forEach { eventType -> EventTypes.values().toList().forEach { eventType ->
put("top${eventType.eventName.removePrefix("on")}", mapOf("registrationName" to eventType.eventName)) put("top${eventType.eventName.removePrefix("on")}", mapOf("registrationName" to eventType.eventName))
} }
} }

View File

@ -7,7 +7,6 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.media3.common.util.Util;
import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.BufferConfig;
import com.brentvatne.common.api.BufferingStrategy; import com.brentvatne.common.api.BufferingStrategy;
@ -203,7 +202,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
} }
@ReactProp(name = PROP_TEXT_TRACKS) @ReactProp(name = PROP_TEXT_TRACKS)
public void setPropTextTracks(final ReactExoplayerView videoView, public void setTextTracks(final ReactExoplayerView videoView,
@Nullable ReadableArray textTracks) { @Nullable ReadableArray textTracks) {
SideLoadedTextTrackList sideLoadedTextTracks = SideLoadedTextTrackList.Companion.parse(textTracks); SideLoadedTextTrackList sideLoadedTextTracks = SideLoadedTextTrackList.Companion.parse(textTracks);
videoView.setTextTracks(sideLoadedTextTracks); videoView.setTextTracks(sideLoadedTextTracks);
@ -245,12 +244,12 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
} }
@ReactProp(name = PROP_MAXIMUM_BIT_RATE) @ReactProp(name = PROP_MAXIMUM_BIT_RATE)
public void setMaxBitRate(final ReactExoplayerView videoView, final int maxBitRate) { public void setMaxBitRate(final ReactExoplayerView videoView, final float maxBitRate) {
videoView.setMaxBitRateModifier(maxBitRate); videoView.setMaxBitRateModifier((int)maxBitRate);
} }
@ReactProp(name = PROP_MIN_LOAD_RETRY_COUNT) @ReactProp(name = PROP_MIN_LOAD_RETRY_COUNT)
public void minLoadRetryCount(final ReactExoplayerView videoView, final int minLoadRetryCount) { public void setMinLoadRetryCount(final ReactExoplayerView videoView, final int minLoadRetryCount) {
videoView.setMinLoadRetryCountModifier(minLoadRetryCount); videoView.setMinLoadRetryCountModifier(minLoadRetryCount);
} }
@ -310,12 +309,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setSubtitleStyle(SubtitleStyle.parse(src)); videoView.setSubtitleStyle(SubtitleStyle.parse(src));
} }
@ReactProp(name = PROP_SHUTTER_COLOR, customType = "Color") @ReactProp(name = PROP_SHUTTER_COLOR, defaultInt = 0)
public void setShutterColor(final ReactExoplayerView videoView, final Integer color) { public void setShutterColor(final ReactExoplayerView videoView, final int color) {
videoView.setShutterColor(color == null ? Color.BLACK : color); videoView.setShutterColor(color == 0 ? Color.BLACK : color);
} }
@ReactProp(name = PROP_BUFFER_CONFIG) @ReactProp(name = PROP_BUFFER_CONFIG)
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) { public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
BufferConfig config = BufferConfig.parse(bufferConfig); BufferConfig config = BufferConfig.parse(bufferConfig);

View File

@ -13,7 +13,7 @@ class ReactVideoPackage(private val config: ReactExoplayerConfig? = null) : Reac
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
listOf( listOf(
VideoDecoderPropertiesModule(reactContext), VideoDecoderInfoModule(reactContext),
VideoManagerModule(reactContext) VideoManagerModule(reactContext)
) )

View File

@ -11,7 +11,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReactMethod
import java.util.UUID import java.util.UUID
class VideoDecoderPropertiesModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) { class VideoDecoderInfoModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String = REACT_CLASS override fun getName(): String = REACT_CLASS
@ReactMethod @ReactMethod
@ -37,36 +37,36 @@ class VideoDecoderPropertiesModule(reactContext: ReactApplicationContext?) : Rea
} }
@ReactMethod @ReactMethod
fun isCodecSupported(mimeType: String?, width: Int, height: Int, p: Promise) { fun isCodecSupported(mimeType: String?, width: Double, height: Double, p: Promise?) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
p.resolve("unsupported") p?.resolve("unsupported")
return return
} }
val mRegularCodecs = MediaCodecList(MediaCodecList.REGULAR_CODECS) val mRegularCodecs = MediaCodecList(MediaCodecList.REGULAR_CODECS)
val format = MediaFormat.createVideoFormat(mimeType!!, width, height) val format = MediaFormat.createVideoFormat(mimeType!!, width.toInt(), height.toInt())
val codecName = mRegularCodecs.findDecoderForFormat(format) val codecName = mRegularCodecs.findDecoderForFormat(format)
if (codecName == null) { if (codecName == null) {
p.resolve("unsupported") p?.resolve("unsupported")
return return
} }
// Fallback for android < 10 // Fallback for android < 10
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
p.resolve("software") p?.resolve("software")
return return
} }
val isHardwareAccelerated = mRegularCodecs.codecInfos.any { val isHardwareAccelerated = mRegularCodecs.codecInfos.any {
it.name.equals(codecName, ignoreCase = true) && it.isHardwareAccelerated it.name.equals(codecName, ignoreCase = true) && it.isHardwareAccelerated
} }
p.resolve(if (isHardwareAccelerated) "software" else "hardware") p?.resolve(if (isHardwareAccelerated) "software" else "hardware")
} }
@ReactMethod @ReactMethod
fun isHEVCSupported(p: Promise) = isCodecSupported("video/hevc", 1920, 1080, p) fun isHEVCSupported(p: Promise) = isCodecSupported("video/hevc", 1920.0, 1080.0, p)
companion object { companion object {
private val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) private val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
private const val SECURITY_LEVEL_PROPERTY = "securityLevel" private const val SECURITY_LEVEL_PROPERTY = "securityLevel"
private const val REACT_CLASS = "VideoDecoderProperties" private const val REACT_CLASS = "VideoDecoderInfoModule"
} }
} }

View File

@ -1,12 +1,10 @@
package com.brentvatne.react package com.brentvatne.react
import com.brentvatne.common.toolbox.ReactBridgeUtils
import com.brentvatne.exoplayer.ReactExoplayerView import com.brentvatne.exoplayer.ReactExoplayerView
import com.facebook.react.bridge.Promise import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.common.UIManagerType import com.facebook.react.uimanager.common.UIManagerType
@ -37,31 +35,33 @@ class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextB
} }
@ReactMethod @ReactMethod
fun setPlayerPauseState(paused: Boolean?, reactTag: Int) { fun setPlayerPauseStateCmd(reactTag: Int, paused: Boolean?) {
performOnPlayerView(reactTag) { performOnPlayerView(reactTag) {
it?.setPausedModifier(paused!!) it?.setPausedModifier(paused!!)
} }
} }
@ReactMethod @ReactMethod
fun seek(info: ReadableMap, reactTag: Int) { fun seekCmd(reactTag: Int, time: Float, tolerance: Float) {
if (!info.hasKey("time")) {
return
}
val time = ReactBridgeUtils.safeGetInt(info, "time")
performOnPlayerView(reactTag) { performOnPlayerView(reactTag) {
it?.seekTo((time * 1000f).roundToInt().toLong()) it?.seekTo((time * 1000f).roundToInt().toLong())
} }
} }
@ReactMethod @ReactMethod
fun setVolume(volume: Float, reactTag: Int) { fun setVolumeCmd(reactTag: Int, volume: Float) {
performOnPlayerView(reactTag) { performOnPlayerView(reactTag) {
it?.setVolumeModifier(volume) it?.setVolumeModifier(volume)
} }
} }
@ReactMethod
fun setFullScreenCmd(reactTag: Int, fullScreen: Boolean) {
performOnPlayerView(reactTag) {
it?.setFullscreen(fullScreen)
}
}
@ReactMethod @ReactMethod
fun getCurrentPosition(reactTag: Int, promise: Promise) { fun getCurrentPosition(reactTag: Int, promise: Promise) {
performOnPlayerView(reactTag) { performOnPlayerView(reactTag) {
@ -69,13 +69,6 @@ class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextB
} }
} }
@ReactMethod
fun setFullScreen(fullScreen: Boolean, reactTag: Int) {
performOnPlayerView(reactTag) {
it?.setFullscreen(fullScreen)
}
}
companion object { companion object {
private const val REACT_CLASS = "VideoManager" private const val REACT_CLASS = "VideoManager"
} }

View File

@ -762,15 +762,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
} }
@objc @objc
func setSeek(_ info: NSDictionary!) { func setSeek(_ time: NSNumber, _ tolerance: NSNumber) {
let seekTime: NSNumber! = info["time"] as! NSNumber
let seekTolerance: NSNumber! = info["tolerance"] as! NSNumber
let item: AVPlayerItem? = _player?.currentItem let item: AVPlayerItem? = _player?.currentItem
_pendingSeek = true _pendingSeek = true
guard item != nil, let player = _player, let item, item.status == AVPlayerItem.Status.readyToPlay else { guard item != nil, let player = _player, let item, item.status == AVPlayerItem.Status.readyToPlay else {
_pendingSeekTime = seekTime.floatValue _pendingSeekTime = time.floatValue
return return
} }
@ -778,15 +776,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
player: player, player: player,
playerItem: item, playerItem: item,
paused: _paused, paused: _paused,
seekTime: seekTime.floatValue, seekTime: time.floatValue,
seekTolerance: seekTolerance.floatValue seekTolerance: tolerance.floatValue
) { [weak self] (_: Bool) in ) { [weak self] (_: Bool) in
guard let self else { return } guard let self else { return }
self._playerObserver.addTimeObserverIfNotSet() self._playerObserver.addTimeObserverIfNotSet()
self.setPaused(self._paused) self.setPaused(self._paused)
self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))), self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))),
"seekTime": seekTime, "seekTime": time,
"target": self.reactTag]) "target": self.reactTag])
} }
@ -1303,7 +1301,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
// MARK: - Export // MARK: - Export
@objc @objc
func save(options: NSDictionary!, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { func save(_ options: NSDictionary!, _ resolve: @escaping RCTPromiseResolveBlock, _ reject: @escaping RCTPromiseRejectBlock) {
RCTVideoSave.save( RCTVideoSave.save(
options: options, options: options,
resolve: resolve, resolve: resolve,
@ -1320,14 +1318,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
_resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl) _resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl)
} }
func dismissFullscreenPlayer() {
setFullscreen(false)
}
func presentFullscreenPlayer() {
setFullscreen(true)
}
// MARK: - RCTPlayerObserverHandler // MARK: - RCTPlayerObserverHandler
func handleTimeUpdate(time _: CMTime) { func handleTimeUpdate(time _: CMTime) {
@ -1381,18 +1371,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
Task { Task {
if self._pendingSeek { if self._pendingSeek {
self.setSeek([ self.setSeek(NSNumber(value: self._pendingSeekTime), NSNumber(value: 100))
"time": NSNumber(value: self._pendingSeekTime),
"tolerance": NSNumber(value: 100),
])
self._pendingSeek = false self._pendingSeek = false
} }
if self._startPosition >= 0 { if self._startPosition >= 0 {
self.setSeek([ self.setSeek(NSNumber(value: self._startPosition), NSNumber(value: 100))
"time": NSNumber(value: self._startPosition),
"tolerance": NSNumber(value: 100),
])
self._startPosition = -1 self._startPosition = -1
} }

View File

@ -1,5 +1,5 @@
#import "React/RCTViewManager.h" #import "React/RCTViewManager.h"
#import <React/RCTBridge.h> #import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager)
@ -37,7 +37,7 @@ RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL); RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
RCT_EXPORT_VIEW_PROPERTY(localSourceEncryptionKeyScheme, NSString); RCT_EXPORT_VIEW_PROPERTY(localSourceEncryptionKeyScheme, NSString);
RCT_EXPORT_VIEW_PROPERTY(subtitleStyle, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(subtitleStyle, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(showNotificationControls, BOOL) RCT_EXPORT_VIEW_PROPERTY(showNotificationControls, BOOL);
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock);
@ -68,31 +68,21 @@ RCT_EXPORT_VIEW_PROPERTY(onTextTracks, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onTextTrackDataChanged, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onTextTrackDataChanged, RCTDirectEventBlock);
RCT_EXTERN_METHOD(seekCmd : (nonnull NSNumber*)reactTag time : (nonnull NSNumber*)time tolerance : (nonnull NSNumber*)tolerance)
RCT_EXTERN_METHOD(setLicenseResultCmd : (nonnull NSNumber*)reactTag lisence : (NSString*)license licenseUrl : (NSString*)licenseUrl)
RCT_EXTERN_METHOD(setLicenseResultErrorCmd : (nonnull NSNumber*)reactTag error : (NSString*)error licenseUrl : (NSString*)licenseUrl)
RCT_EXTERN_METHOD(setPlayerPauseStateCmd : (nonnull NSNumber*)reactTag paused : (nonnull BOOL)paused)
RCT_EXTERN_METHOD(setVolumeCmd : (nonnull NSNumber*)reactTag volume : (nonnull float*)volume)
RCT_EXTERN_METHOD(setFullScreenCmd : (nonnull NSNumber*)reactTag fullscreen : (nonnull BOOL)fullScreen)
RCT_EXTERN_METHOD(save RCT_EXTERN_METHOD(save
: (NSDictionary*)options reactTag : (nonnull NSNumber*)reactTag options
: (nonnull NSNumber*)reactTag resolver : (NSDictionary*)options resolve
: (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject) : (RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(seek : (NSDictionary*)info reactTag : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(setLicenseResult : (NSString*)license licenseUrl : (NSString*)licenseUrl reactTag : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(setLicenseResultError : (NSString*)error licenseUrl : (NSString*)licenseUrl reactTag : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(setPlayerPauseState : (nonnull NSNumber*)paused reactTag : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(presentFullscreenPlayer : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(dismissFullscreenPlayer : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(setVolume : (nonnull float*)volume reactTag : (nonnull NSNumber*)reactTag)
RCT_EXTERN_METHOD(getCurrentPosition RCT_EXTERN_METHOD(getCurrentPosition
: (nonnull NSNumber*)reactTag resolver : (nonnull NSNumber*)reactTag resolve
: (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject) : (RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(setFullScreen : (BOOL)fullScreen reactTag : (nonnull NSNumber*)reactTag)
@end @end

View File

@ -30,76 +30,62 @@ class RCTVideoManager: RCTViewManager {
} }
} }
@objc(save:reactTag:resolver:rejecter:) @objc(seekCmd:time:tolerance:)
func save(options: NSDictionary, reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { func seekCmd(_ reactTag: NSNumber, time: NSNumber, tolerance: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.save(options: options, resolve: resolve, reject: reject) videoView?.setSeek(time, tolerance)
}) })
} }
@objc(seek:reactTag:) @objc(setLicenseResultCmd:license:licenseUrl:)
func seek(info: NSDictionary, reactTag: NSNumber) { func setLicenseResultCmd(_ reactTag: NSNumber, license: NSString, licenseUrl: NSString) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setSeek(info)
})
}
@objc(setLicenseResult:licenseUrl:reactTag:)
func setLicenseResult(license: NSString, licenseUrl: NSString, reactTag: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setLicenseResult(license as String, licenseUrl as String) videoView?.setLicenseResult(license as String, licenseUrl as String)
}) })
} }
@objc(setLicenseResultError:licenseUrl:reactTag:) @objc(setLicenseResultErrorCmd:error:licenseUrl:)
func setLicenseResultError(error: NSString, licenseUrl: NSString, reactTag: NSNumber) { func setLicenseResultErrorCmd(_ reactTag: NSNumber, error: NSString, licenseUrl: NSString) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setLicenseResultError(error as String, licenseUrl as String) videoView?.setLicenseResultError(error as String, licenseUrl as String)
}) })
} }
@objc(dismissFullscreenPlayer:) @objc(setPlayerPauseStateCmd:paused:)
func dismissFullscreenPlayer(_ reactTag: NSNumber) { func setPlayerPauseStateCmd(_ reactTag: NSNumber, paused: Bool) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.dismissFullscreenPlayer() videoView?.setPaused(paused)
}) })
} }
@objc(presentFullscreenPlayer:) @objc(setVolumeCmd:volume:)
func presentFullscreenPlayer(_ reactTag: NSNumber) { func setVolumeCmd(_ reactTag: NSNumber, volume: Float) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.presentFullscreenPlayer() videoView?.setVolume(volume)
}) })
} }
@objc(setPlayerPauseState:reactTag:) @objc(setFullScreenCmd:fullscreen:)
func setPlayerPauseState(paused: NSNumber, reactTag: NSNumber) { func setFullScreenCmd(_ reactTag: NSNumber, fullScreen: Bool) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setPaused(paused.boolValue)
})
}
@objc(setVolume:reactTag:)
func setVolume(value: Float, reactTag: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setVolume(value)
})
}
@objc(getCurrentPosition:resolver:rejecter:)
func getCurrentPosition(reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.getCurrentPlaybackTime(resolve, reject)
})
}
@objc(setFullScreen:reactTag:)
func setFullScreen(fullScreen: Bool, reactTag: NSNumber) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.setFullscreen(fullScreen) videoView?.setFullscreen(fullScreen)
}) })
} }
@objc(save:options:resolve:reject:)
func save(_ reactTag: NSNumber, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.save(options, resolve, reject)
})
}
@objc(getCurrentPosition:resolve:reject:)
func getCurrentPosition(_ reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
performOnVideoView(withReactTag: reactTag, callback: { videoView in
videoView?.getCurrentPlaybackTime(resolve, reject)
})
}
override class func requiresMainQueueSetup() -> Bool { override class func requiresMainQueueSetup() -> Bool {
return true return true
} }

View File

@ -5,57 +5,47 @@ import React, {
useRef, useRef,
forwardRef, forwardRef,
useImperativeHandle, useImperativeHandle,
type ComponentRef,
} from 'react'; } from 'react';
import { import type {ElementRef} from 'react';
View, import {View, StyleSheet, Image, Platform, processColor} from 'react-native';
StyleSheet, import type {StyleProp, ImageStyle, NativeSyntheticEvent} from 'react-native';
Image,
Platform,
type StyleProp,
type ImageStyle,
type NativeSyntheticEvent,
} from 'react-native';
import NativeVideoComponent, { import NativeVideoComponent from './specs/VideoNativeComponent';
type OnAudioFocusChangedData, import type {
type OnAudioTracksData, OnAudioFocusChangedData,
type OnBandwidthUpdateData, OnAudioTracksData,
type OnBufferData, OnBandwidthUpdateData,
type OnControlsVisibilityChange, OnBufferData,
type OnExternalPlaybackChangeData, OnControlsVisibilityChange,
type OnGetLicenseData, OnExternalPlaybackChangeData,
type OnLoadStartData, OnGetLicenseData,
type OnPictureInPictureStatusChangedData, OnLoadStartData,
type OnPlaybackStateChangedData, OnPictureInPictureStatusChangedData,
type OnProgressData, OnPlaybackStateChangedData,
type OnSeekData, OnProgressData,
type OnTextTrackDataChangedData, OnSeekData,
type OnTimedMetadataData, OnTextTrackDataChangedData,
type OnVideoAspectRatioData, OnTimedMetadataData,
type OnVideoErrorData, OnVideoAspectRatioData,
type OnVideoTracksData, OnVideoErrorData,
type VideoComponentType, OnVideoTracksData,
type VideoSrc, VideoSrc,
} from './specs/VideoNativeComponent'; } from './specs/VideoNativeComponent';
import { import {
generateHeaderForNative, generateHeaderForNative,
getReactTag, getReactTag,
resolveAssetSourceForVideo, resolveAssetSourceForVideo,
} from './utils'; } from './utils';
import {VideoManager} from './specs/VideoNativeComponent'; import NativeVideoManager from './specs/NativeVideoManager';
import { import type {VideoSaveData} from './specs/NativeVideoManager';
type OnLoadData, import {ViewType} from './types';
type OnTextTracksData, import type {
type OnReceiveAdEventData, OnLoadData,
type ReactVideoProps, OnTextTracksData,
ViewType, OnReceiveAdEventData,
ReactVideoProps,
} from './types'; } from './types';
export type VideoSaveData = {
uri: string;
};
export interface VideoRef { export interface VideoRef {
seek: (time: number, tolerance?: number) => void; seek: (time: number, tolerance?: number) => void;
resume: () => void; resume: () => void;
@ -65,10 +55,10 @@ export interface VideoRef {
restoreUserInterfaceForPictureInPictureStopCompleted: ( restoreUserInterfaceForPictureInPictureStopCompleted: (
restore: boolean, restore: boolean,
) => void; ) => void;
save: (options: object) => Promise<VideoSaveData>;
setVolume: (volume: number) => void; setVolume: (volume: number) => void;
getCurrentPosition: () => Promise<number>;
setFullScreen: (fullScreen: boolean) => void; setFullScreen: (fullScreen: boolean) => void;
save: (options: object) => Promise<VideoSaveData> | void;
getCurrentPosition: () => Promise<number>;
} }
const Video = forwardRef<VideoRef, ReactVideoProps>( const Video = forwardRef<VideoRef, ReactVideoProps>(
@ -79,7 +69,6 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
resizeMode, resizeMode,
posterResizeMode, posterResizeMode,
poster, poster,
fullscreen,
drm, drm,
textTracks, textTracks,
selectedVideoTrack, selectedVideoTrack,
@ -88,6 +77,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
useTextureView, useTextureView,
useSecureView, useSecureView,
viewType, viewType,
shutterColor,
onLoadStart, onLoadStart,
onLoad, onLoad,
onError, onError,
@ -122,9 +112,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
}, },
ref, ref,
) => { ) => {
const nativeRef = useRef<ComponentRef<VideoComponentType>>(null); const nativeRef = useRef<ElementRef<typeof NativeVideoComponent>>(null);
const [showPoster, setShowPoster] = useState(!!poster); const [showPoster, setShowPoster] = useState(!!poster);
const [isFullscreen, setIsFullscreen] = useState(fullscreen);
const [ const [
_restoreUserInterfaceForPIPStopCompletionHandler, _restoreUserInterfaceForPIPStopCompletionHandler,
setRestoreUserInterfaceForPIPStopCompletionHandler, setRestoreUserInterfaceForPIPStopCompletionHandler,
@ -274,12 +263,10 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
} }
const callSeekFunction = () => { const callSeekFunction = () => {
VideoManager.seek( NativeVideoManager.seekCmd(
{
time,
tolerance: tolerance || 0,
},
getReactTag(nativeRef), getReactTag(nativeRef),
time,
tolerance || 0,
); );
}; };
@ -287,31 +274,59 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
ios: callSeekFunction, ios: callSeekFunction,
android: callSeekFunction, android: callSeekFunction,
default: () => { default: () => {
// TODO: Implement VideoManager.seek for windows // TODO: Implement VideoManager.seekCmd for windows
nativeRef.current?.setNativeProps({seek: time}); nativeRef.current?.setNativeProps({seek: time});
}, },
})(); })();
}, []); }, []);
const presentFullscreenPlayer = useCallback(() => {
setIsFullscreen(true);
}, [setIsFullscreen]);
const dismissFullscreenPlayer = useCallback(() => {
setIsFullscreen(false);
}, [setIsFullscreen]);
const save = useCallback((options: object) => {
// VideoManager.save can be null on android & windows
return VideoManager.save?.(options, getReactTag(nativeRef));
}, []);
const pause = useCallback(() => { const pause = useCallback(() => {
return VideoManager.setPlayerPauseState(true, getReactTag(nativeRef)); return NativeVideoManager.setPlayerPauseStateCmd(
getReactTag(nativeRef),
true,
);
}, []); }, []);
const resume = useCallback(() => { const resume = useCallback(() => {
return VideoManager.setPlayerPauseState(false, getReactTag(nativeRef)); return NativeVideoManager.setPlayerPauseStateCmd(
getReactTag(nativeRef),
false,
);
}, []);
const setVolume = useCallback((volume: number) => {
return NativeVideoManager.setVolumeCmd(getReactTag(nativeRef), volume);
}, []);
const setFullScreen = useCallback((fullScreen: boolean) => {
return NativeVideoManager.setFullScreenCmd(
getReactTag(nativeRef),
fullScreen,
);
}, []);
const presentFullscreenPlayer = useCallback(
() => setFullScreen(true),
[setFullScreen],
);
const dismissFullscreenPlayer = useCallback(
() => setFullScreen(false),
[setFullScreen],
);
const save = useCallback((options: object) => {
// VideoManager.save can be null on android & windows
if (Platform.OS !== 'ios') {
return;
}
// @todo Must implement it in a different way.
return NativeVideoManager.save?.(getReactTag(nativeRef), options);
}, []);
const getCurrentPosition = useCallback(() => {
// @todo Must implement it in a different way.
return NativeVideoManager.getCurrentPosition(getReactTag(nativeRef));
}, []); }, []);
const restoreUserInterfaceForPictureInPictureStopCompleted = useCallback( const restoreUserInterfaceForPictureInPictureStopCompleted = useCallback(
@ -321,18 +336,6 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
[setRestoreUserInterfaceForPIPStopCompletionHandler], [setRestoreUserInterfaceForPIPStopCompletionHandler],
); );
const setVolume = useCallback((volume: number) => {
return VideoManager.setVolume(volume, getReactTag(nativeRef));
}, []);
const getCurrentPosition = useCallback(() => {
return VideoManager.getCurrentPosition(getReactTag(nativeRef));
}, []);
const setFullScreen = useCallback((fullScreen: boolean) => {
return VideoManager.setFullScreen(fullScreen, getReactTag(nativeRef));
}, []);
const onVideoLoadStart = useCallback( const onVideoLoadStart = useCallback(
(e: NativeSyntheticEvent<OnLoadStartData>) => { (e: NativeSyntheticEvent<OnLoadStartData>) => {
hasPoster && setShowPoster(true); hasPoster && setShowPoster(true);
@ -379,6 +382,11 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
[onPlaybackStateChanged], [onPlaybackStateChanged],
); );
const _shutterColor = useMemo(() => {
const color = processColor(shutterColor);
return typeof color === 'number' ? color : undefined;
}, [shutterColor]);
// android only // android only
const _onTimedMetadata = useCallback( const _onTimedMetadata = useCallback(
(e: NativeSyntheticEvent<OnTimedMetadataData>) => { (e: NativeSyntheticEvent<OnTimedMetadataData>) => {
@ -494,56 +502,41 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
[onControlsVisibilityChange], [onControlsVisibilityChange],
); );
const useExternalGetLicense = drm?.getLicense instanceof Function; const usingExternalGetLicense = drm?.getLicense instanceof Function;
const onGetLicense = useCallback( const onGetLicense = useCallback(
(event: NativeSyntheticEvent<OnGetLicenseData>) => { async (event: NativeSyntheticEvent<OnGetLicenseData>) => {
if (useExternalGetLicense) { if (!usingExternalGetLicense) {
return;
}
const data = event.nativeEvent; const data = event.nativeEvent;
if (data && data.spcBase64) { let result;
const getLicenseOverride = drm.getLicense( if (data?.spcBase64) {
try {
// Handles both scenarios, getLicenseOverride being a promise and not.
const license = await drm.getLicense(
data.spcBase64, data.spcBase64,
data.contentId, data.contentId,
data.licenseUrl, data.licenseUrl,
data.loadedLicenseUrl, data.loadedLicenseUrl,
); );
const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not. result =
getLicensePromise typeof license === 'string' ? license : 'Empty license result';
.then((result) => { } catch {
if (result !== undefined) { result = 'fetch error';
nativeRef.current && }
VideoManager.setLicenseResult( } else {
result = 'No spc received';
}
if (nativeRef.current) {
NativeVideoManager.setLicenseResultErrorCmd(
getReactTag(nativeRef),
result, result,
data.loadedLicenseUrl, data.loadedLicenseUrl,
getReactTag(nativeRef),
); );
} else {
nativeRef.current &&
VideoManager.setLicenseResultError(
'Empty license result',
data.loadedLicenseUrl,
getReactTag(nativeRef),
);
}
})
.catch(() => {
nativeRef.current &&
VideoManager.setLicenseResultError(
'fetch error',
data.loadedLicenseUrl,
getReactTag(nativeRef),
);
});
} else {
VideoManager.setLicenseResultError(
'No spc received',
data.loadedLicenseUrl,
getReactTag(nativeRef),
);
}
} }
}, },
[drm, useExternalGetLicense], [drm, usingExternalGetLicense],
); );
useImperativeHandle( useImperativeHandle(
@ -613,7 +606,6 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
src={src} src={src}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
resizeMode={resizeMode} resizeMode={resizeMode}
fullscreen={isFullscreen}
restoreUserInterfaceForPIPStopCompletionHandler={ restoreUserInterfaceForPIPStopCompletionHandler={
_restoreUserInterfaceForPIPStopCompletionHandler _restoreUserInterfaceForPIPStopCompletionHandler
} }
@ -621,7 +613,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
selectedTextTrack={_selectedTextTrack} selectedTextTrack={_selectedTextTrack}
selectedAudioTrack={_selectedAudioTrack} selectedAudioTrack={_selectedAudioTrack}
selectedVideoTrack={_selectedVideoTrack} selectedVideoTrack={_selectedVideoTrack}
onGetLicense={useExternalGetLicense ? onGetLicense : undefined} shutterColor={_shutterColor}
onGetLicense={usingExternalGetLicense ? onGetLicense : undefined}
onVideoLoad={ onVideoLoad={
onLoad || hasPoster onLoad || hasPoster
? (onVideoLoad as (e: NativeSyntheticEvent<object>) => void) ? (onVideoLoad as (e: NativeSyntheticEvent<object>) => void)

View File

@ -0,0 +1,29 @@
import {Platform} from 'react-native';
import NativeVideoDecoderInfoModule from './specs/NativeVideoDecoderInfoModule';
const errMsgGen = (moduleName: string, propertyName: string) =>
`The method or property ${moduleName}.${propertyName} is not available on ${Platform.OS}.`;
export const VideoDecoderProperties = {
async getWidevineLevel() {
if (Platform.OS !== 'android') {
throw new Error(errMsgGen('VideoDecoderProperties', 'getWidevineLevel'));
}
return NativeVideoDecoderInfoModule.getWidevineLevel();
},
async isCodecSupported(
...args: Parameters<typeof NativeVideoDecoderInfoModule.isCodecSupported>
) {
if (Platform.OS !== 'android') {
throw new Error(errMsgGen('VideoDecoderProperties', 'isCodecSupported'));
}
return NativeVideoDecoderInfoModule.isCodecSupported(...args);
},
async isHEVCSupported() {
if (Platform.OS !== 'android') {
throw new Error(errMsgGen('VideoDecoderProperties', 'isHEVCSupported'));
}
return NativeVideoDecoderInfoModule.isHEVCSupported();
},
};

View File

@ -1,5 +1,5 @@
import Video from './Video'; import Video from './Video';
export {VideoDecoderProperties} from './specs/VideoNativeComponent'; export {VideoDecoderProperties} from './VideoDecoderProperties';
export * from './types'; export * from './types';
export type {VideoRef} from './Video'; export type {VideoRef} from './Video';
export default Video; export default Video;

View File

@ -0,0 +1,15 @@
import {NativeModules} from 'react-native';
import type {Int32} from 'react-native/Libraries/Types/CodegenTypes';
// @TODO rename to "Spec" when applying new arch
interface VideoDecoderInfoModuleType {
getWidevineLevel: () => Promise<Int32>;
isCodecSupported: (
mimeType: string,
width: Int32,
height: Int32,
) => Promise<'unsupported' | 'hardware' | 'software'>;
isHEVCSupported: () => Promise<'unsupported' | 'hardware' | 'software'>;
}
export default NativeModules.VideoDecoderInfoModule as VideoDecoderInfoModuleType;

View File

@ -0,0 +1,32 @@
import {NativeModules} from 'react-native';
import type {
Int32,
Float,
UnsafeObject,
} from 'react-native/Libraries/Types/CodegenTypes';
export type VideoSaveData = {
uri: string;
};
// @TODO rename to "Spec" when applying new arch
export interface VideoManagerType {
seekCmd: (reactTag: Int32, time: Float, tolerance?: Float) => Promise<void>;
setPlayerPauseStateCmd: (reactTag: Int32, paused: boolean) => Promise<void>;
setLicenseResultCmd: (
reactTag: Int32,
result: string,
licenseUrl: string,
) => Promise<void>;
setLicenseResultErrorCmd: (
reactTag: Int32,
error: string,
licenseUrl: string,
) => Promise<void>;
setFullScreenCmd: (reactTag: Int32, fullScreen: boolean) => Promise<void>;
setVolumeCmd: (reactTag: Int32, volume: number) => Promise<void>;
save: (reactTag: Int32, option: UnsafeObject) => Promise<VideoSaveData>;
getCurrentPosition: (reactTag: Int32) => Promise<Int32>;
}
export default NativeModules.VideoManager as VideoManagerType;

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/ban-types */
import type {HostComponent, ViewProps} from 'react-native'; import type {HostComponent, ViewProps} from 'react-native';
import {NativeModules, requireNativeComponent} from 'react-native'; import {requireNativeComponent} from 'react-native';
import type { import type {
DirectEventHandler, DirectEventHandler,
Double, Double,
@ -91,11 +91,6 @@ type SelectedVideoTrack = Readonly<{
value?: string; value?: string;
}>; }>;
export type Seek = Readonly<{
time: Float;
tolerance?: Float;
}>;
type BufferConfigLive = Readonly<{ type BufferConfigLive = Readonly<{
maxPlaybackSpeed?: Float; maxPlaybackSpeed?: Float;
minPlaybackSpeed?: Float; minPlaybackSpeed?: Float;
@ -289,7 +284,7 @@ export type OnAudioFocusChangedData = Readonly<{
type ControlsStyles = Readonly<{ type ControlsStyles = Readonly<{
hideSeekBar?: boolean; hideSeekBar?: boolean;
seekIncrementMS?: number; seekIncrementMS?: Int32;
}>; }>;
export type OnControlsVisibilityChange = Readonly<{ export type OnControlsVisibilityChange = Readonly<{
@ -300,10 +295,13 @@ export interface VideoNativeProps extends ViewProps {
src?: VideoSrc; src?: VideoSrc;
adTagUrl?: string; adTagUrl?: string;
allowsExternalPlayback?: boolean; // ios, true allowsExternalPlayback?: boolean; // ios, true
disableFocus?: boolean; // android
maxBitRate?: Float; maxBitRate?: Float;
resizeMode?: WithDefault<string, 'none'>; resizeMode?: WithDefault<string, 'none'>;
repeat?: boolean; repeat?: boolean;
automaticallyWaitsToMinimizeStalling?: boolean; automaticallyWaitsToMinimizeStalling?: boolean;
shutterColor?: Int32;
audioOutput?: WithDefault<string, 'speaker'>;
textTracks?: TextTracks; textTracks?: TextTracks;
selectedTextTrack?: SelectedTextTrack; selectedTextTrack?: SelectedTextTrack;
selectedAudioTrack?: SelectedAudioTrack; selectedAudioTrack?: SelectedAudioTrack;
@ -375,45 +373,8 @@ export interface VideoNativeProps extends ViewProps {
onVideoTracks?: DirectEventHandler<OnVideoTracksData>; // android onVideoTracks?: DirectEventHandler<OnVideoTracksData>; // android
} }
export type VideoComponentType = HostComponent<VideoNativeProps>; type NativeVideoComponentType = HostComponent<VideoNativeProps>;
export type VideoSaveData = {
uri: string;
};
export interface VideoManagerType {
save: (option: object, reactTag: number) => Promise<VideoSaveData>;
seek: (option: Seek, reactTag: number) => Promise<void>;
setPlayerPauseState: (paused: boolean, reactTag: number) => Promise<void>;
setLicenseResult: (
result: string,
licenseUrl: string,
reactTag: number,
) => Promise<void>;
setLicenseResultError: (
error: string,
licenseUrl: string,
reactTag: number,
) => Promise<void>;
setVolume: (volume: number, reactTag: number) => Promise<void>;
getCurrentPosition: (reactTag: number) => Promise<number>;
setFullScreen: (fullScreen: boolean, reactTag: number) => Promise<void>;
}
export interface VideoDecoderPropertiesType {
getWidevineLevel: () => Promise<number>;
isCodecSupported: (
mimeType: string,
width: number,
height: number,
) => Promise<'unsupported' | 'hardware' | 'software'>;
isHEVCSupported: () => Promise<'unsupported' | 'hardware' | 'software'>;
}
export const VideoManager = NativeModules.VideoManager as VideoManagerType;
export const VideoDecoderProperties =
NativeModules.VideoDecoderProperties as VideoDecoderPropertiesType;
export default requireNativeComponent<VideoNativeProps>( export default requireNativeComponent<VideoNativeProps>(
'RCTVideo', 'RCTVideo',
) as VideoComponentType; ) as NativeVideoComponentType;

View File

@ -2,10 +2,10 @@
* Define Available view type for android * Define Available view type for android
* these values shall match android spec, see ViewType.kt * these values shall match android spec, see ViewType.kt
*/ */
enum ResizeMode { enum ViewType {
TEXTURE = 0, TEXTURE = 0,
SURFACE = 1, SURFACE = 1,
SURFACE_SECURE = 2, SURFACE_SECURE = 2,
} }
export default ResizeMode; export default ViewType;

View File

@ -68,7 +68,7 @@ export type Drm = Readonly<{
contentId: string, contentId: string,
licenseUrl: string, licenseUrl: string,
loadedLicenseUrl: string, loadedLicenseUrl: string,
) => void; // ios ) => string | Promise<string>; // ios
/* eslint-enable @typescript-eslint/no-unused-vars */ /* eslint-enable @typescript-eslint/no-unused-vars */
}>; }>;

View File

@ -43,6 +43,10 @@ export function resolveAssetSourceForVideo(
return source as ReactVideoSourceProperties; return source as ReactVideoSourceProperties;
} }
/**
* @deprecated
* Do not use this fn anymore. "findNodeHandle" will be deprecated.
* */
export function getReactTag( export function getReactTag(
ref: RefObject< ref: RefObject<
| Component<unknown, unknown, unknown> | Component<unknown, unknown, unknown>