would this wrk?
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run

This commit is contained in:
Francesco 2025-06-07 18:39:29 +02:00
parent ad2ade2f45
commit 92585beaba

View file

@ -13,6 +13,7 @@ import AVFoundation
import MarqueeLabel import MarqueeLabel
class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDelegate { class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDelegate {
private var airplayButton: AVRoutePickerView!
let module: ScrapingModule let module: ScrapingModule
let streamURL: String let streamURL: String
let fullUrl: String let fullUrl: String
@ -72,7 +73,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
} }
private var pipController: AVPictureInPictureController? private var pipController: AVPictureInPictureController?
private var pipButton: UIButton! private var pipButton: UIButton!
var portraitButtonVisibleConstraints: [NSLayoutConstraint] = [] var portraitButtonVisibleConstraints: [NSLayoutConstraint] = []
var portraitButtonHiddenConstraints: [NSLayoutConstraint] = [] var portraitButtonHiddenConstraints: [NSLayoutConstraint] = []
@ -176,7 +177,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
qualityButton, qualityButton,
speedButton, speedButton,
watchNextButton, watchNextButton,
volumeSliderHostingView volumeSliderHostingView,
pipButton,
airplayButton
].compactMap { $0 } ].compactMap { $0 }
private var originalHiddenStates: [UIView: Bool] = [:] private var originalHiddenStates: [UIView: Bool] = [:]
@ -437,7 +440,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
sliderHostingController = nil sliderHostingController = nil
try? AVAudioSession.sharedInstance().setActive(false) try? AVAudioSession.sharedInstance().setActive(false)
} }
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &playerItemKVOContext else { guard context == &playerItemKVOContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
@ -1239,51 +1242,65 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
} }
private func setupPipIfSupported() { private func setupPipIfSupported() {
airplayButton = AVRoutePickerView(frame: .zero)
airplayButton.translatesAutoresizingMaskIntoConstraints = false
airplayButton.activeTintColor = .white
airplayButton.tintColor = .white
airplayButton.backgroundColor = .clear
airplayButton.prioritizesVideoDevices = true
airplayButton.setContentHuggingPriority(.required, for: .horizontal)
airplayButton.setContentCompressionResistancePriority(.required, for: .horizontal)
controlsContainerView.addSubview(airplayButton)
guard AVPictureInPictureController.isPictureInPictureSupported() else { guard AVPictureInPictureController.isPictureInPictureSupported() else {
return return
} }
let pipPlayerLayer = AVPlayerLayer(player: playerViewController.player) let pipPlayerLayer = AVPlayerLayer(player: playerViewController.player)
pipPlayerLayer.frame = playerViewController.view.layer.bounds pipPlayerLayer.frame = playerViewController.view.layer.bounds
pipPlayerLayer.videoGravity = .resizeAspect pipPlayerLayer.videoGravity = .resizeAspect
playerViewController.view.layer.insertSublayer(pipPlayerLayer, at: 0) playerViewController.view.layer.insertSublayer(pipPlayerLayer, at: 0)
pipController = AVPictureInPictureController(playerLayer: pipPlayerLayer) pipController = AVPictureInPictureController(playerLayer: pipPlayerLayer)
pipController?.delegate = self pipController?.delegate = self
let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .medium)
let Image = UIImage(systemName: "pip", withConfiguration: config) let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .medium)
pipButton = UIButton(type: .system) let Image = UIImage(systemName: "pip", withConfiguration: config)
pipButton.setImage(Image, for: .normal) pipButton = UIButton(type: .system)
pipButton.tintColor = .white pipButton.setImage(Image, for: .normal)
pipButton.addTarget(self, action: #selector(pipButtonTapped(_:)), for: .touchUpInside) pipButton.tintColor = .white
pipButton.addTarget(self, action: #selector(pipButtonTapped(_:)), for: .touchUpInside)
pipButton.layer.shadowColor = UIColor.black.cgColor
pipButton.layer.shadowOffset = CGSize(width: 0, height: 2) pipButton.layer.shadowColor = UIColor.black.cgColor
pipButton.layer.shadowOpacity = 0.6 pipButton.layer.shadowOffset = CGSize(width: 0, height: 2)
pipButton.layer.shadowRadius = 4 pipButton.layer.shadowOpacity = 0.6
pipButton.layer.masksToBounds = false pipButton.layer.shadowRadius = 4
pipButton.layer.masksToBounds = false
controlsContainerView.addSubview(pipButton)
pipButton.translatesAutoresizingMaskIntoConstraints = false controlsContainerView.addSubview(pipButton)
pipButton.translatesAutoresizingMaskIntoConstraints = false
// NEW: pin pipButton to the left of lockButton:
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
pipButton.centerYAnchor.constraint(equalTo: dimButton.centerYAnchor), pipButton.centerYAnchor.constraint(equalTo: dimButton.centerYAnchor),
pipButton.trailingAnchor.constraint(equalTo: dimButton.leadingAnchor, constant: -8), pipButton.trailingAnchor.constraint(equalTo: dimButton.leadingAnchor, constant: -8),
pipButton.widthAnchor.constraint(equalToConstant: 44), pipButton.widthAnchor.constraint(equalToConstant: 44),
pipButton.heightAnchor.constraint(equalToConstant: 44) pipButton.heightAnchor.constraint(equalToConstant: 44),
]) airplayButton.centerYAnchor.constraint(equalTo: pipButton.centerYAnchor),
airplayButton.trailingAnchor.constraint(equalTo: pipButton.leadingAnchor, constant: -8),
pipButton.isHidden = !isPipButtonVisible airplayButton.widthAnchor.constraint(equalToConstant: 44),
airplayButton.heightAnchor.constraint(equalToConstant: 44)
NotificationCenter.default.addObserver( ])
self,
selector: #selector(startPipIfNeeded), pipButton.isHidden = !isPipButtonVisible
name: UIApplication.willResignActiveNotification,
object: nil NotificationCenter.default.addObserver(
) self,
} selector: #selector(startPipIfNeeded),
name: UIApplication.willResignActiveNotification,
object: nil
)
}
func setupMenuButton() { func setupMenuButton() {
let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .bold) let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .bold)
@ -1749,13 +1766,13 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
pip.startPictureInPicture() pip.startPictureInPicture()
} }
} }
@objc private func startPipIfNeeded() { @objc private func startPipIfNeeded() {
guard isPipAutoEnabled, guard isPipAutoEnabled,
let pip = pipController, let pip = pipController,
!pip.isPictureInPictureActive else { !pip.isPictureInPictureActive else {
return return
} }
pip.startPictureInPicture() pip.startPictureInPicture()
} }
@ -1799,7 +1816,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
updateSkipButtonsVisibility() updateSkipButtonsVisibility()
} }
} }
@objc private func skipIntro() { @objc private func skipIntro() {
if let range = skipIntervals.op { if let range = skipIntervals.op {
player.seek(to: range.end) player.seek(to: range.end)
@ -1845,19 +1862,16 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
UIView.animate(withDuration: 0.25) { UIView.animate(withDuration: 0.25) {
self.blackCoverView.alpha = self.isDimmed ? 1.0 : 0.4 self.blackCoverView.alpha = self.isDimmed ? 1.0 : 0.4
// fade all controls (and lock button) in or out
for v in self.controlsToHide { v.alpha = self.isDimmed ? 0 : 1 } for v in self.controlsToHide { v.alpha = self.isDimmed ? 0 : 1 }
self.dimButton.alpha = self.isDimmed ? 0 : 1 self.dimButton.alpha = self.isDimmed ? 0 : 1
self.lockButton.alpha = self.isDimmed ? 0 : 1 self.lockButton.alpha = self.isDimmed ? 0 : 1
// switch subtitle constraints just like toggleControls()
self.subtitleBottomToSafeAreaConstraint?.isActive = !self.isControlsVisible self.subtitleBottomToSafeAreaConstraint?.isActive = !self.isControlsVisible
self.subtitleBottomToSliderConstraint?.isActive = self.isControlsVisible self.subtitleBottomToSliderConstraint?.isActive = self.isControlsVisible
self.view.layoutIfNeeded() self.view.layoutIfNeeded()
} }
// slide the dim-icon over
dimButtonToSlider.isActive = !isDimmed dimButtonToSlider.isActive = !isDimmed
dimButtonToRight.isActive = isDimmed dimButtonToRight.isActive = isDimmed
} }
@ -1877,17 +1891,17 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
private func tryAniListUpdate() { private func tryAniListUpdate() {
guard !aniListUpdatedSuccessfully else { return } guard !aniListUpdatedSuccessfully else { return }
guard aniListID > 0 else { guard aniListID > 0 else {
Logger.shared.log("AniList ID is invalid, skipping update.", type: "Warning") Logger.shared.log("AniList ID is invalid, skipping update.", type: "Warning")
return return
} }
let client = AniListMutation() let client = AniListMutation()
client.fetchMediaStatus(mediaId: aniListID) { [weak self] statusResult in client.fetchMediaStatus(mediaId: aniListID) { [weak self] statusResult in
guard let self = self else { return } guard let self = self else { return }
let newStatus: String = { let newStatus: String = {
switch statusResult { switch statusResult {
case .success(let mediaStatus): case .success(let mediaStatus):
@ -1895,7 +1909,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
return "CURRENT" return "CURRENT"
} }
return (self.episodeNumber == self.totalEpisodes) ? "COMPLETED" : "CURRENT" return (self.episodeNumber == self.totalEpisodes) ? "COMPLETED" : "CURRENT"
case .failure(let error): case .failure(let error):
Logger.shared.log( Logger.shared.log(
"Failed to fetch AniList status: \(error.localizedDescription). " + "Failed to fetch AniList status: \(error.localizedDescription). " +
@ -1918,26 +1932,26 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
"AniList progress updated to \(newStatus) for ep \(self.episodeNumber)", "AniList progress updated to \(newStatus) for ep \(self.episodeNumber)",
type: "General" type: "General"
) )
case .failure(let error): case .failure(let error):
let errorString = error.localizedDescription.lowercased() let errorString = error.localizedDescription.lowercased()
Logger.shared.log("AniList progress update failed: \(errorString)", type: "Error") Logger.shared.log("AniList progress update failed: \(errorString)", type: "Error")
if errorString.contains("access token not found") { if errorString.contains("access token not found") {
Logger.shared.log("AniList update will NOT retry due to missing token.", type: "Error") Logger.shared.log("AniList update will NOT retry due to missing token.", type: "Error")
self.aniListUpdateImpossible = true self.aniListUpdateImpossible = true
} else { } else {
if self.aniListRetryCount < self.aniListMaxRetries { if self.aniListRetryCount < self.aniListMaxRetries {
self.aniListRetryCount += 1 self.aniListRetryCount += 1
let delaySeconds = 5.0 let delaySeconds = 5.0
Logger.shared.log( Logger.shared.log(
"AniList update will retry in \(delaySeconds)s " + "AniList update will retry in \(delaySeconds)s " +
"(attempt \(self.aniListRetryCount)).", "(attempt \(self.aniListRetryCount)).",
type: "Debug" type: "Debug"
) )
DispatchQueue.main.asyncAfter(deadline: .now() + delaySeconds) { DispatchQueue.main.asyncAfter(deadline: .now() + delaySeconds) {
self.tryAniListUpdate() self.tryAniListUpdate()
} }
@ -2625,4 +2639,4 @@ extension CustomMediaPlayerViewController: AVPictureInPictureControllerDelegate
// The mind is the source of good and evil, only you yourself can decide which you will bring yourself. -seiike // The mind is the source of good and evil, only you yourself can decide which you will bring yourself. -seiike
// guys watch Clannad already - ibro // guys watch Clannad already - ibro
// May the Divine Providence bestow its infinite mercy upon your soul, and may eternal grace find you beyond the shadows of this mortal realm. - paul, 15/11/2005 - 13/05/2023 // May the Divine Providence bestow its infinite mercy upon your soul, and may eternal grace find you beyond the shadows of this mortal realm. - paul, 15/11/2005 - 13/05/2023
// this dumbass defo used gpt // this dumbass defo used gpt, ong he did bro