added player gestures (#250)
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build macOS App (push) Has been cancelled

* added player gestures

* make opacity viewable but also allowing it to have transparency

* removed old labels

---------

Co-authored-by: cranci1 <100066266+cranci1@users.noreply.github.com>
This commit is contained in:
Mousica 2025-10-19 10:19:01 +03:00 committed by GitHub
parent 39e366e896
commit 0b7ca51ac8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -53,6 +53,10 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
private var isHoldPauseEnabled: Bool {
UserDefaults.standard.bool(forKey: "holdForPauseEnabled")
}
private var isChatGesturesEnabled: Bool {
UserDefaults.standard.bool(forKey: "chatGesturesEnabled")
}
private var isSkip85Visible: Bool {
if UserDefaults.standard.object(forKey: "skip85Visible") == nil {
@ -228,9 +232,20 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
private var endTimeIcon: UIImageView?
private var endTimeSeparator: UIView?
private var isEndTimeVisible: Bool = false
private var titleStackAboveSkipButtonConstraints: [NSLayoutConstraint] = []
private var titleStackAboveSliderConstraints: [NSLayoutConstraint] = []
private var volumeOverlay: UIView?
private var volumeLabel: UILabel?
private var volumeIcon: UIImageView?
private var brightnessOverlay: UIView?
private var brightnessLabel: UILabel?
private var brightnessIcon: UIImageView?
private var volumePanGesture: UIPanGestureRecognizer?
private var brightnessPanGesture: UIPanGestureRecognizer?
private var initialVolume: Float = 0.0
private var initialBrightness: CGFloat = 0.0
var episodeNumberLabel: UILabel!
var titleLabel: MarqueeLabel!
@ -287,7 +302,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
asset = AVURLAsset(url: url)
// Try to load OP/ED skip sidecar for local files
self.loadLocalSkipSidecar(for: url)
} else {
Logger.shared.log("Loading remote URL: \(url.absoluteString)", type: "Debug")
@ -358,6 +372,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
setupTimeBatteryIndicator()
setupTopRowLayout()
updateSkipButtonsVisibility()
if isChatGesturesEnabled {
setupVolumeOverlay()
setupBrightnessOverlay()
}
if !isSkip85Visible {
skip85Button.isHidden = true
@ -1062,7 +1081,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
doubleTapGesture.numberOfTapsRequired = 2
view.addGestureRecognizer(doubleTapGesture)
if let gestures = view.gestureRecognizers {
for gesture in gestures {
if let tapGesture = gesture as? UITapGestureRecognizer, tapGesture.numberOfTapsRequired == 1 {
@ -1071,19 +1090,33 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
}
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
if let introSwipe = skipIntroButton.gestureRecognizers?.first(
where: { $0 is UISwipeGestureRecognizer && ($0 as! UISwipeGestureRecognizer).direction == .left }
),
let outroSwipe = skipOutroButton.gestureRecognizers?.first(
where: { $0 is UISwipeGestureRecognizer && ($0 as! UISwipeGestureRecognizer).direction == .left }
) {
let outroSwipe = skipOutroButton.gestureRecognizers?.first(
where: { $0 is UISwipeGestureRecognizer && ($0 as! UISwipeGestureRecognizer).direction == .left }
) {
panGesture.require(toFail: introSwipe)
panGesture.require(toFail: outroSwipe)
}
view.addGestureRecognizer(panGesture)
if isChatGesturesEnabled {
volumePanGesture = UIPanGestureRecognizer(target: self, action: #selector(handleVolumePan(_:)))
volumePanGesture?.delegate = self
if let volumePanGesture = volumePanGesture {
view.addGestureRecognizer(volumePanGesture)
}
brightnessPanGesture = UIPanGestureRecognizer(target: self, action: #selector(handleBrightnessPan(_:)))
brightnessPanGesture?.delegate = self
if let brightnessPanGesture = brightnessPanGesture {
view.addGestureRecognizer(brightnessPanGesture)
}
}
}
func showSkipFeedback(direction: String) {
@ -2631,7 +2664,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
asset = AVURLAsset(url: url)
// Try to load OP/ED skip sidecar for local files
self.loadLocalSkipSidecar(for: url)
} else {
Logger.shared.log("Switching to remote URL: \(url.absoluteString)", type: "Debug")
@ -3073,7 +3105,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
@objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
switch gesture.state {
case .began:
resetControlsInactivityTimer()
@ -3085,6 +3117,74 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
break
}
}
@objc private func handleVolumePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
let location = gesture.location(in: view)
let screenWidth = view.bounds.width
let rightZone = CGRect(
x: screenWidth * 0.8,
y: 0,
width: screenWidth * 0.2,
height: view.bounds.height
)
guard rightZone.contains(location) else {
return
}
switch gesture.state {
case .began:
initialVolume = audioSession.outputVolume
showVolumeOverlay()
case .changed:
let deltaY = -translation.y
let sensitivity: Float = 0.005
let volumeChange = Float(deltaY) * sensitivity
let newVolume = max(0, min(1, initialVolume + volumeChange))
systemVolumeSlider?.value = newVolume
updateVolumeOverlay(volume: newVolume)
case .ended:
hideVolumeOverlay()
default:
break
}
}
@objc private func handleBrightnessPan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
let location = gesture.location(in: view)
let screenWidth = view.bounds.width
let leftZone = CGRect(
x: 0,
y: 0,
width: screenWidth * 0.2,
height: view.bounds.height
)
guard leftZone.contains(location) else {
return
}
switch gesture.state {
case .began:
initialBrightness = UIScreen.main.brightness
showBrightnessOverlay()
case .changed:
let deltaY = -translation.y
let sensitivity: CGFloat = 0.005
let brightnessChange = deltaY * sensitivity
let newBrightness = max(0, min(1, initialBrightness + brightnessChange))
UIScreen.main.brightness = newBrightness
updateBrightnessOverlay(brightness: newBrightness)
case .ended:
hideBrightnessOverlay()
default:
break
}
}
private func beginHoldSpeed() {
guard let player = player else { return }
@ -3397,6 +3497,146 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
batteryLabel?.text = "N/A"
}
}
private func setupVolumeOverlay() {
volumeOverlay = UIView()
volumeOverlay?.backgroundColor = UIColor.black.withAlphaComponent(0.7)
volumeOverlay?.layer.cornerRadius = 20
volumeOverlay?.isHidden = true
volumeOverlay?.alpha = 0
volumeOverlay?.translatesAutoresizingMaskIntoConstraints = false
if let volumeOverlay = volumeOverlay {
view.addSubview(volumeOverlay)
NSLayoutConstraint.activate([
volumeOverlay.centerXAnchor.constraint(equalTo: view.centerXAnchor),
volumeOverlay.centerYAnchor.constraint(equalTo: view.centerYAnchor),
volumeOverlay.widthAnchor.constraint(equalToConstant: 120),
volumeOverlay.heightAnchor.constraint(equalToConstant: 120)
])
volumeIcon = UIImageView(image: UIImage(systemName: "speaker.wave.2.fill"))
volumeIcon?.tintColor = .white
volumeIcon?.contentMode = .scaleAspectFit
volumeIcon?.translatesAutoresizingMaskIntoConstraints = false
if let volumeIcon = volumeIcon {
volumeOverlay.addSubview(volumeIcon)
NSLayoutConstraint.activate([
volumeIcon.topAnchor.constraint(equalTo: volumeOverlay.topAnchor, constant: 20),
volumeIcon.centerXAnchor.constraint(equalTo: volumeOverlay.centerXAnchor),
volumeIcon.widthAnchor.constraint(equalToConstant: 40),
volumeIcon.heightAnchor.constraint(equalToConstant: 40)
])
}
volumeLabel = UILabel()
volumeLabel?.textColor = .white
volumeLabel?.font = UIFont.systemFont(ofSize: 24, weight: .bold)
volumeLabel?.textAlignment = .center
volumeLabel?.translatesAutoresizingMaskIntoConstraints = false
if let volumeLabel = volumeLabel {
volumeOverlay.addSubview(volumeLabel)
NSLayoutConstraint.activate([
volumeLabel.topAnchor.constraint(equalTo: volumeIcon?.bottomAnchor ?? volumeOverlay.topAnchor, constant: 10),
volumeLabel.centerXAnchor.constraint(equalTo: volumeOverlay.centerXAnchor),
volumeLabel.bottomAnchor.constraint(equalTo: volumeOverlay.bottomAnchor, constant: -20)
])
}
}
}
private func setupBrightnessOverlay() {
brightnessOverlay = UIView()
brightnessOverlay?.backgroundColor = UIColor.black.withAlphaComponent(0.7)
brightnessOverlay?.layer.cornerRadius = 20
brightnessOverlay?.isHidden = true
brightnessOverlay?.alpha = 0
brightnessOverlay?.translatesAutoresizingMaskIntoConstraints = false
if let brightnessOverlay = brightnessOverlay {
view.addSubview(brightnessOverlay)
NSLayoutConstraint.activate([
brightnessOverlay.centerXAnchor.constraint(equalTo: view.centerXAnchor),
brightnessOverlay.centerYAnchor.constraint(equalTo: view.centerYAnchor),
brightnessOverlay.widthAnchor.constraint(equalToConstant: 120),
brightnessOverlay.heightAnchor.constraint(equalToConstant: 120)
])
brightnessIcon = UIImageView(image: UIImage(systemName: "sun.max.fill"))
brightnessIcon?.tintColor = .white
brightnessIcon?.contentMode = .scaleAspectFit
brightnessIcon?.translatesAutoresizingMaskIntoConstraints = false
if let brightnessIcon = brightnessIcon {
brightnessOverlay.addSubview(brightnessIcon)
NSLayoutConstraint.activate([
brightnessIcon.topAnchor.constraint(equalTo: brightnessOverlay.topAnchor, constant: 20),
brightnessIcon.centerXAnchor.constraint(equalTo: brightnessOverlay.centerXAnchor),
brightnessIcon.widthAnchor.constraint(equalToConstant: 40),
brightnessIcon.heightAnchor.constraint(equalToConstant: 40)
])
}
brightnessLabel = UILabel()
brightnessLabel?.textColor = .white
brightnessLabel?.font = UIFont.systemFont(ofSize: 24, weight: .bold)
brightnessLabel?.textAlignment = .center
brightnessLabel?.translatesAutoresizingMaskIntoConstraints = false
if let brightnessLabel = brightnessLabel {
brightnessOverlay.addSubview(brightnessLabel)
NSLayoutConstraint.activate([
brightnessLabel.topAnchor.constraint(equalTo: brightnessIcon?.bottomAnchor ?? brightnessOverlay.topAnchor, constant: 10),
brightnessLabel.centerXAnchor.constraint(equalTo: brightnessOverlay.centerXAnchor),
brightnessLabel.bottomAnchor.constraint(equalTo: brightnessOverlay.bottomAnchor, constant: -20)
])
}
}
}
private func showVolumeOverlay() {
volumeOverlay?.isHidden = false
UIView.animate(withDuration: 0.2) {
self.volumeOverlay?.alpha = 0.8
}
}
private func hideVolumeOverlay() {
UIView.animate(withDuration: 0.2, animations: {
self.volumeOverlay?.alpha = 0.0
}) { _ in
self.volumeOverlay?.isHidden = true
}
}
private func updateVolumeOverlay(volume: Float) {
let percentage = Int(volume * 100)
volumeLabel?.text = "\(percentage)%"
}
private func showBrightnessOverlay() {
brightnessOverlay?.isHidden = false
UIView.animate(withDuration: 0.2) {
self.brightnessOverlay?.alpha = 0.8
}
}
private func hideBrightnessOverlay() {
UIView.animate(withDuration: 0.2, animations: {
self.brightnessOverlay?.alpha = 0.0
}) { _ in
self.brightnessOverlay?.isHidden = true
}
}
private func updateBrightnessOverlay(brightness: CGFloat) {
let percentage = Int(brightness * 100)
brightnessLabel?.text = "\(percentage)%"
}
@objc private func handlePlaybackEnded() {
guard isAutoplayEnabled else { return }
@ -3711,6 +3951,20 @@ extension CustomMediaPlayerViewController {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let location = touch.location(in: view)
if isChatGesturesEnabled {
if gestureRecognizer == volumePanGesture {
return location.x > view.bounds.width / 2
} else if gestureRecognizer == brightnessPanGesture {
return location.x <= view.bounds.width / 2
}
}
return true
}
}
// yes? Like the plural of the famous american rapper ye? -IBHRAD
@ -3847,8 +4101,6 @@ class GradientBlurButton: UIButton {
}
}
/// Load OP/ED skip data from a simple sidecar JSON saved next to the local video (if present)
extension CustomMediaPlayerViewController {
private struct SkipSidecar: Decodable {