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:
parent
de8ade0620
commit
c2084c2ace
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
229
src/Video.tsx
229
src/Video.tsx
@ -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)
|
||||||
|
29
src/VideoDecoderProperties.ts
Normal file
29
src/VideoDecoderProperties.ts
Normal 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();
|
||||||
|
},
|
||||||
|
};
|
@ -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;
|
||||||
|
15
src/specs/NativeVideoDecoderInfoModule.ts
Normal file
15
src/specs/NativeVideoDecoderInfoModule.ts
Normal 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;
|
32
src/specs/NativeVideoManager.ts
Normal file
32
src/specs/NativeVideoManager.ts
Normal 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;
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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 */
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user