From f512faba06900cc99d093416dcae67147405c9be Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:59:26 +0200 Subject: [PATCH] =?UTF-8?q?i=20despise=20unclean=20merges=20=F0=9F=99=8F?= =?UTF-8?q?=20(also=20heres=20ur=20fidget=20spinner)=20(#83)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomPlayer/CustomPlayer.swift | 127 +++++++++++------- .../SettingsSubViews/SettingsViewPlayer.swift | 2 +- 2 files changed, 79 insertions(+), 50 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 08fcf00..929b00b 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -61,23 +61,18 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele private var isDoubleTapSkipEnabled: Bool { if UserDefaults.standard.object(forKey: "doubleTapSeekEnabled") == nil { - return true + return false } return UserDefaults.standard.bool(forKey: "doubleTapSeekEnabled") } - var showWatchNextButton = true - var watchNextButtonTimer: Timer? - var isWatchNextRepositioned: Bool = false - var isWatchNextVisible: Bool = false - var lastDuration: Double = 0.0 - var watchNextButtonAppearedAt: Double? - var portraitButtonVisibleConstraints: [NSLayoutConstraint] = [] var portraitButtonHiddenConstraints: [NSLayoutConstraint] = [] var landscapeButtonVisibleConstraints: [NSLayoutConstraint] = [] var landscapeButtonHiddenConstraints: [NSLayoutConstraint] = [] var currentMarqueeConstraints: [NSLayoutConstraint] = [] + private var currentMenuButtonTrailing: NSLayoutConstraint! + var subtitleForegroundColor: String = "white" var subtitleBackgroundEnabled: Bool = true @@ -287,6 +282,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } else { marqueeLabel.lineBreakMode = .byClipping } + updateMenuButtonConstraints() } override func viewDidAppear(_ animated: Bool) { @@ -336,8 +332,17 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele DispatchQueue.main.async { [weak self] in guard let self = self else { return } if self.qualityButton.isHidden && self.isHLSStream { + // 1) reveal the quality button self.qualityButton.isHidden = false self.qualityButton.menu = self.qualitySelectionMenu() + + // 2) update the trailing constraint for the menuButton + self.updateMenuButtonConstraints() + + // 3) animate the shift + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) { + self.view.layoutIfNeeded() + } } } } @@ -420,7 +425,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele let playPauseTap = UITapGestureRecognizer(target: self, action: #selector(togglePlayPause)) playPauseTap.delaysTouchesBegan = false - playPauseTap.delegate = self // Ensure you return true in shouldRecognizeSimultaneouslyWith + playPauseTap.delegate = self playPauseButton.addGestureRecognizer(playPauseTap) @@ -809,10 +814,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele NSLayoutConstraint.activate([ menuButton.topAnchor.constraint(equalTo: qualityButton.topAnchor), - menuButton.trailingAnchor.constraint(equalTo: qualityButton.leadingAnchor, constant: -6), menuButton.widthAnchor.constraint(equalToConstant: 40), - menuButton.heightAnchor.constraint(equalToConstant: 40) + menuButton.heightAnchor.constraint(equalToConstant: 40), ]) + + currentMenuButtonTrailing = menuButton.trailingAnchor.constraint(equalTo: qualityButton.leadingAnchor, constant: -6) } func setupSpeedButton() { @@ -916,11 +922,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele qualityButton.menu = qualitySelectionMenu() qualityButton.isHidden = true - speedButton.layer.shadowColor = UIColor.black.cgColor - speedButton.layer.shadowOffset = CGSize(width: 0, height: 2) - speedButton.layer.shadowOpacity = 0.6 - speedButton.layer.shadowRadius = 4 - speedButton.layer.masksToBounds = false + qualityButton.layer.shadowColor = UIColor.black.cgColor + qualityButton.layer.shadowOffset = CGSize(width: 0, height: 2) + qualityButton.layer.shadowOpacity = 0.6 + qualityButton.layer.shadowRadius = 4 + qualityButton.layer.masksToBounds = false controlsContainerView.addSubview(qualityButton) qualityButton.translatesAutoresizingMaskIntoConstraints = false @@ -1040,37 +1046,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } ) } - - let isNearEnd = (self.duration - self.currentTimeVal) <= (self.duration * 0.10) - && self.currentTimeVal != self.duration - && self.showWatchNextButton - && self.duration != 0 - - - if isNearEnd { - if !self.isWatchNextVisible { - self.watchNextButtonAppearedAt = self.currentTimeVal - - } - } else { - - } } } - func repositionWatchNextButton() { - self.isWatchNextRepositioned = true - UIView.animate(withDuration: 0.3, animations: { - NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) - NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) - self.view.layoutIfNeeded() - self.watchNextButton.alpha = 0.0 - }, completion: { _ in - self.watchNextButton.isHidden = true - }) - self.watchNextButtonTimer?.invalidate() - self.watchNextButtonTimer = nil - } func startUpdateTimer() { updateTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in @@ -1079,6 +1057,25 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } } + func updateMenuButtonConstraints() { + // tear down last one + currentMenuButtonTrailing.isActive = false + + // pick the “next” visible control + let anchor: NSLayoutXAxisAnchor + if !qualityButton.isHidden { + anchor = qualityButton.leadingAnchor + } else if !speedButton.isHidden { + anchor = speedButton.leadingAnchor + } else { + anchor = controlsContainerView.trailingAnchor + } + + // rebuild & activate + currentMenuButtonTrailing = menuButton.trailingAnchor.constraint(equalTo: anchor, constant: -6) + currentMenuButtonTrailing.isActive = true + } + @objc func toggleControls() { isControlsVisible.toggle() UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: { @@ -1096,6 +1093,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] finished in guard self != nil else { return } } + animateButtonRotation(backwardButton, clockwise: false) } } @@ -1107,6 +1105,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] finished in guard self != nil else { return } } + animateButtonRotation(forwardButton) } } @@ -1117,6 +1116,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] finished in guard self != nil else { return } } + animateButtonRotation(backwardButton, clockwise: false) } @objc func seekForward() { @@ -1125,6 +1125,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele currentTimeVal = min(currentTimeVal + finalSkip, duration) player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] finished in guard self != nil else { return } } + animateButtonRotation(forwardButton) } @objc func handleDoubleTap(_ gesture: UITapGestureRecognizer) { @@ -1240,6 +1241,30 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } } + private func animateButtonRotation(_ button: UIView, clockwise: Bool = true) { + if button.layer.animation(forKey: "rotate360") != nil { + return + } + button.superview?.layoutIfNeeded() + + button.layer.shouldRasterize = true + button.layer.rasterizationScale = UIScreen.main.scale + button.layer.allowsEdgeAntialiasing = true + + let rotation = CABasicAnimation(keyPath: "transform.rotation.z") + rotation.fromValue = 0 + rotation.toValue = CGFloat.pi * 2 * (clockwise ? 1 : -1) + rotation.duration = 0.43 + rotation.timingFunction = CAMediaTimingFunction(name: .linear) + + button.layer.add(rotation, forKey: "rotate360") + + DispatchQueue.main.asyncAfter(deadline: .now() + rotation.duration) { + button.layer.shouldRasterize = false + } + } + + private func parseM3U8(url: URL, completion: @escaping () -> Void) { var request = URLRequest(url: url) request.addValue("\(module.metadata.baseUrl)", forHTTPHeaderField: "Referer") @@ -1403,19 +1428,23 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele parseM3U8(url: url) { [weak self] in guard let self = self else { return } - - if let lastSelectedQuality = UserDefaults.standard.string(forKey: "lastSelectedQuality"), - self.qualities.contains(where: { $0.1 == lastSelectedQuality }) { - self.switchToQuality(urlString: lastSelectedQuality) + if let last = UserDefaults.standard.string(forKey: "lastSelectedQuality"), + self.qualities.contains(where: { $0.1 == last }) { + self.switchToQuality(urlString: last) } + // reveal + animate self.qualityButton.isHidden = false self.qualityButton.menu = self.qualitySelectionMenu() - self.updateMarqueeConstraints() + self.updateMenuButtonConstraints() + UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) { + self.view.layoutIfNeeded() + } } } else { isHLSStream = false qualityButton.isHidden = true + updateMenuButtonConstraints() } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 69deaf4..d425931 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -16,7 +16,7 @@ struct SettingsViewPlayer: View { @AppStorage("skipIncrementHold") private var skipIncrementHold: Double = 30.0 @AppStorage("holdForPauseEnabled") private var holdForPauseEnabled = false @AppStorage("skip85Visible") private var skip85Visible: Bool = true - @AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = true + @AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"]