mirror of
https://github.com/zoriya/react-native-video.git
synced 2026-08-16 02:56:22 +00:00
feat: add more basic functionality (#5)
This commit is contained in:
@@ -263,14 +263,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-VideoExample/Pods-VideoExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-VideoExample/Pods-VideoExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VideoExample/Pods-VideoExample-frameworks.sh\"\n";
|
||||
@@ -306,14 +302,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-VideoExample/Pods-VideoExample-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-VideoExample/Pods-VideoExample-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VideoExample/Pods-VideoExample-resources.sh\"\n";
|
||||
@@ -523,7 +515,10 @@
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
@@ -592,7 +587,10 @@
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
|
||||
+8
-2
@@ -12,7 +12,12 @@ import { VideoView, createSource, useVideoPlayer } from 'react-native-video';
|
||||
const VideoDemo = () => {
|
||||
const [show, setShow] = React.useState(false);
|
||||
|
||||
const player = useVideoPlayer('https://www.w3schools.com/html/mov_bbb.mp4');
|
||||
const player = useVideoPlayer(
|
||||
'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
(_player) => {
|
||||
_player.loop = true;
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@@ -27,7 +32,8 @@ const VideoDemo = () => {
|
||||
</View>
|
||||
<Button title="Play" onPress={() => player.play()} />
|
||||
<Button title="Pause" onPress={() => player.pause()} />
|
||||
<Button title="Seek to 3sec" onPress={() => (player.currentTime = 3)} />
|
||||
<Button title="Seek to 0sec" onPress={() => player.seekTo(0)} />
|
||||
<Button title="Seek by 1sec" onPress={() => player.seekBy(1)} />
|
||||
<Button
|
||||
title={show ? 'Hide VideoView' : 'Show VideoView'}
|
||||
onPress={() => setShow((prev) => !prev)}
|
||||
|
||||
+47
-2
@@ -12,9 +12,10 @@ import com.margelo.nitro.NitroModules
|
||||
import com.margelo.nitro.core.Promise
|
||||
import com.video.core.LibraryError
|
||||
import com.video.core.PlayerError
|
||||
import com.video.core.SourceError
|
||||
import com.video.core.utils.Threading.runOnMainThread
|
||||
import com.video.core.utils.Threading.runOnMainThreadSync
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@UnstableApi
|
||||
@DoNotStrip
|
||||
@@ -51,9 +52,15 @@ class HybridVideoPlayer() : HybridVideoPlayerSpec() {
|
||||
get() = runOnMainThreadSync { return@runOnMainThreadSync playerPointer.currentPosition.toDouble() / 1000.0 }
|
||||
set(value) = runOnMainThread { playerPointer.seekTo((value * 1000).toLong()) }
|
||||
|
||||
// volume defined by user
|
||||
var userVolume: Double = 1.0
|
||||
|
||||
override var volume: Double
|
||||
get() = runOnMainThreadSync { return@runOnMainThreadSync playerPointer.volume.toDouble() }
|
||||
set(value) = runOnMainThread { playerPointer.volume = value.toFloat() }
|
||||
set(value) = runOnMainThread {
|
||||
userVolume = value
|
||||
playerPointer.volume = value.toFloat()
|
||||
}
|
||||
|
||||
override val duration: Double
|
||||
get() {
|
||||
@@ -61,6 +68,36 @@ class HybridVideoPlayer() : HybridVideoPlayerSpec() {
|
||||
return if (duration == C.TIME_UNSET) Double.NaN else duration.toDouble() / 1000.0
|
||||
}
|
||||
|
||||
override var loop: Boolean
|
||||
get() {
|
||||
val repeatMode = runOnMainThreadSync { return@runOnMainThreadSync playerPointer.repeatMode }
|
||||
return repeatMode == Player.REPEAT_MODE_ONE
|
||||
}
|
||||
set(value) = runOnMainThread {
|
||||
playerPointer.repeatMode = if (value) Player.REPEAT_MODE_ONE else Player.REPEAT_MODE_OFF
|
||||
}
|
||||
|
||||
override var muted: Boolean
|
||||
get() = runOnMainThreadSync {
|
||||
val playerVolume = playerPointer.volume.toDouble()
|
||||
val isMuted = playerVolume == 0.0
|
||||
return@runOnMainThreadSync isMuted
|
||||
}
|
||||
set(value) = runOnMainThread {
|
||||
if (value) {
|
||||
userVolume = volume
|
||||
playerPointer.volume = 0f
|
||||
} else {
|
||||
playerPointer.volume = userVolume.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
override var rate: Double
|
||||
get() = runOnMainThreadSync { return@runOnMainThreadSync playerPointer.playbackParameters.speed.toDouble() }
|
||||
set(value) = runOnMainThread {
|
||||
playerPointer.playbackParameters = playerPointer.playbackParameters.withSpeed(value.toFloat())
|
||||
}
|
||||
|
||||
private fun initializePlayer() {
|
||||
if (NitroModules.applicationContext == null) {
|
||||
throw LibraryError.ApplicationContextNotFound
|
||||
@@ -101,6 +138,14 @@ class HybridVideoPlayer() : HybridVideoPlayerSpec() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun seekBy(time: Double) {
|
||||
currentTime = (currentTime + time).coerceIn(0.0, duration)
|
||||
}
|
||||
|
||||
override fun seekTo(time: Double) {
|
||||
currentTime = time.coerceIn(0.0, duration)
|
||||
}
|
||||
|
||||
override fun replaceSourceAsync(source: HybridVideoPlayerSourceSpec): Promise<Unit> {
|
||||
return Promise.async {
|
||||
val hybridSource = source as? HybridVideoPlayerSource ?: throw PlayerError.InvalidSource
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// VideoPlayerObserver.swift
|
||||
// ReactNativeVideo
|
||||
//
|
||||
// Created by Krzysztof Moch on 15/04/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
protocol VideoPlayerObserverDelegate: AnyObject {
|
||||
func onPlayedToEnd(player: AVPlayer)
|
||||
func onPlayerItemChange(player: AVPlayer, playerItem: AVPlayerItem?)
|
||||
}
|
||||
|
||||
extension VideoPlayerObserverDelegate {
|
||||
func onPlayedToEnd(player: AVPlayer) {}
|
||||
func onPlayerItemChange(player: AVPlayer, playerItem: AVPlayerItem?) {}
|
||||
}
|
||||
|
||||
class VideoPlayerObserver {
|
||||
private weak var delegate: HybridVideoPlayer?
|
||||
var player: AVPlayer? {
|
||||
delegate?.player
|
||||
}
|
||||
|
||||
// Player observers
|
||||
var playerCurrentItemObserver: NSKeyValueObservation?
|
||||
|
||||
// Player item observers
|
||||
var playbackEndedObserver: NSObjectProtocol?
|
||||
|
||||
init(delegate: HybridVideoPlayer) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
deinit {
|
||||
removeObservers()
|
||||
invalidatePlayerItemObservers()
|
||||
}
|
||||
|
||||
public func updatePlayerObservers() {
|
||||
invalidatePlayerItemObservers()
|
||||
removeObservers()
|
||||
|
||||
initializePlayerObservers()
|
||||
}
|
||||
|
||||
private func initializePlayerObservers() {
|
||||
guard let player else {
|
||||
return
|
||||
}
|
||||
|
||||
playerCurrentItemObserver = player.observe(\.currentItem, options: [.new, .old]) { [weak self] _, change in
|
||||
self?.onPlayerCurrentItemChanged(player: player, change: change)
|
||||
}
|
||||
}
|
||||
|
||||
private func initializePlayerItemObservers(player: AVPlayer, playerItem: AVPlayerItem) {
|
||||
playbackEndedObserver = NotificationCenter.default.addObserver(
|
||||
forName: .AVPlayerItemDidPlayToEndTime,
|
||||
object: playerItem,
|
||||
queue: nil
|
||||
) { [weak self] notification in
|
||||
self?.delegate?.onPlayedToEnd(player: player)
|
||||
}
|
||||
}
|
||||
|
||||
private func invalidatePlayerItemObservers() {
|
||||
NotificationCenter.default.removeObserver(playbackEndedObserver as Any)
|
||||
}
|
||||
|
||||
|
||||
private func removeObservers() {
|
||||
playerCurrentItemObserver?.invalidate()
|
||||
}
|
||||
|
||||
func onPlayerCurrentItemChanged(player: AVPlayer, change: NSKeyValueObservedChange<AVPlayerItem?>) {
|
||||
let newPlayerItem = change.newValue?.flatMap { $0 }
|
||||
|
||||
// Remove observers for old player item
|
||||
invalidatePlayerItemObservers()
|
||||
|
||||
if let playerItem = newPlayerItem {
|
||||
// Initialize observers for new player item
|
||||
initializePlayerItemObservers(player: player, playerItem: playerItem)
|
||||
|
||||
delegate?.onPlayerItemChange(player: player, playerItem: playerItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,15 @@ import Foundation
|
||||
import NitroModules
|
||||
import AVFoundation
|
||||
|
||||
class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
class HybridVideoPlayer: HybridVideoPlayerSpec, VideoPlayerObserverDelegate {
|
||||
/**
|
||||
* This in general should not be used directly, use `playerPointer` instead. This should be set only from within the playerQueue.
|
||||
*/
|
||||
private var player: AVPlayer?
|
||||
var player: AVPlayer? {
|
||||
didSet {
|
||||
playerObserver?.updatePlayerObservers()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The player queue is used to synchronize player initialization.
|
||||
@@ -35,9 +39,12 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
|
||||
do {
|
||||
let item = try initializePlayerItem()
|
||||
player?.replaceCurrentItem(with: item)
|
||||
playerItem = item
|
||||
player = AVPlayer(playerItem: playerItem)
|
||||
|
||||
// We need to intialize empty player first so player observers are set
|
||||
// before we set the player item
|
||||
player = AVPlayer()
|
||||
player?.replaceCurrentItem(with: item)
|
||||
} catch {
|
||||
playerItem = nil
|
||||
player = AVPlayer()
|
||||
@@ -54,6 +61,20 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
}
|
||||
|
||||
var playerItem: AVPlayerItem?
|
||||
var playerObserver: VideoPlayerObserver?
|
||||
|
||||
init(source: (any HybridVideoPlayerSourceSpec)) throws {
|
||||
self.source = source
|
||||
|
||||
super.init()
|
||||
self.playerObserver = VideoPlayerObserver(delegate: self)
|
||||
}
|
||||
|
||||
deinit {
|
||||
release()
|
||||
}
|
||||
|
||||
// MARK: - Hybrid Impl
|
||||
|
||||
var source: any HybridVideoPlayerSourceSpec
|
||||
|
||||
@@ -66,6 +87,15 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
}
|
||||
}
|
||||
|
||||
var muted: Bool {
|
||||
set {
|
||||
playerPointer.isMuted = newValue
|
||||
}
|
||||
get {
|
||||
return playerPointer.isMuted
|
||||
}
|
||||
}
|
||||
|
||||
var currentTime: Double {
|
||||
set {
|
||||
playerPointer.seek(
|
||||
@@ -83,14 +113,20 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
Double(playerPointer.currentItem?.duration.seconds ?? Double.nan)
|
||||
}
|
||||
|
||||
init(source: (any HybridVideoPlayerSourceSpec)) throws {
|
||||
self.source = source
|
||||
super.init()
|
||||
var rate: Double {
|
||||
set {
|
||||
if #available(iOS 16.0, tvOS 16.0, *) {
|
||||
playerPointer.defaultRate = Float(newValue)
|
||||
}
|
||||
|
||||
playerPointer.rate = Float(newValue)
|
||||
}
|
||||
get {
|
||||
return Double(playerPointer.rate)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
release()
|
||||
}
|
||||
var loop: Bool = false
|
||||
|
||||
func clean() throws {
|
||||
release()
|
||||
@@ -106,6 +142,9 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
if let source = self.source as? HybridVideoPlayerSource {
|
||||
source.releaseAsset()
|
||||
}
|
||||
|
||||
// Clear player observer
|
||||
self.playerObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +169,26 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
playerPointer.pause()
|
||||
}
|
||||
|
||||
func seekBy(time: Double) throws {
|
||||
guard let currentItem = playerPointer.currentItem else {
|
||||
throw PlayerError.notIntilaized.error()
|
||||
}
|
||||
|
||||
let currentItemTime = currentItem.currentTime()
|
||||
|
||||
// Duration is NaN for live streams
|
||||
let fixedDurration = duration.isNaN ? Double.infinity : duration
|
||||
|
||||
// Clap by <0, duration>
|
||||
let newTime = max(0, min(currentItemTime.seconds + time, fixedDurration))
|
||||
|
||||
currentTime = newTime
|
||||
}
|
||||
|
||||
func seekTo(time: Double) {
|
||||
currentTime = time
|
||||
}
|
||||
|
||||
func replaceSourceAsync(source: (any HybridVideoPlayerSourceSpec)) throws -> Promise<Void> {
|
||||
return Promise.parallel { [weak self] in
|
||||
guard let self else {
|
||||
@@ -156,6 +215,8 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Internal Methods
|
||||
|
||||
private func initializePlayerItem() throws -> AVPlayerItem {
|
||||
guard let _source = source as? HybridVideoPlayerSource else {
|
||||
throw PlayerError.invalidSource.error()
|
||||
@@ -170,6 +231,19 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
return AVPlayerItem(asset: asset)
|
||||
}
|
||||
|
||||
// MARK: - VideoPlayerObserverDelegate
|
||||
|
||||
func onPlayedToEnd(player: AVPlayer) {
|
||||
// TODO: Notify listeners once we implement callbacks
|
||||
|
||||
if loop {
|
||||
currentTime = 0
|
||||
try? play()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Memory Management
|
||||
|
||||
var memorySize: Int {
|
||||
var size = 0
|
||||
|
||||
|
||||
+35
@@ -63,6 +63,33 @@ namespace margelo::nitro::video {
|
||||
static const auto method = javaClassStatic()->getMethod<void(double /* currentTime */)>("setCurrentTime");
|
||||
method(_javaPart, currentTime);
|
||||
}
|
||||
bool JHybridVideoPlayerSpec::getMuted() {
|
||||
static const auto method = javaClassStatic()->getMethod<jboolean()>("getMuted");
|
||||
auto __result = method(_javaPart);
|
||||
return static_cast<bool>(__result);
|
||||
}
|
||||
void JHybridVideoPlayerSpec::setMuted(bool muted) {
|
||||
static const auto method = javaClassStatic()->getMethod<void(jboolean /* muted */)>("setMuted");
|
||||
method(_javaPart, muted);
|
||||
}
|
||||
bool JHybridVideoPlayerSpec::getLoop() {
|
||||
static const auto method = javaClassStatic()->getMethod<jboolean()>("getLoop");
|
||||
auto __result = method(_javaPart);
|
||||
return static_cast<bool>(__result);
|
||||
}
|
||||
void JHybridVideoPlayerSpec::setLoop(bool loop) {
|
||||
static const auto method = javaClassStatic()->getMethod<void(jboolean /* loop */)>("setLoop");
|
||||
method(_javaPart, loop);
|
||||
}
|
||||
double JHybridVideoPlayerSpec::getRate() {
|
||||
static const auto method = javaClassStatic()->getMethod<double()>("getRate");
|
||||
auto __result = method(_javaPart);
|
||||
return __result;
|
||||
}
|
||||
void JHybridVideoPlayerSpec::setRate(double rate) {
|
||||
static const auto method = javaClassStatic()->getMethod<void(double /* rate */)>("setRate");
|
||||
method(_javaPart, rate);
|
||||
}
|
||||
|
||||
// Methods
|
||||
std::shared_ptr<Promise<void>> JHybridVideoPlayerSpec::replaceSourceAsync(const std::shared_ptr<margelo::nitro::video::HybridVideoPlayerSourceSpec>& source) {
|
||||
@@ -107,5 +134,13 @@ namespace margelo::nitro::video {
|
||||
static const auto method = javaClassStatic()->getMethod<void()>("pause");
|
||||
method(_javaPart);
|
||||
}
|
||||
void JHybridVideoPlayerSpec::seekBy(double time) {
|
||||
static const auto method = javaClassStatic()->getMethod<void(double /* time */)>("seekBy");
|
||||
method(_javaPart, time);
|
||||
}
|
||||
void JHybridVideoPlayerSpec::seekTo(double time) {
|
||||
static const auto method = javaClassStatic()->getMethod<void(double /* time */)>("seekTo");
|
||||
method(_javaPart, time);
|
||||
}
|
||||
|
||||
} // namespace margelo::nitro::video
|
||||
|
||||
+8
@@ -53,6 +53,12 @@ namespace margelo::nitro::video {
|
||||
void setVolume(double volume) override;
|
||||
double getCurrentTime() override;
|
||||
void setCurrentTime(double currentTime) override;
|
||||
bool getMuted() override;
|
||||
void setMuted(bool muted) override;
|
||||
bool getLoop() override;
|
||||
void setLoop(bool loop) override;
|
||||
double getRate() override;
|
||||
void setRate(double rate) override;
|
||||
|
||||
public:
|
||||
// Methods
|
||||
@@ -61,6 +67,8 @@ namespace margelo::nitro::video {
|
||||
std::shared_ptr<Promise<void>> preload() override;
|
||||
void play() override;
|
||||
void pause() override;
|
||||
void seekBy(double time) override;
|
||||
void seekTo(double time) override;
|
||||
|
||||
private:
|
||||
friend HybridBase;
|
||||
|
||||
+26
@@ -56,6 +56,24 @@ abstract class HybridVideoPlayerSpec: HybridObject() {
|
||||
@set:DoNotStrip
|
||||
@set:Keep
|
||||
abstract var currentTime: Double
|
||||
|
||||
@get:DoNotStrip
|
||||
@get:Keep
|
||||
@set:DoNotStrip
|
||||
@set:Keep
|
||||
abstract var muted: Boolean
|
||||
|
||||
@get:DoNotStrip
|
||||
@get:Keep
|
||||
@set:DoNotStrip
|
||||
@set:Keep
|
||||
abstract var loop: Boolean
|
||||
|
||||
@get:DoNotStrip
|
||||
@get:Keep
|
||||
@set:DoNotStrip
|
||||
@set:Keep
|
||||
abstract var rate: Double
|
||||
|
||||
// Methods
|
||||
@DoNotStrip
|
||||
@@ -77,6 +95,14 @@ abstract class HybridVideoPlayerSpec: HybridObject() {
|
||||
@DoNotStrip
|
||||
@Keep
|
||||
abstract fun pause(): Unit
|
||||
|
||||
@DoNotStrip
|
||||
@Keep
|
||||
abstract fun seekBy(time: Double): Unit
|
||||
|
||||
@DoNotStrip
|
||||
@Keep
|
||||
abstract fun seekTo(time: Double): Unit
|
||||
|
||||
private external fun initHybrid(): HybridData
|
||||
|
||||
|
||||
+30
@@ -73,6 +73,24 @@ namespace margelo::nitro::video {
|
||||
inline void setCurrentTime(double currentTime) noexcept override {
|
||||
_swiftPart.setCurrentTime(std::forward<decltype(currentTime)>(currentTime));
|
||||
}
|
||||
inline bool getMuted() noexcept override {
|
||||
return _swiftPart.getMuted();
|
||||
}
|
||||
inline void setMuted(bool muted) noexcept override {
|
||||
_swiftPart.setMuted(std::forward<decltype(muted)>(muted));
|
||||
}
|
||||
inline bool getLoop() noexcept override {
|
||||
return _swiftPart.getLoop();
|
||||
}
|
||||
inline void setLoop(bool loop) noexcept override {
|
||||
_swiftPart.setLoop(std::forward<decltype(loop)>(loop));
|
||||
}
|
||||
inline double getRate() noexcept override {
|
||||
return _swiftPart.getRate();
|
||||
}
|
||||
inline void setRate(double rate) noexcept override {
|
||||
_swiftPart.setRate(std::forward<decltype(rate)>(rate));
|
||||
}
|
||||
|
||||
public:
|
||||
// Methods
|
||||
@@ -110,6 +128,18 @@ namespace margelo::nitro::video {
|
||||
std::rethrow_exception(__result.error());
|
||||
}
|
||||
}
|
||||
inline void seekBy(double time) override {
|
||||
auto __result = _swiftPart.seekBy(std::forward<decltype(time)>(time));
|
||||
if (__result.hasError()) [[unlikely]] {
|
||||
std::rethrow_exception(__result.error());
|
||||
}
|
||||
}
|
||||
inline void seekTo(double time) override {
|
||||
auto __result = _swiftPart.seekTo(std::forward<decltype(time)>(time));
|
||||
if (__result.hasError()) [[unlikely]] {
|
||||
std::rethrow_exception(__result.error());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ReactNativeVideo::HybridVideoPlayerSpec_cxx _swiftPart;
|
||||
|
||||
+5
@@ -15,6 +15,9 @@ public protocol HybridVideoPlayerSpec_protocol: HybridObject {
|
||||
var duration: Double { get }
|
||||
var volume: Double { get set }
|
||||
var currentTime: Double { get set }
|
||||
var muted: Bool { get set }
|
||||
var loop: Bool { get set }
|
||||
var rate: Double { get set }
|
||||
|
||||
// Methods
|
||||
func replaceSourceAsync(source: (any HybridVideoPlayerSourceSpec)) throws -> Promise<Void>
|
||||
@@ -22,6 +25,8 @@ public protocol HybridVideoPlayerSpec_protocol: HybridObject {
|
||||
func preload() throws -> Promise<Void>
|
||||
func play() throws -> Void
|
||||
func pause() throws -> Void
|
||||
func seekBy(time: Double) throws -> Void
|
||||
func seekTo(time: Double) throws -> Void
|
||||
}
|
||||
|
||||
/// See ``HybridVideoPlayerSpec``
|
||||
|
||||
+55
@@ -135,6 +135,39 @@ public class HybridVideoPlayerSpec_cxx {
|
||||
self.__implementation.currentTime = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public final var muted: Bool {
|
||||
@inline(__always)
|
||||
get {
|
||||
return self.__implementation.muted
|
||||
}
|
||||
@inline(__always)
|
||||
set {
|
||||
self.__implementation.muted = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public final var loop: Bool {
|
||||
@inline(__always)
|
||||
get {
|
||||
return self.__implementation.loop
|
||||
}
|
||||
@inline(__always)
|
||||
set {
|
||||
self.__implementation.loop = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public final var rate: Double {
|
||||
@inline(__always)
|
||||
get {
|
||||
return self.__implementation.rate
|
||||
}
|
||||
@inline(__always)
|
||||
set {
|
||||
self.__implementation.rate = newValue
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
@inline(__always)
|
||||
@@ -211,4 +244,26 @@ public class HybridVideoPlayerSpec_cxx {
|
||||
return bridge.create_Result_void_(__exceptionPtr)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public final func seekBy(time: Double) -> bridge.Result_void_ {
|
||||
do {
|
||||
try self.__implementation.seekBy(time: time)
|
||||
return bridge.create_Result_void_()
|
||||
} catch (let __error) {
|
||||
let __exceptionPtr = __error.toCpp()
|
||||
return bridge.create_Result_void_(__exceptionPtr)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
public final func seekTo(time: Double) -> bridge.Result_void_ {
|
||||
do {
|
||||
try self.__implementation.seekTo(time: time)
|
||||
return bridge.create_Result_void_()
|
||||
} catch (let __error) {
|
||||
let __exceptionPtr = __error.toCpp()
|
||||
return bridge.create_Result_void_(__exceptionPtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -20,15 +20,24 @@ namespace margelo::nitro::video {
|
||||
prototype.registerHybridSetter("volume", &HybridVideoPlayerSpec::setVolume);
|
||||
prototype.registerHybridGetter("currentTime", &HybridVideoPlayerSpec::getCurrentTime);
|
||||
prototype.registerHybridSetter("currentTime", &HybridVideoPlayerSpec::setCurrentTime);
|
||||
prototype.registerHybridGetter("muted", &HybridVideoPlayerSpec::getMuted);
|
||||
prototype.registerHybridSetter("muted", &HybridVideoPlayerSpec::setMuted);
|
||||
prototype.registerHybridGetter("loop", &HybridVideoPlayerSpec::getLoop);
|
||||
prototype.registerHybridSetter("loop", &HybridVideoPlayerSpec::setLoop);
|
||||
prototype.registerHybridGetter("rate", &HybridVideoPlayerSpec::getRate);
|
||||
prototype.registerHybridSetter("rate", &HybridVideoPlayerSpec::setRate);
|
||||
prototype.registerHybridMethod("replaceSourceAsync", &HybridVideoPlayerSpec::replaceSourceAsync);
|
||||
prototype.registerHybridMethod("clean", &HybridVideoPlayerSpec::clean);
|
||||
prototype.registerHybridMethod("preload", &HybridVideoPlayerSpec::preload);
|
||||
prototype.registerHybridMethod("play", &HybridVideoPlayerSpec::play);
|
||||
prototype.registerHybridMethod("pause", &HybridVideoPlayerSpec::pause);
|
||||
prototype.registerHybridMethod("seekBy", &HybridVideoPlayerSpec::seekBy);
|
||||
prototype.registerHybridMethod("seekTo", &HybridVideoPlayerSpec::seekTo);
|
||||
});
|
||||
}
|
||||
|
||||
void HybridVideoPlayerSpec::dispose() {
|
||||
clean();
|
||||
HybridObject::dispose();
|
||||
}
|
||||
} // namespace margelo::nitro::video
|
||||
|
||||
+8
@@ -53,6 +53,12 @@ namespace margelo::nitro::video {
|
||||
virtual void setVolume(double volume) = 0;
|
||||
virtual double getCurrentTime() = 0;
|
||||
virtual void setCurrentTime(double currentTime) = 0;
|
||||
virtual bool getMuted() = 0;
|
||||
virtual void setMuted(bool muted) = 0;
|
||||
virtual bool getLoop() = 0;
|
||||
virtual void setLoop(bool loop) = 0;
|
||||
virtual double getRate() = 0;
|
||||
virtual void setRate(double rate) = 0;
|
||||
|
||||
public:
|
||||
// Methods
|
||||
@@ -61,6 +67,8 @@ namespace margelo::nitro::video {
|
||||
virtual std::shared_ptr<Promise<void>> preload() = 0;
|
||||
virtual void play() = 0;
|
||||
virtual void pause() = 0;
|
||||
virtual void seekBy(double time) = 0;
|
||||
virtual void seekTo(double time) = 0;
|
||||
|
||||
protected:
|
||||
// Hybrid Setup
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NitroModules } from 'react-native-nitro-modules';
|
||||
import { type VideoPlayer as VideoPlayerImpl } from '../spec/nitro/VideoPlayer.nitro';
|
||||
import type { VideoPlayerSource } from '../spec/nitro/VideoPlayerSource.nitro';
|
||||
import type { VideoConfig, VideoSource } from './types/VideoConfig';
|
||||
@@ -61,18 +62,23 @@ class VideoPlayer implements VideoPlayerBase {
|
||||
*/
|
||||
private wrapPromise<T>(promise: Promise<T>) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
promise.then(resolve).catch(reject);
|
||||
promise.then(resolve).catch((error) => {
|
||||
reject(this.throwError(error));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Source
|
||||
get source(): VideoPlayerSource {
|
||||
return this.player.source;
|
||||
}
|
||||
|
||||
// Duration
|
||||
get duration(): number {
|
||||
return this.player.duration;
|
||||
}
|
||||
|
||||
// Volume
|
||||
get volume(): number {
|
||||
return this.player.volume;
|
||||
}
|
||||
@@ -81,6 +87,7 @@ class VideoPlayer implements VideoPlayerBase {
|
||||
this.player.volume = value;
|
||||
}
|
||||
|
||||
// Current Time
|
||||
get currentTime(): number {
|
||||
return this.player.currentTime;
|
||||
}
|
||||
@@ -89,8 +96,37 @@ class VideoPlayer implements VideoPlayerBase {
|
||||
this.player.currentTime = value;
|
||||
}
|
||||
|
||||
preload(): Promise<void> {
|
||||
return this.wrapPromise(this.player.preload());
|
||||
// Muted
|
||||
get muted(): boolean {
|
||||
return this.player.muted;
|
||||
}
|
||||
|
||||
set muted(value: boolean) {
|
||||
this.player.muted = value;
|
||||
}
|
||||
|
||||
// Loop
|
||||
get loop(): boolean {
|
||||
return this.player.loop;
|
||||
}
|
||||
|
||||
set loop(value: boolean) {
|
||||
this.player.loop = value;
|
||||
}
|
||||
|
||||
// Rate
|
||||
get rate(): number {
|
||||
return this.player.rate;
|
||||
}
|
||||
|
||||
set rate(value: number) {
|
||||
this.player.rate = value;
|
||||
}
|
||||
|
||||
async preload(): Promise<void> {
|
||||
await this.wrapPromise(this.player.preload());
|
||||
|
||||
NitroModules.updateMemorySize(this.player);
|
||||
}
|
||||
|
||||
play(): void {
|
||||
@@ -109,12 +145,30 @@ class VideoPlayer implements VideoPlayerBase {
|
||||
}
|
||||
}
|
||||
|
||||
replaceSourceAsync(
|
||||
seekBy(time: number): void {
|
||||
try {
|
||||
this.player.seekBy(time);
|
||||
} catch (error) {
|
||||
this.throwError(error);
|
||||
}
|
||||
}
|
||||
|
||||
seekTo(time: number): void {
|
||||
try {
|
||||
this.player.seekTo(time);
|
||||
} catch (error) {
|
||||
this.throwError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async replaceSourceAsync(
|
||||
source: VideoSource | VideoConfig | VideoPlayerSource
|
||||
): Promise<void> {
|
||||
return this.wrapPromise(
|
||||
await this.wrapPromise(
|
||||
this.player.replaceSourceAsync(createSource(source))
|
||||
);
|
||||
|
||||
NitroModules.updateMemorySize(this.player);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface VideoPlayerBase {
|
||||
|
||||
/**
|
||||
* The volume of the video (0.0 = 0%, 1.0 = 100%).
|
||||
* @note If the player is {@link muted}, the volume will be 0.0.
|
||||
*/
|
||||
volume: number;
|
||||
|
||||
@@ -25,6 +26,22 @@ export interface VideoPlayerBase {
|
||||
*/
|
||||
currentTime: number;
|
||||
|
||||
/**
|
||||
* Whether the player is muted.
|
||||
*/
|
||||
muted: boolean;
|
||||
|
||||
/**
|
||||
* Whether the player is looped.
|
||||
*/
|
||||
loop: boolean;
|
||||
|
||||
/**
|
||||
* Controls the speed at which the player should play.
|
||||
* @note if rate is = 0, it will pause video.
|
||||
*/
|
||||
rate: number;
|
||||
|
||||
/**
|
||||
* Preload the video.
|
||||
* This is useful to avoid delay when the user plays the video.
|
||||
@@ -42,6 +59,22 @@ export interface VideoPlayerBase {
|
||||
*/
|
||||
pause(): void;
|
||||
|
||||
/**
|
||||
* Seek by given time.
|
||||
* If the time is negative, it will seek backward.
|
||||
* time will be clamped if it is out of range (0 ~ {@link duration}).
|
||||
* @param time - The time to seek from current time in seconds.
|
||||
*/
|
||||
seekBy(time: number): void;
|
||||
|
||||
/**
|
||||
* Seek to a specific time in the video.
|
||||
* @param time - The time to seek to in seconds.
|
||||
* @note This have same effect as {@link currentTime} setter.
|
||||
* @note time will be clamped if it is out of range (0 ~ {@link duration}).
|
||||
*/
|
||||
seekTo(time: number): void;
|
||||
|
||||
/**
|
||||
* Replace the current source of the player.
|
||||
* @param source - The new source of the video.
|
||||
|
||||
Reference in New Issue
Block a user