From 3da081b40cec54d0fcb7365ed9e8cb7fabb0a0fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20F=C3=B6rster?= Date: Wed, 1 May 2024 18:37:42 +0200 Subject: [PATCH] small delay before vertical scrubbing is possible This avoids accidentally scrubbing. The screen now needs to be touched at least 250 ms before time scrubbing is possible. should fix #393 --- Shared/Player/PlayerDragGesture.swift | 152 +++++++++++++++----------- Shared/Player/VideoPlayerView.swift | 10 +- 2 files changed, 97 insertions(+), 65 deletions(-) diff --git a/Shared/Player/PlayerDragGesture.swift b/Shared/Player/PlayerDragGesture.swift index 39e098e7..2fc0c575 100644 --- a/Shared/Player/PlayerDragGesture.swift +++ b/Shared/Player/PlayerDragGesture.swift @@ -3,81 +3,109 @@ import SwiftUI extension VideoPlayerView { var playerDragGesture: some Gesture { - DragGesture(minimumDistance: 30, coordinateSpace: .global) - #if os(iOS) + let longPressGesture = LongPressGesture(minimumDuration: 0) + .updating($isLongPressing) { currentState, gestureState, _ in + gestureState = currentState + } + .onEnded { _ in + if startTouchTime == nil { + startTouchTime = Date() + } + } + + let dragGesture = DragGesture(minimumDistance: 30, coordinateSpace: .global) .updating($dragGestureOffset) { value, state, _ in guard isVerticalDrag else { return } var translation = value.translation translation.height = max(0, translation.height) state = translation } - #endif .updating($dragGestureState) { _, state, _ in state = true } .onChanged { value in - guard player.presentingPlayer, - !controlsOverlayModel.presenting, - dragGestureState else { return } - - if player.controls.presentingControls, !player.musicMode { - player.controls.presentingControls = false - } - - if player.musicMode { - player.backend.stopControlsUpdates() - } - - let verticalDrag = value.translation.height - let horizontalDrag = value.translation.width - - #if os(iOS) - if viewDragOffset > 0, !isVerticalDrag { - isVerticalDrag = true - } - #endif - - if !isVerticalDrag, - horizontalPlayerGestureEnabled, - abs(horizontalDrag) > seekGestureSensitivity, - !isHorizontalDrag, - player.activeBackend == .mpv || !avPlayerUsesSystemControls - { - isHorizontalDrag = true - player.seek.onSeekGestureStart() - viewDragOffset = 0 - } - - if horizontalPlayerGestureEnabled, isHorizontalDrag { - player.seek.updateCurrentTime { - let time = player.backend.playerItemDuration?.seconds ?? 0 - if player.seek.gestureStart.isNil { - player.seek.gestureStart = time - } - let timeSeek = (time / player.playerSize.width) * horizontalDrag * seekGestureSpeed - - player.seek.gestureSeek = timeSeek - } - return - } - - guard verticalDrag > 0 else { return } - viewDragOffset = verticalDrag - - if verticalDrag > 60, - player.playingFullScreen - { - player.exitFullScreen(showControls: false) - #if os(iOS) - if Constants.isIPhone { - Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait) - } - #endif + if abs(value.translation.width) < abs(value.translation.height) { + processDragGesture(value) + } else if getElapsedTouchTime() >= 0.25 { + processDragGesture(value) } } - .onEnded { _ in - onPlayerDragGestureEnded() + .onEnded { value in + if abs(value.translation.width) < abs(value.translation.height) { + onPlayerDragGestureEnded() + } else if getElapsedTouchTime() >= 0.25 { + onPlayerDragGestureEnded() + } + + startTouchTime = nil } + return longPressGesture.sequenced(before: dragGesture) + } + + func getElapsedTouchTime() -> TimeInterval { + guard let startTouchTime = startTouchTime else { + return 0 + } + return Date().timeIntervalSince(startTouchTime) + } + + func processDragGesture(_ value: DragGesture.Value) { + guard player.presentingPlayer, + !controlsOverlayModel.presenting, + dragGestureState else { return } + + if player.controls.presentingControls, !player.musicMode { + player.controls.presentingControls = false + } + + if player.musicMode { + player.backend.stopControlsUpdates() + } + + let verticalDrag = value.translation.height + let horizontalDrag = value.translation.width + + #if os(iOS) + if viewDragOffset > 0, !isVerticalDrag { + isVerticalDrag = true + } + #endif + + if !isVerticalDrag, + horizontalPlayerGestureEnabled, + abs(horizontalDrag) > seekGestureSensitivity, + !isHorizontalDrag, + player.activeBackend == .mpv || !avPlayerUsesSystemControls + { + isHorizontalDrag = true + player.seek.onSeekGestureStart() + viewDragOffset = 0 + } + + if horizontalPlayerGestureEnabled, isHorizontalDrag { + player.seek.updateCurrentTime { + let time = player.backend.playerItemDuration?.seconds ?? 0 + if player.seek.gestureStart.isNil { + player.seek.gestureStart = time + } + let timeSeek = (time / player.playerSize.width) * horizontalDrag * seekGestureSpeed + + player.seek.gestureSeek = timeSeek + } + return + } + + guard verticalDrag > 0 else { return } + viewDragOffset = verticalDrag + + if verticalDrag > 60, player.playingFullScreen { + player.exitFullScreen(showControls: false) + #if os(iOS) + if Constants.isIPhone { + Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait) + } + #endif + } } func onPlayerDragGestureEnded() { diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index f9463d6a..37d92aa0 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -47,9 +47,13 @@ struct VideoPlayerView: View { #if !os(tvOS) @GestureState var dragGestureState = false @GestureState var dragGestureOffset = CGSize.zero - @State var isHorizontalDrag = false // swiftlint:disable:this swiftui_state_private - @State var isVerticalDrag = false // swiftlint:disable:this swiftui_state_private - @State var viewDragOffset = Self.hiddenOffset // swiftlint:disable:this swiftui_state_private + @GestureState var isLongPressing = false + // swiftlint:disable swiftui_state_private + @State var isHorizontalDrag = false + @State var isVerticalDrag = false + @State var viewDragOffset = Self.hiddenOffset + @State var startTouchTime: Date? = nil + // swiftlint:enable swiftui_state_private #endif @ObservedObject var player = PlayerModel.shared // swiftlint:disable:this swiftui_state_private