Files
react-native-video/ios/view/VideoComponentViewObserver.swift
2026-03-25 17:28:35 +01:00

177 lines
4.8 KiB
Swift

//
// VideoComponentViewObserver.swift
// ReactNativeVideo
//
// Created by Krzysztof Moch on 06/05/2025.
//
import Foundation
import AVKit
import AVFoundation
protocol VideoComponentViewDelegate: AnyObject {
func onPictureInPictureChange(_ isActive: Bool)
func onFullscreenChange(_ isActive: Bool)
func willEnterFullscreen()
func willExitFullscreen()
func willEnterPictureInPicture()
func willExitPictureInPicture()
func onReadyToDisplay()
}
// Map delegate methods to view manager methods
final class VideoViewDelegate: NSObject, VideoComponentViewDelegate {
weak var viewManager: HybridVideoViewViewManager?
init(viewManager: HybridVideoViewViewManager) {
self.viewManager = viewManager
}
func onPictureInPictureChange(_ isActive: Bool) {
viewManager?.onPictureInPictureChange(isActive)
}
func onFullscreenChange(_ isActive: Bool) {
viewManager?.onFullscreenChange(isActive)
}
func willEnterFullscreen() {
viewManager?.willEnterFullscreen()
}
func willExitFullscreen() {
viewManager?.willExitFullscreen()
}
func willEnterPictureInPicture() {
viewManager?.willEnterPictureInPicture()
}
func willExitPictureInPicture() {
viewManager?.willExitPictureInPicture()
}
func onReadyToDisplay() {
if let player = viewManager?.player as? HybridVideoPlayer {
player._eventEmitter?.onReadyToDisplay()
}
}
}
class VideoComponentViewObserver: NSObject, AVPlayerViewControllerDelegate {
private weak var view: VideoComponentView?
var delegate: VideoViewDelegate? {
get {
return view?.delegate
}
}
var playerViewController: AVPlayerViewController? {
return view?.playerViewController
}
// playerViewController observers
var onReadyToDisplayObserver: NSKeyValueObservation?
init(view: VideoComponentView) {
self.view = view
super.init()
}
func initializePlayerViewContorollerObservers() {
guard let playerViewController = playerViewController else {
return
}
onReadyToDisplayObserver = playerViewController.observe(\.isReadyForDisplay, options: [.new]) { [weak self] _, change in
guard let self = self else { return }
if change.newValue == true {
self.delegate?.onReadyToDisplay()
}
}
}
func removePlayerViewControllerObservers() {
onReadyToDisplayObserver?.invalidate()
onReadyToDisplayObserver = nil
}
func updatePlayerViewControllerObservers() {
removePlayerViewControllerObservers()
initializePlayerViewContorollerObservers()
}
func playerViewControllerDidStartPictureInPicture(_: AVPlayerViewController) {
delegate?.onPictureInPictureChange(true)
}
func playerViewControllerDidStopPictureInPicture(_: AVPlayerViewController) {
delegate?.onPictureInPictureChange(false)
}
func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {
delegate?.willEnterPictureInPicture()
}
func playerViewControllerWillStopPictureInPicture(_: AVPlayerViewController) {
delegate?.willExitPictureInPicture()
}
func playerViewControllerRestoreUserInterfaceForPictureInPictureStop(
_ playerViewController: AVPlayerViewController,
completionHandler: @escaping (Bool) -> Void
) {
let isViewAttached = view?.window != nil
completionHandler(isViewAttached)
}
func playerViewController(
_: AVPlayerViewController,
willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
) {
delegate?.willExitFullscreen()
coordinator.animate(alongsideTransition: nil) { [weak self] context in
guard let self = self else { return }
if context.isCancelled {
// iOS bug: window.isUserInteractionEnabled is left as false after cancelled fullscreen dismiss
if let window = self.playerViewController?.view.window, !window.isUserInteractionEnabled {
window.isUserInteractionEnabled = true
}
self.delegate?.willEnterFullscreen()
return
}
self.delegate?.onFullscreenChange(false)
}
}
func playerViewController(
_: AVPlayerViewController,
willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
) {
delegate?.willEnterFullscreen()
coordinator.animate(alongsideTransition: nil) { [weak self] context in
guard let self = self else { return }
if context.isCancelled {
// iOS bug: window.isUserInteractionEnabled is left as false after cancelled fullscreen transition
if let window = self.playerViewController?.view.window, !window.isUserInteractionEnabled {
window.isUserInteractionEnabled = true
}
self.delegate?.willExitFullscreen()
return
}
self.delegate?.onFullscreenChange(true)
}
}
}