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