mirror of
https://github.com/zoriya/react-native-video.git
synced 2026-06-07 12:25:51 +00:00
fix(ios): Improves playback state and buffering events (#18)
Co-authored-by: Pieczasz <bartekp854@gmail.com> Co-authored-by: Krzysztof Moch <krzysmoch.programs@gmail.com>
This commit is contained in:
committed by
GitHub
parent
6baa1e4f4a
commit
b31f8f0732
@@ -8,7 +8,7 @@ PODS:
|
|||||||
- hermes-engine (0.77.2):
|
- hermes-engine (0.77.2):
|
||||||
- hermes-engine/Pre-built (= 0.77.2)
|
- hermes-engine/Pre-built (= 0.77.2)
|
||||||
- hermes-engine/Pre-built (0.77.2)
|
- hermes-engine/Pre-built (0.77.2)
|
||||||
- NitroModules (0.25.2):
|
- NitroModules (0.26.2):
|
||||||
- DoubleConversion
|
- DoubleConversion
|
||||||
- glog
|
- glog
|
||||||
- hermes-engine
|
- hermes-engine
|
||||||
@@ -1816,7 +1816,7 @@ SPEC CHECKSUMS:
|
|||||||
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
|
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
|
||||||
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
|
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
|
||||||
hermes-engine: 8eb265241fa1d7095d3a40d51fd90f7dce68217c
|
hermes-engine: 8eb265241fa1d7095d3a40d51fd90f7dce68217c
|
||||||
NitroModules: 5cc92d3b0baf1124ed8136a015924eea03db912e
|
NitroModules: 39248e88212416d858a4f4561cf719c6cc8ef900
|
||||||
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
|
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
|
||||||
RCTDeprecation: 85b72250b63cfb54f29ca96ceb108cb9ef3c2079
|
RCTDeprecation: 85b72250b63cfb54f29ca96ceb108cb9ef3c2079
|
||||||
RCTRequired: 567cb8f5d42b990331bfd93faad1d8999b1c1736
|
RCTRequired: 567cb8f5d42b990331bfd93faad1d8999b1c1736
|
||||||
|
|||||||
@@ -136,6 +136,22 @@ const VideoDemo = () => {
|
|||||||
[addEvent]
|
[addEvent]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handlePlayerSeek = React.useCallback(
|
||||||
|
(time: number) => {
|
||||||
|
addEvent(`Player: onSeek ${time.toFixed(2)}s`);
|
||||||
|
},
|
||||||
|
[addEvent]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePlayerStateChange = React.useCallback(
|
||||||
|
(state: { isPlaying: boolean; isBuffering: boolean }) => {
|
||||||
|
addEvent(
|
||||||
|
`Player: onPlaybackStateChange isPlaying=${state.isPlaying}, isBuffering=${state.isBuffering}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[addEvent]
|
||||||
|
);
|
||||||
|
|
||||||
// Setup player
|
// Setup player
|
||||||
const player = useVideoPlayer(
|
const player = useVideoPlayer(
|
||||||
{
|
{
|
||||||
@@ -157,6 +173,8 @@ const VideoDemo = () => {
|
|||||||
useEvent(player, 'onBuffer', handlePlayerBuffer);
|
useEvent(player, 'onBuffer', handlePlayerBuffer);
|
||||||
useEvent(player, 'onProgress', handlePlayerProgress);
|
useEvent(player, 'onProgress', handlePlayerProgress);
|
||||||
useEvent(player, 'onStatusChange', handlePlayerStatusChange);
|
useEvent(player, 'onStatusChange', handlePlayerStatusChange);
|
||||||
|
useEvent(player, 'onSeek', handlePlayerSeek);
|
||||||
|
useEvent(player, 'onPlaybackStateChange', handlePlayerStateChange);
|
||||||
|
|
||||||
// Sync settings with player
|
// Sync settings with player
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import AVFoundation
|
|||||||
protocol VideoPlayerObserverDelegate: AnyObject {
|
protocol VideoPlayerObserverDelegate: AnyObject {
|
||||||
func onPlayedToEnd(player: AVPlayer)
|
func onPlayedToEnd(player: AVPlayer)
|
||||||
func onPlayerItemChange(player: AVPlayer, playerItem: AVPlayerItem?)
|
func onPlayerItemChange(player: AVPlayer, playerItem: AVPlayerItem?)
|
||||||
|
func onPlayerItemWillChange(hasNewPlayerItem: Bool)
|
||||||
func onTextTrackDataChanged(texts: [NSAttributedString])
|
func onTextTrackDataChanged(texts: [NSAttributedString])
|
||||||
func onTimedMetadataChanged(timedMetadata: [AVMetadataItem])
|
func onTimedMetadataChanged(timedMetadata: [AVMetadataItem])
|
||||||
func onRateChanged(rate: Float)
|
func onRateChanged(rate: Float)
|
||||||
@@ -28,6 +29,7 @@ protocol VideoPlayerObserverDelegate: AnyObject {
|
|||||||
extension VideoPlayerObserverDelegate {
|
extension VideoPlayerObserverDelegate {
|
||||||
func onPlayedToEnd(player: AVPlayer) {}
|
func onPlayedToEnd(player: AVPlayer) {}
|
||||||
func onPlayerItemChange(player: AVPlayer, playerItem: AVPlayerItem?) {}
|
func onPlayerItemChange(player: AVPlayer, playerItem: AVPlayerItem?) {}
|
||||||
|
func onPlayerItemWillChange(hasNewPlayerItem: Bool) {}
|
||||||
func onTextTrackDataChanged(texts: [NSAttributedString]) {}
|
func onTextTrackDataChanged(texts: [NSAttributedString]) {}
|
||||||
func onTimedMetadataChanged(timedMetadata: [AVMetadataItem]) {}
|
func onTimedMetadataChanged(timedMetadata: [AVMetadataItem]) {}
|
||||||
func onRateChanged(rate: Float) {}
|
func onRateChanged(rate: Float) {}
|
||||||
@@ -62,6 +64,7 @@ class VideoPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate, AVP
|
|||||||
var playbackEndedObserver: NSObjectProtocol?
|
var playbackEndedObserver: NSObjectProtocol?
|
||||||
var playbackBufferEmptyObserver: NSKeyValueObservation?
|
var playbackBufferEmptyObserver: NSKeyValueObservation?
|
||||||
var playbackLikelyToKeepUpObserver: NSKeyValueObservation?
|
var playbackLikelyToKeepUpObserver: NSKeyValueObservation?
|
||||||
|
var playbackBufferFullObserver: NSKeyValueObservation?
|
||||||
var playerItemStatusObserver: NSKeyValueObservation?
|
var playerItemStatusObserver: NSKeyValueObservation?
|
||||||
var playerItemAccessLogObserver: NSObjectProtocol?
|
var playerItemAccessLogObserver: NSObjectProtocol?
|
||||||
|
|
||||||
@@ -144,15 +147,7 @@ class VideoPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate, AVP
|
|||||||
self?.onPlayerAccessLog(playerItem: playerItem)
|
self?.onPlayerAccessLog(playerItem: playerItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackBufferEmptyObserver = playerItem.observe(\.isPlaybackBufferEmpty, options: [.new]) { [weak self] _, change in
|
setupBufferObservers(for: playerItem)
|
||||||
guard change.newValue == true else { return }
|
|
||||||
self?.delegate?.onPlaybackBufferEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
playbackLikelyToKeepUpObserver = playerItem.observe(\.isPlaybackLikelyToKeepUp, options: [.new]) { [weak self] _, change in
|
|
||||||
guard change.newValue == true else { return }
|
|
||||||
self?.delegate?.onPlaybackLikelyToKeepUp()
|
|
||||||
}
|
|
||||||
|
|
||||||
playerItemStatusObserver = playerItem.observe(\.status, options: [.new]) { [weak self] _, change in
|
playerItemStatusObserver = playerItem.observe(\.status, options: [.new]) { [weak self] _, change in
|
||||||
self?.delegate?.onPlayerItemStatusChanged(status: playerItem.status)
|
self?.delegate?.onPlayerItemStatusChanged(status: playerItem.status)
|
||||||
@@ -178,10 +173,7 @@ class VideoPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate, AVP
|
|||||||
self.playerItemAccessLogObserver = nil
|
self.playerItemAccessLogObserver = nil
|
||||||
}
|
}
|
||||||
// Invalidate KVO observers
|
// Invalidate KVO observers
|
||||||
playbackBufferEmptyObserver?.invalidate()
|
clearBufferObservers()
|
||||||
playbackBufferEmptyObserver = nil
|
|
||||||
playbackLikelyToKeepUpObserver?.invalidate()
|
|
||||||
playbackLikelyToKeepUpObserver = nil
|
|
||||||
playerItemStatusObserver?.invalidate()
|
playerItemStatusObserver?.invalidate()
|
||||||
playerItemStatusObserver = nil
|
playerItemStatusObserver = nil
|
||||||
// Remove outputs if needed
|
// Remove outputs if needed
|
||||||
@@ -241,6 +233,9 @@ class VideoPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate, AVP
|
|||||||
// Remove observers for old player item
|
// Remove observers for old player item
|
||||||
invalidatePlayerItemObservers()
|
invalidatePlayerItemObservers()
|
||||||
|
|
||||||
|
// Notify delegate about player item state change
|
||||||
|
delegate?.onPlayerItemWillChange(hasNewPlayerItem: newPlayerItem != nil)
|
||||||
|
|
||||||
if let playerItem = newPlayerItem {
|
if let playerItem = newPlayerItem {
|
||||||
// Initialize observers for new player item
|
// Initialize observers for new player item
|
||||||
initializePlayerItemObservers(player: player, playerItem: playerItem)
|
initializePlayerItemObservers(player: player, playerItem: playerItem)
|
||||||
@@ -256,4 +251,44 @@ class VideoPlayerObserver: NSObject, AVPlayerItemMetadataOutputPushDelegate, AVP
|
|||||||
|
|
||||||
delegate?.onBandwidthUpdate(bitrate: lastEvent.indicatedBitrate)
|
delegate?.onBandwidthUpdate(bitrate: lastEvent.indicatedBitrate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Buffer State Management
|
||||||
|
|
||||||
|
func setupBufferObservers(for playerItem: AVPlayerItem) {
|
||||||
|
clearBufferObservers()
|
||||||
|
|
||||||
|
// Observe buffer empty - this indicates definite buffering
|
||||||
|
playbackBufferEmptyObserver = playerItem.observe(\.isPlaybackBufferEmpty, options: [.new, .initial]) { [weak self] playerItem, change in
|
||||||
|
let isEmpty = change.newValue ?? playerItem.isPlaybackBufferEmpty
|
||||||
|
if isEmpty {
|
||||||
|
self?.delegate?.onPlaybackBufferEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe likely to keep up - this indicates that buffering has finished
|
||||||
|
playbackLikelyToKeepUpObserver = playerItem.observe(\.isPlaybackLikelyToKeepUp, options: [.new, .initial]) { [weak self] playerItem, change in
|
||||||
|
let isLikelyToKeepUp = change.newValue ?? playerItem.isPlaybackLikelyToKeepUp
|
||||||
|
if isLikelyToKeepUp {
|
||||||
|
self?.delegate?.onPlaybackLikelyToKeepUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe buffer full as an additional signal
|
||||||
|
playbackBufferFullObserver = playerItem.observe(\.isPlaybackBufferFull, options: [.new, .initial]) { [weak self] playerItem, change in
|
||||||
|
let isFull = change.newValue ?? playerItem.isPlaybackBufferFull
|
||||||
|
if isFull {
|
||||||
|
self?.delegate?.onPlaybackLikelyToKeepUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearBufferObservers() {
|
||||||
|
playbackBufferEmptyObserver?.invalidate()
|
||||||
|
playbackBufferFullObserver?.invalidate()
|
||||||
|
playbackLikelyToKeepUpObserver?.invalidate()
|
||||||
|
|
||||||
|
playbackBufferEmptyObserver = nil
|
||||||
|
playbackBufferFullObserver = nil
|
||||||
|
playbackLikelyToKeepUpObserver = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-40
@@ -22,6 +22,7 @@ extension HybridVideoPlayer: VideoPlayerObserverDelegate {
|
|||||||
|
|
||||||
func onRateChanged(rate: Float) {
|
func onRateChanged(rate: Float) {
|
||||||
eventEmitter.onPlaybackRateChange(Double(rate))
|
eventEmitter.onPlaybackRateChange(Double(rate))
|
||||||
|
updateAndEmitPlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func onVolumeChanged(volume: Float) {
|
func onVolumeChanged(volume: Float) {
|
||||||
@@ -29,28 +30,21 @@ extension HybridVideoPlayer: VideoPlayerObserverDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func onPlaybackBufferEmpty() {
|
func onPlaybackBufferEmpty() {
|
||||||
if playerItem?.isPlaybackBufferEmpty == true {
|
isCurrentlyBuffering = true
|
||||||
eventEmitter.onBuffer(true)
|
status = .loading
|
||||||
status = .loading
|
updateAndEmitPlaybackState()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func onProgressUpdate(currentTime: Double, bufferDuration bufferDuration: Double) {
|
func onProgressUpdate(currentTime: Double, bufferDuration: Double) {
|
||||||
eventEmitter.onProgress(.init(currentTime: currentTime, bufferDuration: bufferDuration))
|
eventEmitter.onProgress(.init(currentTime: currentTime, bufferDuration: bufferDuration))
|
||||||
}
|
}
|
||||||
|
|
||||||
func onPlaybackLikelyToKeepUp() {
|
func onPlaybackLikelyToKeepUp() {
|
||||||
guard let playerItem else {
|
isCurrentlyBuffering = false
|
||||||
return
|
if playerPointer.timeControlStatus == .playing {
|
||||||
}
|
|
||||||
|
|
||||||
if !playerItem.isPlaybackBufferEmpty && playerItem.isPlaybackBufferEmpty {
|
|
||||||
eventEmitter.onBuffer(true)
|
|
||||||
status = .loading
|
|
||||||
} else if playerItem.isPlaybackLikelyToKeepUp {
|
|
||||||
eventEmitter.onBuffer(false)
|
|
||||||
status = .readytoplay
|
status = .readytoplay
|
||||||
}
|
}
|
||||||
|
updateAndEmitPlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func onExternalPlaybackActiveChanged(isActive: Bool) {
|
func onExternalPlaybackActiveChanged(isActive: Bool) {
|
||||||
@@ -58,62 +52,67 @@ extension HybridVideoPlayer: VideoPlayerObserverDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func onTimeControlStatusChanged(status: AVPlayer.TimeControlStatus) {
|
func onTimeControlStatusChanged(status: AVPlayer.TimeControlStatus) {
|
||||||
// check for error
|
|
||||||
if playerPointer.status == .failed || playerItem?.status == .failed {
|
if playerPointer.status == .failed || playerItem?.status == .failed {
|
||||||
self.status = .error
|
self.status = .error
|
||||||
|
isCurrentlyBuffering = false
|
||||||
eventEmitter.onPlaybackStateChange(.init(isPlaying: false, isBuffering: false))
|
eventEmitter.onPlaybackStateChange(.init(isPlaying: false, isBuffering: false))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if player is waiting to play at specified rate
|
switch status {
|
||||||
if playerPointer.timeControlStatus == .waitingToPlayAtSpecifiedRate {
|
case .waitingToPlayAtSpecifiedRate:
|
||||||
|
isCurrentlyBuffering = true
|
||||||
self.status = .loading
|
self.status = .loading
|
||||||
return
|
break
|
||||||
}
|
|
||||||
|
|
||||||
self.status = .readytoplay
|
|
||||||
|
|
||||||
// check for playback state
|
|
||||||
switch playerPointer.timeControlStatus {
|
|
||||||
case .playing:
|
case .playing:
|
||||||
eventEmitter.onPlaybackStateChange(.init(isPlaying: true, isBuffering: playerItem?.isPlaybackBufferEmpty == true))
|
isCurrentlyBuffering = false
|
||||||
|
self.status = .readytoplay
|
||||||
|
break
|
||||||
|
|
||||||
case .paused:
|
case .paused:
|
||||||
eventEmitter.onPlaybackStateChange(.init(isPlaying: false, isBuffering: playerItem?.isPlaybackBufferEmpty == true))
|
isCurrentlyBuffering = false
|
||||||
default:
|
self.status = .readytoplay
|
||||||
|
break
|
||||||
|
|
||||||
|
@unknown default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateAndEmitPlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func onPlayerStatusChanged(status: AVPlayer.Status) {
|
func onPlayerStatusChanged(status: AVPlayer.Status) {
|
||||||
// check for error
|
|
||||||
if status == .failed || playerItem?.status == .failed {
|
if status == .failed || playerItem?.status == .failed {
|
||||||
self.status = .error
|
self.status = .error
|
||||||
|
isCurrentlyBuffering = false
|
||||||
|
updateAndEmitPlaybackState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func onPlayerItemStatusChanged(status: AVPlayerItem.Status) {
|
func onPlayerItemStatusChanged(status: AVPlayerItem.Status) {
|
||||||
if status == .failed {
|
if status == .failed {
|
||||||
self.status = .error
|
self.status = .error
|
||||||
|
isCurrentlyBuffering = false
|
||||||
|
updateAndEmitPlaybackState()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch status {
|
switch status {
|
||||||
case .unknown:
|
case .unknown:
|
||||||
|
isCurrentlyBuffering = true
|
||||||
self.status = .loading
|
self.status = .loading
|
||||||
case .readyToPlay:
|
|
||||||
self.status = playerItem?.isPlaybackBufferEmpty == true ? .loading : .readytoplay
|
// Set initial buffering state when we have a playerItem
|
||||||
case .failed:
|
if let playerItem = self.playerItem {
|
||||||
self.status = .error
|
if playerItem.isPlaybackBufferEmpty {
|
||||||
@unknown default:
|
isCurrentlyBuffering = true
|
||||||
break
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if self.status == .error || self.status == .readytoplay {
|
|
||||||
guard let playerItem else {
|
|
||||||
// unlikely to happen
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .readyToPlay:
|
||||||
|
guard let playerItem else { return }
|
||||||
|
|
||||||
let height = playerItem.presentationSize.height
|
let height = playerItem.presentationSize.height
|
||||||
let width = playerItem.presentationSize.width
|
let width = playerItem.presentationSize.width
|
||||||
let orientation: VideoOrientation = playerItem.asset.tracks.first(where: { $0.mediaType == .video })?.orientation ?? .unknown
|
let orientation: VideoOrientation = playerItem.asset.tracks.first(where: { $0.mediaType == .video })?.orientation ?? .unknown
|
||||||
@@ -121,7 +120,21 @@ extension HybridVideoPlayer: VideoPlayerObserverDelegate {
|
|||||||
eventEmitter.onLoad(
|
eventEmitter.onLoad(
|
||||||
.init(currentTime, duration, height, width, orientation)
|
.init(currentTime, duration, height, width, orientation)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if playerItem.isPlaybackLikelyToKeepUp && !playerItem.isPlaybackBufferEmpty {
|
||||||
|
isCurrentlyBuffering = false
|
||||||
|
self.status = .readytoplay
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failed:
|
||||||
|
self.status = .error
|
||||||
|
isCurrentlyBuffering = false
|
||||||
|
|
||||||
|
@unknown default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateAndEmitPlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func onTextTrackDataChanged(texts: [NSAttributedString]) {
|
func onTextTrackDataChanged(texts: [NSAttributedString]) {
|
||||||
@@ -145,4 +158,23 @@ extension HybridVideoPlayer: VideoPlayerObserverDelegate {
|
|||||||
func onBandwidthUpdate(bitrate: Double) {
|
func onBandwidthUpdate(bitrate: Double) {
|
||||||
eventEmitter.onBandwidthUpdate(.init(bitrate: bitrate, width: nil, height: nil))
|
eventEmitter.onBandwidthUpdate(.init(bitrate: bitrate, width: nil, height: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func onPlayerItemWillChange(hasNewPlayerItem: Bool) {
|
||||||
|
if hasNewPlayerItem {
|
||||||
|
// Set initial buffering state when playerItem is assigned
|
||||||
|
isCurrentlyBuffering = true
|
||||||
|
status = .loading
|
||||||
|
updateAndEmitPlaybackState()
|
||||||
|
} else {
|
||||||
|
// Clean up state when playerItem is cleared
|
||||||
|
isCurrentlyBuffering = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAndEmitPlaybackState() {
|
||||||
|
let isPlaying = playerPointer.rate > 0 && !isCurrentlyBuffering
|
||||||
|
|
||||||
|
eventEmitter.onPlaybackStateChange(.init(isPlaying: isPlaying, isBuffering: isCurrentlyBuffering))
|
||||||
|
eventEmitter.onBuffer(isCurrentlyBuffering)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,9 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
|||||||
|
|
||||||
// Text track selection state
|
// Text track selection state
|
||||||
private var selectedExternalTrackIndex: Int? = nil
|
private var selectedExternalTrackIndex: Int? = nil
|
||||||
|
|
||||||
|
// MARK: - Buffering state tracking
|
||||||
|
var isCurrentlyBuffering: Bool = false
|
||||||
|
|
||||||
var isPlaying: Bool {
|
var isPlaying: Bool {
|
||||||
get {
|
get {
|
||||||
@@ -185,6 +188,7 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
|||||||
func release() {
|
func release() {
|
||||||
playerQueue.async { [weak self] in
|
playerQueue.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
self.player?.replaceCurrentItem(with: nil)
|
self.player?.replaceCurrentItem(with: nil)
|
||||||
self.player = nil
|
self.player = nil
|
||||||
self.playerItem = nil
|
self.playerItem = nil
|
||||||
@@ -295,7 +299,7 @@ class HybridVideoPlayer: HybridVideoPlayerSpec {
|
|||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Internal Methods
|
// MARK: - Methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the player item synchronously. This is used to initialize the player item before it is set to the player.
|
* Initialize the player item synchronously. This is used to initialize the player item before it is set to the player.
|
||||||
|
|||||||
Reference in New Issue
Block a user