From 39e366e89611a45dc1d23f64b698401cb8d6ac88 Mon Sep 17 00:00:00 2001 From: cranci1 <100066266+cranci1@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:33:47 +0200 Subject: [PATCH] Fixed PiP kinda + Crash issue --- .../CustomPlayer/CustomPlayer.swift | 132 ++++++++++-------- .../JavaScriptCore+Extensions.swift | 2 +- .../SettingsSubViews/SettingsViewPlayer.swift | 8 -- 3 files changed, 73 insertions(+), 69 deletions(-) diff --git a/Sora/MediaUtils/CustomPlayer/CustomPlayer.swift b/Sora/MediaUtils/CustomPlayer/CustomPlayer.swift index a4563e8..45b52b2 100644 --- a/Sora/MediaUtils/CustomPlayer/CustomPlayer.swift +++ b/Sora/MediaUtils/CustomPlayer/CustomPlayer.swift @@ -12,7 +12,7 @@ import MediaPlayer import AVFoundation import MarqueeLabel -class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDelegate { +class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDelegate, AVPlayerViewControllerDelegate { private var airplayButton: AVRoutePickerView! let module: ScrapingModule let streamURL: String @@ -68,13 +68,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele return UserDefaults.standard.bool(forKey: "doubleTapSeekEnabled") } - private var isPipButtonVisible: Bool { - if UserDefaults.standard.object(forKey: "pipButtonVisible") == nil { - return true - } - return UserDefaults.standard.bool(forKey: "pipButtonVisible") - } - private var isAutoplayEnabled: Bool { if UserDefaults.standard.object(forKey: "autoplayNext") == nil { return true @@ -82,7 +75,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele return UserDefaults.standard.bool(forKey: "autoplayNext") } private var pipController: AVPictureInPictureController? - private var pipButton: UIButton! var portraitButtonVisibleConstraints: [NSLayoutConstraint] = [] var portraitButtonHiddenConstraints: [NSLayoutConstraint] = [] @@ -187,7 +179,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele skip85Button, controlButtonsContainer, volumeSliderHostingView, - pipButton, airplayButton, timeBatteryContainer, endTimeIcon, @@ -207,7 +198,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele views.append(contentsOf: view.subviews.filter { $0 is UIVisualEffectView || - ($0.layer.cornerRadius > 0 && $0 != dismissButton && $0 != lockButton && $0 != dimButton && $0 != pipButton && $0 != holdSpeedIndicator && $0 != volumeSliderHostingView) + ($0.layer.cornerRadius > 0 && $0 != dismissButton && $0 != lockButton && $0 != dimButton && $0 != holdSpeedIndicator && $0 != volumeSliderHostingView) }) return views @@ -496,7 +487,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele view.addSubview(capsuleContainer) capsuleContainer.alpha = isControlsVisible ? 1.0 : 0.0 - let buttons: [UIView] = [airplayButton, pipButton, lockButton, dimButton] + let buttons: [UIView] = [airplayButton, lockButton, dimButton] for btn in buttons { btn.removeFromSuperview() capsuleContainer.addSubview(btn) @@ -728,7 +719,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele skipIntroButton?.removeFromSuperview() skipOutroButton?.removeFromSuperview() skip85Button?.removeFromSuperview() - pipButton?.removeFromSuperview() airplayButton?.removeFromSuperview() menuButton?.removeFromSuperview() speedButton?.removeFromSuperview() @@ -807,6 +797,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele playerViewController = AVPlayerViewController() playerViewController.player = player playerViewController.showsPlaybackControls = false + playerViewController.delegate = self + addChild(playerViewController) if playerViewController.view.superview == nil { view.addSubview(playerViewController.view) @@ -2205,18 +2197,23 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } } - @objc private func pipButtonTapped(_ sender: UIButton) { - guard let pip = pipController else { return } - if pip.isPictureInPictureActive { - pip.stopPictureInPicture() - } else { - pip.startPictureInPicture() - } - } - @objc private func startPipIfNeeded() { - Logger.shared.log("PIP", type: "Genral") - pipController!.startPictureInPicture() + guard let pipController = pipController else { + Logger.shared.log("PiP controller not available", type: "Error") + return + } + + guard AVPictureInPictureController.isPictureInPictureSupported() else { + Logger.shared.log("PiP not supported on this device", type: "Error") + return + } + + guard !pipController.isPictureInPictureActive else { + Logger.shared.log("PiP already active", type: "Debug") + return + } + + pipController.startPictureInPicture() } @objc private func lockTapped() { @@ -3568,47 +3565,20 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele playerViewController.allowsPictureInPicturePlayback = true - let playerLayerContainer = UIView() - playerLayerContainer.translatesAutoresizingMaskIntoConstraints = false - view.insertSubview(playerLayerContainer, at: 0) - - NSLayoutConstraint.activate([ - playerLayerContainer.topAnchor.constraint(equalTo: view.topAnchor), - playerLayerContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor), - playerLayerContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), - playerLayerContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor) - ]) - - let pipPlayerLayer = AVPlayerLayer(player: playerViewController.player) - pipPlayerLayer.frame = playerViewController.view.layer.bounds - pipPlayerLayer.videoGravity = .resizeAspect - - playerViewController.view.layer.insertSublayer(pipPlayerLayer, at: 0) - pipController = AVPictureInPictureController(playerLayer: pipPlayerLayer) + if let playerLayer = playerViewController.view.layer.sublayers?.first(where: { $0 is AVPlayerLayer }) as? AVPlayerLayer { + pipController = AVPictureInPictureController(playerLayer: playerLayer) + } else { + pipController = AVPictureInPictureController(playerLayer: AVPlayerLayer(player: player)) + } pipController?.delegate = self - let Image = UIImage(systemName: "pip", withConfiguration: cfg) - pipButton = UIButton(type: .system) - pipButton.setImage(Image, for: .normal) - pipButton.tintColor = .white - pipButton.addTarget(self, action: #selector(pipButtonTapped(_:)), for: .touchUpInside) - - controlsContainerView.addSubview(pipButton) - pipButton.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - pipButton.centerYAnchor.constraint(equalTo: dimButton.centerYAnchor), - pipButton.trailingAnchor.constraint(equalTo: dimButton.leadingAnchor, constant: -8), - pipButton.widthAnchor.constraint(equalToConstant: 30), - pipButton.heightAnchor.constraint(equalToConstant: 24), - airplayButton.centerYAnchor.constraint(equalTo: pipButton.centerYAnchor), - airplayButton.trailingAnchor.constraint(equalTo: pipButton.leadingAnchor, constant: -4), + airplayButton.centerYAnchor.constraint(equalTo: dimButton.centerYAnchor), + airplayButton.trailingAnchor.constraint(equalTo: dimButton.leadingAnchor, constant: -8), airplayButton.widthAnchor.constraint(equalToConstant: 24), airplayButton.heightAnchor.constraint(equalToConstant: 24) ]) - pipButton.isHidden = !isPipButtonVisible - NotificationCenter.default.addObserver(self, selector: #selector(startPipIfNeeded), name: UIApplication.didEnterBackgroundNotification, object: nil) } @@ -3701,11 +3671,11 @@ class GradientOverlayButton: UIButton { extension CustomMediaPlayerViewController: AVPictureInPictureControllerDelegate { func pictureInPictureControllerWillStartPictureInPicture(_ pipController: AVPictureInPictureController) { - pipButton.alpha = 0.5 + // PiP will start } func pictureInPictureControllerDidStopPictureInPicture(_ pipController: AVPictureInPictureController) { - pipButton.alpha = 1.0 + // PiP did stop } func pictureInPictureController(_ pipController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { @@ -3713,6 +3683,30 @@ extension CustomMediaPlayerViewController: AVPictureInPictureControllerDelegate } } +// MARK: - AVPlayerViewControllerDelegate +extension CustomMediaPlayerViewController { + func playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { + let windowScene = UIApplication.shared.connectedScenes + .filter { $0.activationState == .foregroundActive } + .compactMap { $0 as? UIWindowScene } + .first + + let window = windowScene?.windows.first(where: { $0.isKeyWindow }) + + if let topVC = window?.rootViewController?.topmostViewController() { + if topVC != self { + topVC.present(self, animated: true) { + completionHandler(true) + } + } else { + completionHandler(true) + } + } else { + completionHandler(false) + } + } +} + extension CustomMediaPlayerViewController { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true @@ -3897,3 +3891,21 @@ extension CustomMediaPlayerViewController { } } } + +extension UIViewController { + func topmostViewController() -> UIViewController { + if let presented = self.presentedViewController { + return presented.topmostViewController() + } + + if let navigation = self as? UINavigationController { + return navigation.visibleViewController?.topmostViewController() ?? navigation + } + + if let tabBar = self as? UITabBarController { + return tabBar.selectedViewController?.topmostViewController() ?? tabBar + } + + return self + } +} diff --git a/Sora/Utlis & Misc/Extensions/JavaScriptCore+Extensions.swift b/Sora/Utlis & Misc/Extensions/JavaScriptCore+Extensions.swift index ee26986..69c54d5 100644 --- a/Sora/Utlis & Misc/Extensions/JavaScriptCore+Extensions.swift +++ b/Sora/Utlis & Misc/Extensions/JavaScriptCore+Extensions.swift @@ -165,7 +165,7 @@ extension JSContext { if httpMethod == "GET" && !bodyIsEmpty { Logger.shared.log("GET request must not have a body", type: "Error") DispatchQueue.main.async { - reject.call(withArguments: ["GET request must not have a body"]) + resolve.call(withArguments: ["GET request must not have a body"]) } return } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 338a664..7c3f741 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -204,7 +204,6 @@ struct SettingsViewPlayer: View { @AppStorage("skip85Visible") private var skip85Visible: Bool = true @AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false @AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true - @AppStorage("pipButtonVisible") private var pipButtonVisible: Bool = true @AppStorage("autoplayNext") private var autoplayNext: Bool = true @AppStorage("videoQualityWiFi") private var wifiQuality: String = VideoQualityPreference.defaultWiFiPreference.rawValue @@ -241,13 +240,6 @@ struct SettingsViewPlayer: View { showDivider: true ) - SettingsToggleRow( - icon: "pip", - title: NSLocalizedString("Show PiP Button", comment: ""), - isOn: $pipButtonVisible, - showDivider: true - ) - SettingsToggleRow( icon: "play.circle.fill", title: NSLocalizedString("Autoplay Next", comment: ""),