Fixed PiP kinda + Crash issue
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build macOS App (push) Has been cancelled

This commit is contained in:
cranci1 2025-09-26 15:33:47 +02:00
parent c42216f793
commit 39e366e896
3 changed files with 73 additions and 69 deletions

View file

@ -12,7 +12,7 @@ import MediaPlayer
import AVFoundation import AVFoundation
import MarqueeLabel import MarqueeLabel
class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDelegate { class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDelegate, AVPlayerViewControllerDelegate {
private var airplayButton: AVRoutePickerView! private var airplayButton: AVRoutePickerView!
let module: ScrapingModule let module: ScrapingModule
let streamURL: String let streamURL: String
@ -68,13 +68,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
return UserDefaults.standard.bool(forKey: "doubleTapSeekEnabled") 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 { private var isAutoplayEnabled: Bool {
if UserDefaults.standard.object(forKey: "autoplayNext") == nil { if UserDefaults.standard.object(forKey: "autoplayNext") == nil {
return true return true
@ -82,7 +75,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
return UserDefaults.standard.bool(forKey: "autoplayNext") return UserDefaults.standard.bool(forKey: "autoplayNext")
} }
private var pipController: AVPictureInPictureController? private var pipController: AVPictureInPictureController?
private var pipButton: UIButton!
var portraitButtonVisibleConstraints: [NSLayoutConstraint] = [] var portraitButtonVisibleConstraints: [NSLayoutConstraint] = []
var portraitButtonHiddenConstraints: [NSLayoutConstraint] = [] var portraitButtonHiddenConstraints: [NSLayoutConstraint] = []
@ -187,7 +179,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
skip85Button, skip85Button,
controlButtonsContainer, controlButtonsContainer,
volumeSliderHostingView, volumeSliderHostingView,
pipButton,
airplayButton, airplayButton,
timeBatteryContainer, timeBatteryContainer,
endTimeIcon, endTimeIcon,
@ -207,7 +198,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
views.append(contentsOf: view.subviews.filter { views.append(contentsOf: view.subviews.filter {
$0 is UIVisualEffectView || $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 return views
@ -496,7 +487,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
view.addSubview(capsuleContainer) view.addSubview(capsuleContainer)
capsuleContainer.alpha = isControlsVisible ? 1.0 : 0.0 capsuleContainer.alpha = isControlsVisible ? 1.0 : 0.0
let buttons: [UIView] = [airplayButton, pipButton, lockButton, dimButton] let buttons: [UIView] = [airplayButton, lockButton, dimButton]
for btn in buttons { for btn in buttons {
btn.removeFromSuperview() btn.removeFromSuperview()
capsuleContainer.addSubview(btn) capsuleContainer.addSubview(btn)
@ -728,7 +719,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
skipIntroButton?.removeFromSuperview() skipIntroButton?.removeFromSuperview()
skipOutroButton?.removeFromSuperview() skipOutroButton?.removeFromSuperview()
skip85Button?.removeFromSuperview() skip85Button?.removeFromSuperview()
pipButton?.removeFromSuperview()
airplayButton?.removeFromSuperview() airplayButton?.removeFromSuperview()
menuButton?.removeFromSuperview() menuButton?.removeFromSuperview()
speedButton?.removeFromSuperview() speedButton?.removeFromSuperview()
@ -807,6 +797,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
playerViewController = AVPlayerViewController() playerViewController = AVPlayerViewController()
playerViewController.player = player playerViewController.player = player
playerViewController.showsPlaybackControls = false playerViewController.showsPlaybackControls = false
playerViewController.delegate = self
addChild(playerViewController) addChild(playerViewController)
if playerViewController.view.superview == nil { if playerViewController.view.superview == nil {
view.addSubview(playerViewController.view) 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() { @objc private func startPipIfNeeded() {
Logger.shared.log("PIP", type: "Genral") guard let pipController = pipController else {
pipController!.startPictureInPicture() 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() { @objc private func lockTapped() {
@ -3568,47 +3565,20 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
playerViewController.allowsPictureInPicturePlayback = true playerViewController.allowsPictureInPicturePlayback = true
let playerLayerContainer = UIView() if let playerLayer = playerViewController.view.layer.sublayers?.first(where: { $0 is AVPlayerLayer }) as? AVPlayerLayer {
playerLayerContainer.translatesAutoresizingMaskIntoConstraints = false pipController = AVPictureInPictureController(playerLayer: playerLayer)
view.insertSubview(playerLayerContainer, at: 0) } else {
pipController = AVPictureInPictureController(playerLayer: AVPlayerLayer(player: player))
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)
pipController?.delegate = self 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([ NSLayoutConstraint.activate([
pipButton.centerYAnchor.constraint(equalTo: dimButton.centerYAnchor), airplayButton.centerYAnchor.constraint(equalTo: dimButton.centerYAnchor),
pipButton.trailingAnchor.constraint(equalTo: dimButton.leadingAnchor, constant: -8), airplayButton.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.widthAnchor.constraint(equalToConstant: 24), airplayButton.widthAnchor.constraint(equalToConstant: 24),
airplayButton.heightAnchor.constraint(equalToConstant: 24) airplayButton.heightAnchor.constraint(equalToConstant: 24)
]) ])
pipButton.isHidden = !isPipButtonVisible
NotificationCenter.default.addObserver(self, selector: #selector(startPipIfNeeded), name: UIApplication.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(startPipIfNeeded), name: UIApplication.didEnterBackgroundNotification, object: nil)
} }
@ -3701,11 +3671,11 @@ class GradientOverlayButton: UIButton {
extension CustomMediaPlayerViewController: AVPictureInPictureControllerDelegate { extension CustomMediaPlayerViewController: AVPictureInPictureControllerDelegate {
func pictureInPictureControllerWillStartPictureInPicture(_ pipController: AVPictureInPictureController) { func pictureInPictureControllerWillStartPictureInPicture(_ pipController: AVPictureInPictureController) {
pipButton.alpha = 0.5 // PiP will start
} }
func pictureInPictureControllerDidStopPictureInPicture(_ pipController: AVPictureInPictureController) { func pictureInPictureControllerDidStopPictureInPicture(_ pipController: AVPictureInPictureController) {
pipButton.alpha = 1.0 // PiP did stop
} }
func pictureInPictureController(_ pipController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { 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 { extension CustomMediaPlayerViewController {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true 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
}
}

View file

@ -165,7 +165,7 @@ extension JSContext {
if httpMethod == "GET" && !bodyIsEmpty { if httpMethod == "GET" && !bodyIsEmpty {
Logger.shared.log("GET request must not have a body", type: "Error") Logger.shared.log("GET request must not have a body", type: "Error")
DispatchQueue.main.async { DispatchQueue.main.async {
reject.call(withArguments: ["GET request must not have a body"]) resolve.call(withArguments: ["GET request must not have a body"])
} }
return return
} }

View file

@ -204,7 +204,6 @@ struct SettingsViewPlayer: View {
@AppStorage("skip85Visible") private var skip85Visible: Bool = true @AppStorage("skip85Visible") private var skip85Visible: Bool = true
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false @AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true @AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
@AppStorage("pipButtonVisible") private var pipButtonVisible: Bool = true
@AppStorage("autoplayNext") private var autoplayNext: Bool = true @AppStorage("autoplayNext") private var autoplayNext: Bool = true
@AppStorage("videoQualityWiFi") private var wifiQuality: String = VideoQualityPreference.defaultWiFiPreference.rawValue @AppStorage("videoQualityWiFi") private var wifiQuality: String = VideoQualityPreference.defaultWiFiPreference.rawValue
@ -241,13 +240,6 @@ struct SettingsViewPlayer: View {
showDivider: true showDivider: true
) )
SettingsToggleRow(
icon: "pip",
title: NSLocalizedString("Show PiP Button", comment: ""),
isOn: $pipButtonVisible,
showDivider: true
)
SettingsToggleRow( SettingsToggleRow(
icon: "play.circle.fill", icon: "play.circle.fill",
title: NSLocalizedString("Autoplay Next", comment: ""), title: NSLocalizedString("Autoplay Next", comment: ""),