6 changed files with 207 additions and 190 deletions
-
108kplayer/Images.xcassets/AppIcon.appiconset/Contents.json
-
10kplayer/core/KSettings.swift
-
2kplayer/core/KSettingsModel.swift
-
6kplayer/master/KSettingsView.swift
-
97kplayer/video/PlayerInteractionMode.swift
-
174kplayer/video/SVideoPlayer.swift
@ -0,0 +1,97 @@ |
|||
// |
|||
// Created by Marco Schmickler |
|||
// Copyright (c) 2024 Marco Schmickler. All rights reserved. |
|||
// |
|||
|
|||
import CoreGraphics |
|||
|
|||
// MARK: - DragAction |
|||
|
|||
/// What a drag gesture should do, resolved independently of AVKit/SwiftUI. |
|||
enum DragAction { |
|||
case pan // caller updates model.dragOffset |
|||
case scrub(to: Double) // seekTimeSmoothly — frame-accurate scrub |
|||
case step(to: Double) // seekTime — discrete jump |
|||
case jumpSnapshot(Int) // +1 next / -1 previous |
|||
case none |
|||
} |
|||
|
|||
// MARK: - DragInput |
|||
|
|||
/// Pure inputs for interpreting a drag. No AVKit or SwiftUI types. |
|||
struct DragInput { |
|||
let translation: CGSize |
|||
let start: CGPoint |
|||
let currentTime: Double? // nil when player has no item |
|||
let anchorTime: Double // smoothTime baseline for scrubbing |
|||
let seeking: Bool |
|||
} |
|||
|
|||
// MARK: - InteractionMode |
|||
|
|||
/// One set of drag rules. Each mode is a value type with named constants, |
|||
/// so tuning a threshold is a one-line change in one place. |
|||
protocol InteractionMode { |
|||
func resolve(_ input: DragInput) -> DragAction |
|||
} |
|||
|
|||
// MARK: - ScrubMode (paused) |
|||
|
|||
/// When paused, dragging scrubs the timeline. Panning is only unlocked when |
|||
/// the video is zoomed or in crop-preview ("small") mode. |
|||
struct ScrubMode: InteractionMode { |
|||
/// Top zone height in points — finer scrub sensitivity above this line. |
|||
/// 100 when zoomed/fullscreen, 170 otherwise. |
|||
var fineZoneHeight: CGFloat = 170 |
|||
/// Whether the lower zone should pan instead of coarse-scrub. |
|||
var allowsPan: Bool = false |
|||
/// Drag-to-seconds ratio for the fine zone (upper). |
|||
var fineDivisor: Double = 100 |
|||
/// Drag-to-seconds ratio for the coarse zone (lower). |
|||
var coarseDivisor: Double = 30 |
|||
|
|||
func resolve(_ input: DragInput) -> DragAction { |
|||
// Match old behaviour: if the player has no time, fall through to pan. |
|||
guard input.currentTime != nil else { return .pan } |
|||
let d = Double(input.translation.width + input.translation.height) |
|||
if input.start.y < fineZoneHeight { |
|||
return .scrub(to: input.anchorTime + d / fineDivisor) |
|||
} |
|||
if allowsPan { return .pan } |
|||
return .scrub(to: input.anchorTime + d / coarseDivisor) |
|||
} |
|||
} |
|||
|
|||
// MARK: - PlayMode (playing) |
|||
|
|||
/// When playing, a vertical drag can jump to the prev/next snapshot, |
|||
/// a horizontal drag step-seeks, and the lower portion pans when zoomed. |
|||
struct PlayMode: InteractionMode { |
|||
/// Whether the lower area should pan instead of seeking. |
|||
var allowsPan: Bool = false |
|||
/// Y-coordinate above which pan is NOT allowed (upper area → seek only). |
|||
var panZoneTop: CGFloat = 300 |
|||
/// Whether vertical drags should jump to adjacent snapshots. |
|||
var jumpEnabled: Bool = false |
|||
/// Minimum vertical drag distance (pts) to trigger a snapshot jump. |
|||
var jumpThreshold: CGFloat = 100 |
|||
/// Minimum horizontal drag distance (pts) to trigger a step seek. |
|||
var stepThreshold: Double = 40 |
|||
/// Seconds to seek forward on a rightward drag. |
|||
var stepForward: Double = 30 |
|||
/// Seconds to seek backward on a leftward drag. |
|||
var stepBackward: Double = 30 |
|||
|
|||
func resolve(_ input: DragInput) -> DragAction { |
|||
let d = input.translation |
|||
if jumpEnabled { |
|||
if d.height > jumpThreshold { return .jumpSnapshot(+1) } |
|||
if d.height < -jumpThreshold { return .jumpSnapshot(-1) } |
|||
} |
|||
guard let t = input.currentTime, !input.seeking else { return .none } |
|||
if allowsPan && input.start.y > panZoneTop { return .pan } |
|||
if d.width > CGFloat(stepThreshold) { return .step(to: t + stepForward) } |
|||
if d.width < -CGFloat(stepThreshold) { return .step(to: t - stepBackward) } |
|||
return .none |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue