mirror of
https://github.com/cranci1/Sora.git
synced 2026-01-11 20:10:24 +00:00
added player gestures (#250)
* 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:
parent
39e366e896
commit
0b7ca51ac8
1 changed files with 264 additions and 12 deletions
|
|
@ -53,6 +53,10 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
private var isHoldPauseEnabled: Bool {
|
private var isHoldPauseEnabled: Bool {
|
||||||
UserDefaults.standard.bool(forKey: "holdForPauseEnabled")
|
UserDefaults.standard.bool(forKey: "holdForPauseEnabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isChatGesturesEnabled: Bool {
|
||||||
|
UserDefaults.standard.bool(forKey: "chatGesturesEnabled")
|
||||||
|
}
|
||||||
|
|
||||||
private var isSkip85Visible: Bool {
|
private var isSkip85Visible: Bool {
|
||||||
if UserDefaults.standard.object(forKey: "skip85Visible") == nil {
|
if UserDefaults.standard.object(forKey: "skip85Visible") == nil {
|
||||||
|
|
@ -228,9 +232,20 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
private var endTimeIcon: UIImageView?
|
private var endTimeIcon: UIImageView?
|
||||||
private var endTimeSeparator: UIView?
|
private var endTimeSeparator: UIView?
|
||||||
private var isEndTimeVisible: Bool = false
|
private var isEndTimeVisible: Bool = false
|
||||||
|
|
||||||
private var titleStackAboveSkipButtonConstraints: [NSLayoutConstraint] = []
|
private var titleStackAboveSkipButtonConstraints: [NSLayoutConstraint] = []
|
||||||
private var titleStackAboveSliderConstraints: [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 episodeNumberLabel: UILabel!
|
||||||
var titleLabel: MarqueeLabel!
|
var titleLabel: MarqueeLabel!
|
||||||
|
|
@ -287,7 +302,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
}
|
}
|
||||||
|
|
||||||
asset = AVURLAsset(url: url)
|
asset = AVURLAsset(url: url)
|
||||||
// Try to load OP/ED skip sidecar for local files
|
|
||||||
self.loadLocalSkipSidecar(for: url)
|
self.loadLocalSkipSidecar(for: url)
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("Loading remote URL: \(url.absoluteString)", type: "Debug")
|
Logger.shared.log("Loading remote URL: \(url.absoluteString)", type: "Debug")
|
||||||
|
|
@ -358,6 +372,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
setupTimeBatteryIndicator()
|
setupTimeBatteryIndicator()
|
||||||
setupTopRowLayout()
|
setupTopRowLayout()
|
||||||
updateSkipButtonsVisibility()
|
updateSkipButtonsVisibility()
|
||||||
|
|
||||||
|
if isChatGesturesEnabled {
|
||||||
|
setupVolumeOverlay()
|
||||||
|
setupBrightnessOverlay()
|
||||||
|
}
|
||||||
|
|
||||||
if !isSkip85Visible {
|
if !isSkip85Visible {
|
||||||
skip85Button.isHidden = true
|
skip85Button.isHidden = true
|
||||||
|
|
@ -1062,7 +1081,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
|
let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
|
||||||
doubleTapGesture.numberOfTapsRequired = 2
|
doubleTapGesture.numberOfTapsRequired = 2
|
||||||
view.addGestureRecognizer(doubleTapGesture)
|
view.addGestureRecognizer(doubleTapGesture)
|
||||||
|
|
||||||
if let gestures = view.gestureRecognizers {
|
if let gestures = view.gestureRecognizers {
|
||||||
for gesture in gestures {
|
for gesture in gestures {
|
||||||
if let tapGesture = gesture as? UITapGestureRecognizer, tapGesture.numberOfTapsRequired == 1 {
|
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(_:)))
|
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
|
||||||
if let introSwipe = skipIntroButton.gestureRecognizers?.first(
|
if let introSwipe = skipIntroButton.gestureRecognizers?.first(
|
||||||
where: { $0 is UISwipeGestureRecognizer && ($0 as! UISwipeGestureRecognizer).direction == .left }
|
where: { $0 is UISwipeGestureRecognizer && ($0 as! UISwipeGestureRecognizer).direction == .left }
|
||||||
),
|
),
|
||||||
let outroSwipe = skipOutroButton.gestureRecognizers?.first(
|
let outroSwipe = skipOutroButton.gestureRecognizers?.first(
|
||||||
where: { $0 is UISwipeGestureRecognizer && ($0 as! UISwipeGestureRecognizer).direction == .left }
|
where: { $0 is UISwipeGestureRecognizer && ($0 as! UISwipeGestureRecognizer).direction == .left }
|
||||||
) {
|
) {
|
||||||
panGesture.require(toFail: introSwipe)
|
panGesture.require(toFail: introSwipe)
|
||||||
panGesture.require(toFail: outroSwipe)
|
panGesture.require(toFail: outroSwipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
view.addGestureRecognizer(panGesture)
|
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) {
|
func showSkipFeedback(direction: String) {
|
||||||
|
|
@ -2631,7 +2664,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
}
|
}
|
||||||
|
|
||||||
asset = AVURLAsset(url: url)
|
asset = AVURLAsset(url: url)
|
||||||
// Try to load OP/ED skip sidecar for local files
|
|
||||||
self.loadLocalSkipSidecar(for: url)
|
self.loadLocalSkipSidecar(for: url)
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("Switching to remote URL: \(url.absoluteString)", type: "Debug")
|
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) {
|
@objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
|
||||||
let translation = gesture.translation(in: view)
|
let translation = gesture.translation(in: view)
|
||||||
|
|
||||||
switch gesture.state {
|
switch gesture.state {
|
||||||
case .began:
|
case .began:
|
||||||
resetControlsInactivityTimer()
|
resetControlsInactivityTimer()
|
||||||
|
|
@ -3085,6 +3117,74 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
break
|
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() {
|
private func beginHoldSpeed() {
|
||||||
guard let player = player else { return }
|
guard let player = player else { return }
|
||||||
|
|
@ -3397,6 +3497,146 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
batteryLabel?.text = "N/A"
|
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() {
|
@objc private func handlePlaybackEnded() {
|
||||||
guard isAutoplayEnabled else { return }
|
guard isAutoplayEnabled else { return }
|
||||||
|
|
@ -3711,6 +3951,20 @@ extension CustomMediaPlayerViewController {
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
return true
|
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
|
// 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 {
|
extension CustomMediaPlayerViewController {
|
||||||
|
|
||||||
private struct SkipSidecar: Decodable {
|
private struct SkipSidecar: Decodable {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue