mirror of
https://github.com/zoriya/react-native-video.git
synced 2026-05-30 09:51:13 +00:00
feat: refactor player initialization
This commit is contained in:
@@ -18,7 +18,7 @@ class VideoView @JvmOverloads constructor(
|
||||
set(value) {
|
||||
// Clear the SurfaceView when player is about to be set to null
|
||||
if (value == null && field != null) {
|
||||
val player = field?.player
|
||||
val player = field?.playerPointer
|
||||
player?.clearVideoSurfaceView(surfaceView)
|
||||
player?.setVideoSurfaceView(null)
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class VideoView @JvmOverloads constructor(
|
||||
|
||||
// Set the SurfaceView to the player when it's available
|
||||
surfaceView?.let {
|
||||
field?.player?.setVideoSurfaceView(it)
|
||||
field?.playerPointer?.setVideoSurfaceView(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ class VideoView @JvmOverloads constructor(
|
||||
|
||||
// -------- View Lifecycle Methods --------
|
||||
override fun onDetachedFromWindow() {
|
||||
hybridPlayer?.player?.clearVideoSurfaceView(surfaceView)
|
||||
hybridPlayer?.playerPointer?.clearVideoSurfaceView(surfaceView)
|
||||
super.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
hybridPlayer?.player?.setVideoSurfaceView(surfaceView)
|
||||
hybridPlayer?.playerPointer?.setVideoSurfaceView(surfaceView)
|
||||
super.onAttachedToWindow()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,74 +9,128 @@ import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.upstream.DefaultAllocator
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
import com.margelo.nitro.NitroModules
|
||||
import com.margelo.nitro.core.Promise
|
||||
import com.video.utils.Threading.Companion.runOnMainThreadSync
|
||||
import com.video.utils.Threading.Companion.runOnMainThread
|
||||
|
||||
@UnstableApi
|
||||
@DoNotStrip
|
||||
class HybridVideoPlayer() : HybridVideoPlayerSpec() {
|
||||
lateinit var player: Player
|
||||
private lateinit var allocator: DefaultAllocator
|
||||
override lateinit var source: HybridVideoPlayerSourceSpec
|
||||
private var allocator: DefaultAllocator? = null
|
||||
|
||||
private var player: Player? = null
|
||||
|
||||
var playerPointer: Player
|
||||
get() {
|
||||
if (player == null) {
|
||||
runOnMainThreadSync {
|
||||
initializePlayer()
|
||||
|
||||
if (player == null) {
|
||||
throw Exception("Could not initialize player!")
|
||||
}
|
||||
|
||||
if (player!!.playbackState == Player.STATE_IDLE) {
|
||||
player!!.prepare()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return player!!
|
||||
}
|
||||
private set(value) {
|
||||
player = value
|
||||
}
|
||||
|
||||
// Player Properties
|
||||
override var currentTime: Double
|
||||
get() = runOnMainThreadSync { return@runOnMainThreadSync player.currentPosition.toDouble() }
|
||||
set(value) = runOnMainThread { player.seekTo(value.toLong()) }
|
||||
get() = runOnMainThreadSync { return@runOnMainThreadSync playerPointer.currentPosition.toDouble() / 1000.0 }
|
||||
set(value) = runOnMainThread { playerPointer.seekTo((value * 1000).toLong()) }
|
||||
|
||||
override var volume: Double
|
||||
get() = runOnMainThreadSync { return@runOnMainThreadSync player.volume.toDouble() }
|
||||
set(value) = runOnMainThread { player.volume = value.toFloat() }
|
||||
get() = runOnMainThreadSync { return@runOnMainThreadSync playerPointer.volume.toDouble() }
|
||||
set(value) = runOnMainThread { playerPointer.volume = value.toFloat() }
|
||||
|
||||
override val duration: Double
|
||||
get() = runOnMainThreadSync { return@runOnMainThreadSync player.duration.toDouble() }
|
||||
|
||||
private fun initializePlayerFromSource(source: HybridVideoPlayerSource) {
|
||||
this.source = source
|
||||
get() {
|
||||
val duration = runOnMainThreadSync { return@runOnMainThreadSync playerPointer.duration }
|
||||
return if (duration == C.TIME_UNSET) Double.NaN else duration.toDouble() / 1000.0
|
||||
}
|
||||
|
||||
private fun initializePlayer() {
|
||||
if (NitroModules.applicationContext == null) {
|
||||
throw Exception("HybridVideoPlayer: Application Context is null!")
|
||||
}
|
||||
|
||||
val hybridSource = source as? HybridVideoPlayerSource ?: throw Exception("Invalid source type")
|
||||
|
||||
// Initialize the allocator
|
||||
allocator = DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)
|
||||
|
||||
// Create a LoadControl with the allocator
|
||||
val loadControl = DefaultLoadControl.Builder()
|
||||
.setAllocator(allocator)
|
||||
.setAllocator(allocator!!)
|
||||
.build()
|
||||
|
||||
// Build the player with the LoadControl
|
||||
player = ExoPlayer.Builder(NitroModules.applicationContext!!)
|
||||
playerPointer = ExoPlayer.Builder(NitroModules.applicationContext!!)
|
||||
.setLoadControl(loadControl)
|
||||
.setLooper(Looper.getMainLooper())
|
||||
.build()
|
||||
|
||||
player.setMediaItem(source.mediaItem)
|
||||
|
||||
// TODO: Move this to single method to allow more control
|
||||
player.prepare()
|
||||
playerPointer.setMediaItem(hybridSource.mediaItem)
|
||||
}
|
||||
|
||||
constructor(source: HybridVideoPlayerSource) : this() {
|
||||
runOnMainThreadSync {
|
||||
initializePlayerFromSource(source)
|
||||
}
|
||||
this.source = source
|
||||
}
|
||||
|
||||
override fun play() {
|
||||
runOnMainThread {
|
||||
player.play()
|
||||
playerPointer.play()
|
||||
}
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
runOnMainThread {
|
||||
player.pause()
|
||||
playerPointer.pause()
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceSourceAsync(source: HybridVideoPlayerSourceSpec): Promise<Unit> {
|
||||
return Promise.async {
|
||||
val hybridSource = source as? HybridVideoPlayerSource ?: throw Exception("Invalid source type")
|
||||
|
||||
runOnMainThreadSync {
|
||||
// Update source
|
||||
this.source = source
|
||||
playerPointer.setMediaItem(hybridSource.mediaItem)
|
||||
|
||||
// Prepare player
|
||||
playerPointer.prepare()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun preload(): Promise<Unit> {
|
||||
return Promise.async {
|
||||
runOnMainThreadSync {
|
||||
if (playerPointer.playbackState != Player.STATE_IDLE) {
|
||||
return@runOnMainThreadSync
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
initializePlayer()
|
||||
}
|
||||
|
||||
player?.prepare()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updated memorySize property
|
||||
override val memorySize: Long
|
||||
get() = allocator.totalBytesAllocated.toLong()
|
||||
// If player is null, allocator is not ready yet
|
||||
get() = if (allocator == null) 0 else allocator!!.totalBytesAllocated.toLong()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.facebook.proguard.annotations.DoNotStrip
|
||||
|
||||
@DoNotStrip
|
||||
class HybridVideoPlayerFactory(): HybridVideoPlayerFactorySpec() {
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun createPlayer(source: HybridVideoPlayerSourceSpec): HybridVideoPlayerSpec {
|
||||
return HybridVideoPlayer(source as HybridVideoPlayerSource)
|
||||
|
||||
@@ -10,59 +10,142 @@ import NitroModules
|
||||
import AVFoundation
|
||||
|
||||
class HybridVideoPlayer: HybridVideoPlayerSpec {
|
||||
var player: AVPlayer
|
||||
/**
|
||||
* This in general should not be used directly, use `playerPointer` instead. This should be set only from within the playerQueue.
|
||||
*/
|
||||
private var _player: AVPlayer?
|
||||
|
||||
/**
|
||||
* The player queue is used to synchronize player initialization.
|
||||
*/
|
||||
private let playerQueue = DispatchQueue(label: "com.nitro.hybridplayer")
|
||||
|
||||
/**
|
||||
* This is the actual player that should be used for playback. It is initialized lazily when `playerPointer` is accessed.
|
||||
*/
|
||||
var playerPointer: AVPlayer {
|
||||
get {
|
||||
// Synchronize access to player initialization
|
||||
playerQueue.sync {
|
||||
// If player is already initialized and playerItem is set, return it
|
||||
// If playerItem is not set, it means that player was not loaded with any source
|
||||
if _player != nil && playerItem != nil {
|
||||
return _player!
|
||||
}
|
||||
|
||||
do {
|
||||
let item = try initializePlayerItem()
|
||||
_player?.replaceCurrentItem(with: item)
|
||||
playerItem = item
|
||||
_player = AVPlayer(playerItem: playerItem)
|
||||
} catch {
|
||||
playerItem = nil
|
||||
_player = AVPlayer()
|
||||
}
|
||||
|
||||
return _player!
|
||||
}
|
||||
}
|
||||
set {
|
||||
playerQueue.sync {
|
||||
_player = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var playerItem: AVPlayerItem?
|
||||
|
||||
var source: any HybridVideoPlayerSourceSpec
|
||||
|
||||
var volume: Double {
|
||||
set {
|
||||
player.volume = Float(newValue)
|
||||
playerPointer.volume = Float(newValue)
|
||||
}
|
||||
get {
|
||||
return Double(player.volume)
|
||||
return Double(playerPointer.volume)
|
||||
}
|
||||
}
|
||||
|
||||
var currentTime: Double {
|
||||
set {
|
||||
player.seek(
|
||||
playerPointer.seek(
|
||||
to: CMTime(seconds: newValue, preferredTimescale: 1000),
|
||||
toleranceBefore: .zero,
|
||||
toleranceAfter: .zero
|
||||
)
|
||||
}
|
||||
get {
|
||||
player.currentTime().seconds
|
||||
playerPointer.currentTime().seconds
|
||||
}
|
||||
}
|
||||
|
||||
var duration: Double {
|
||||
Double(player.currentItem?.duration.seconds ?? Double.nan)
|
||||
Double(playerPointer.currentItem?.duration.seconds ?? Double.nan)
|
||||
}
|
||||
|
||||
init(source: HybridVideoPlayerSourceSpec) throws {
|
||||
init(source: (any HybridVideoPlayerSourceSpec)) throws {
|
||||
self.source = source
|
||||
|
||||
guard let url = URL(string: source.uri) else {
|
||||
throw RuntimeError.error(withMessage: "Invalid URL: \(source.uri)")
|
||||
super.init()
|
||||
}
|
||||
|
||||
func preload() throws -> NitroModules.Promise<Void> {
|
||||
return Promise.parallel { [weak self] in
|
||||
guard let self else {
|
||||
throw RuntimeError.error(withMessage: "HybridVideoPlayer has been deallocated")
|
||||
}
|
||||
|
||||
try self.playerQueue.sync {
|
||||
self.playerItem = try self.initializePlayerItem()
|
||||
self._player = AVPlayer(playerItem: self.playerItem)
|
||||
}
|
||||
}
|
||||
|
||||
player = AVPlayer(url: url)
|
||||
}
|
||||
|
||||
func play() throws {
|
||||
player.play()
|
||||
playerPointer.play()
|
||||
}
|
||||
|
||||
func pause() throws {
|
||||
player.pause()
|
||||
playerPointer.pause()
|
||||
}
|
||||
|
||||
// Initialize HybridContext
|
||||
var hybridContext = margelo.nitro.HybridContext()
|
||||
func replaceSourceAsync(source: (any HybridVideoPlayerSourceSpec)) throws -> Promise<Void> {
|
||||
return Promise.parallel { [weak self] in
|
||||
guard let self else {
|
||||
throw RuntimeError.error(withMessage: "HybridVideoPlayer has been deallocated")
|
||||
}
|
||||
|
||||
try playerQueue.sync {
|
||||
self.source = source
|
||||
|
||||
do {
|
||||
self.playerItem = try self.initializePlayerItem()
|
||||
|
||||
guard let player = self._player else {
|
||||
throw RuntimeError.error(withMessage: "Player not initialized")
|
||||
}
|
||||
|
||||
player.replaceCurrentItem(with: self.playerItem)
|
||||
} catch {
|
||||
self.playerItem = nil
|
||||
self._player = AVPlayer()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return size of the instance to inform JS GC about memory pressure
|
||||
var memorySize: Int {
|
||||
return getSizeOf(self)
|
||||
private func initializePlayerItem() throws -> AVPlayerItem {
|
||||
guard let _source = source as? HybridVideoPlayerSource else {
|
||||
throw RuntimeError.error(withMessage: "Invalid source")
|
||||
}
|
||||
|
||||
try _source.initializeAsset()
|
||||
|
||||
guard let asset = _source.asset else {
|
||||
throw RuntimeError.error(withMessage: "Failed to initialize asset")
|
||||
}
|
||||
|
||||
return AVPlayerItem(asset: asset)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,4 @@ class HybridVideoPlayerFactory: HybridVideoPlayerFactorySpec {
|
||||
func createPlayer(source: HybridVideoPlayerSourceSpec) throws -> HybridVideoPlayerSpec {
|
||||
return try HybridVideoPlayer(source: source)
|
||||
}
|
||||
|
||||
// Initialize HybridContext
|
||||
var hybridContext = margelo.nitro.HybridContext()
|
||||
|
||||
// Return size of the instance to inform JS GC about memory pressure
|
||||
var memorySize: Int {
|
||||
return getSizeOf(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
class HybridVideoPlayerSourceFactory: HybridVideoPlayerSourceFactorySpec {
|
||||
func fromUri(uri: String) -> HybridVideoPlayerSourceSpec {
|
||||
return HybridVideoPlayerSource(uri: uri)
|
||||
}
|
||||
|
||||
|
||||
// Initialize HybridContext
|
||||
var hybridContext = margelo.nitro.HybridContext()
|
||||
|
||||
// Return size of the instance to inform JS GC about memory pressure
|
||||
var memorySize: Int {
|
||||
return getSizeOf(self)
|
||||
func fromUri(uri: String) throws -> HybridVideoPlayerSourceSpec {
|
||||
return try HybridVideoPlayerSource(uri: uri)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,4 @@ class HybridVideoViewViewManager: HybridVideoViewViewManagerSpec {
|
||||
|
||||
self.view = view
|
||||
}
|
||||
|
||||
// Initialize HybridContext
|
||||
var hybridContext = margelo.nitro.HybridContext()
|
||||
|
||||
// Return size of the instance to inform JS GC about memory pressure
|
||||
var memorySize: Int {
|
||||
return getSizeOf(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,4 @@ class HybridVideoViewViewManagerFactory: HybridVideoViewViewManagerFactorySpec {
|
||||
func createViewManager(nitroId: Double) throws -> any HybridVideoViewViewManagerSpec {
|
||||
return try HybridVideoViewViewManager(nitroId: nitroId)
|
||||
}
|
||||
|
||||
// Initialize HybridContext
|
||||
var hybridContext = margelo.nitro.HybridContext()
|
||||
|
||||
// Return size of the instance to inform JS GC about memory pressure
|
||||
var memorySize: Int {
|
||||
return getSizeOf(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,14 @@ import AVFoundation
|
||||
public var player: HybridVideoPlayerSpec? = nil {
|
||||
didSet {
|
||||
guard let player = player as? HybridVideoPlayer else { return }
|
||||
configureAVPlayerLayer(with: player.player)
|
||||
configureAVPlayerLayer(with: player.playerPointer)
|
||||
}
|
||||
}
|
||||
private var playerView: UIView? = nil
|
||||
private var avPlayer: AVPlayer?
|
||||
private var avPlayerLayer: AVPlayerLayer?
|
||||
|
||||
@objc public var nitroId: NSNumber = -1 {
|
||||
didSet {
|
||||
let test = nitroId
|
||||
VideoComponentView.globalViewsMap.setObject(self, forKey: nitroId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,13 @@ export interface VideoPlayer
|
||||
* Changing the source will reload player.
|
||||
* see {@link VideoPlayerSource}
|
||||
*/
|
||||
source: VideoPlayerSource;
|
||||
readonly source: VideoPlayerSource;
|
||||
|
||||
/**
|
||||
* The current time of the video in seconds (1.0 = 1 sec).
|
||||
* Returns NaN if the current time is not available.
|
||||
*/
|
||||
readonly duration: number;
|
||||
|
||||
/**
|
||||
* The volume of the video (0.0 = 0%, 1.0 = 100%).
|
||||
@@ -22,10 +28,11 @@ export interface VideoPlayer
|
||||
currentTime: number;
|
||||
|
||||
/**
|
||||
* The current time of the video in seconds (1.0 = 1 sec).
|
||||
* Returns NaN if the current time is not available.
|
||||
* Preload the video.
|
||||
* This is useful to avoid delay when the user plays the video.
|
||||
* Preloading too many videos can lead to memory issues or performance issues.
|
||||
*/
|
||||
readonly duration: number;
|
||||
preload(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Start playback of player.
|
||||
@@ -36,6 +43,13 @@ export interface VideoPlayer
|
||||
* Pause playback of player.
|
||||
*/
|
||||
pause(): void;
|
||||
|
||||
/**
|
||||
* Replace the current source of the player.
|
||||
* @param source - The new source of the video.
|
||||
* see {@link VideoPlayerSource}
|
||||
*/
|
||||
replaceSourceAsync(source: VideoPlayerSource): Promise<void>;
|
||||
}
|
||||
|
||||
export interface VideoPlayerFactory
|
||||
|
||||
Reference in New Issue
Block a user