mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 08:32:00 +00:00
would this wrk?
This commit is contained in:
parent
ad2ade2f45
commit
92585beaba
1 changed files with 71 additions and 57 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue