feat: segment playback slider with resolved YouTube chapters
This commit is contained in:
+22
-13
@@ -76,6 +76,15 @@ final class PlayerModel: ObservableObject {
|
||||
currentMedia?.url.absoluteString == metadataLifecycle.identity else { return [] }
|
||||
return metadataLifecycle.visibleIntervals(duration: state.duration)
|
||||
}
|
||||
var sliderBoundaries: [Double] {
|
||||
guard !isBusy, state.status != PLAYER_EMPTY, state.status != PLAYER_ERROR,
|
||||
state.duration.isFinite, state.duration > 0 else { return [] }
|
||||
if case .youtube(let video, _) = currentMedia {
|
||||
return video.chapterBoundaries(duration: state.duration)
|
||||
}
|
||||
return Array(Set(visibleIntervals.flatMap { [$0.start, $0.end] }
|
||||
.filter { $0 > 0 && $0 < state.duration })).sorted()
|
||||
}
|
||||
@Published private var skipOffer: (interval: MiruroInterval, deadline: Double)?
|
||||
private var offeredSegments: Set<MiruroInterval.Kind> = []
|
||||
private let skipClock: () -> Double
|
||||
@@ -2336,7 +2345,7 @@ private struct PlaybackSlider: NSViewRepresentable {
|
||||
var volume = false
|
||||
var enabled = true
|
||||
var bufferedRange: ClosedRange<Double>?
|
||||
var intervals: [MiruroInterval] = []
|
||||
var boundaries: [Double] = []
|
||||
var onEditingChanged: (Bool) -> Void = { _ in }
|
||||
|
||||
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
||||
@@ -2360,9 +2369,9 @@ private struct PlaybackSlider: NSViewRepresentable {
|
||||
slider.isEnabled = enabled
|
||||
slider.editingChanged = onEditingChanged
|
||||
if let cell = slider.cell as? Cell,
|
||||
cell.bufferedRange != bufferedRange || cell.intervals != intervals {
|
||||
cell.bufferedRange != bufferedRange || cell.boundaries != boundaries {
|
||||
cell.bufferedRange = bufferedRange
|
||||
cell.intervals = intervals
|
||||
cell.boundaries = boundaries
|
||||
slider.needsDisplay = true
|
||||
}
|
||||
slider.setAccessibilityLabel(volume ? "Volume" : "Playback Position")
|
||||
@@ -2433,7 +2442,7 @@ private struct PlaybackSlider: NSViewRepresentable {
|
||||
final class Cell: NSSliderCell {
|
||||
var volume = false
|
||||
var bufferedRange: ClosedRange<Double>?
|
||||
var intervals: [MiruroInterval] = []
|
||||
var boundaries: [Double] = []
|
||||
#if PLAYER_CHECK
|
||||
var renderedKnob = NSRect.zero
|
||||
#endif
|
||||
@@ -2450,16 +2459,15 @@ private struct PlaybackSlider: NSViewRepresentable {
|
||||
defer { NSGraphicsContext.restoreGraphicsState() }
|
||||
if !volume, maxValue > minValue, bar.width > 0 {
|
||||
let scale = bar.width / (maxValue - minValue)
|
||||
let boundaries = Array(Set([minValue, maxValue] + intervals.flatMap {
|
||||
[$0.start, $0.end]
|
||||
}.filter { $0 > minValue && $0 < maxValue })).sorted()
|
||||
let track = NSBezierPath()
|
||||
for index in 0..<(boundaries.count - 1) {
|
||||
let start = bar.minX + (boundaries[index] - minValue) * scale
|
||||
let end = bar.minX + (boundaries[index + 1] - minValue) * scale
|
||||
for index in 0...boundaries.count {
|
||||
let startTime = index == 0 ? minValue : boundaries[index - 1]
|
||||
let endTime = index == boundaries.count ? maxValue : boundaries[index]
|
||||
let start = bar.minX + (startTime - minValue) * scale
|
||||
let end = bar.minX + (endTime - minValue) * scale
|
||||
let inset = min(1.5, (end - start) / 4)
|
||||
let left = index == 0 ? start : start + inset
|
||||
let right = index == boundaries.count - 2 ? end : end - inset
|
||||
let right = index == boundaries.count ? end : end - inset
|
||||
track.append(NSBezierPath(roundedRect:
|
||||
NSRect(x: left, y: bar.minY, width: right - left, height: bar.height),
|
||||
xRadius: 1.5, yRadius: 1.5))
|
||||
@@ -2518,8 +2526,9 @@ func playbackSliderHitCheck() -> Bool {
|
||||
for (initial, offset, movement): (Double, CGFloat, CGFloat) in [
|
||||
(50, -4, 0), (50, -2, 0), (50, -1, 0), (50, 0, 0), (50, 1, 0), (50, 2, 0), (50, 4, 0),
|
||||
(20, -1, -20), (80, 1, 20), (20, -4, -20), (80, 4, 20),
|
||||
(20, 0, -500), (80, 0, 500)
|
||||
(20, 0, -500), (80, 0, 500), (25, 0, 0), (25, 0, 50), (75, 0, -50)
|
||||
] {
|
||||
cell.boundaries = initial == 25 || initial == 75 ? [25, 75] : []
|
||||
slider.doubleValue = initial
|
||||
let knob = cell.knobRect(flipped: slider.isFlipped)
|
||||
let point = slider.convert(NSPoint(x: knob.midX + offset, y: knob.midY), to: nil)
|
||||
@@ -3154,7 +3163,7 @@ private struct PlayerContent: View {
|
||||
HStack(spacing: 10) {
|
||||
Text(time(model.seekPosition)).frame(minWidth: 32, alignment: .trailing)
|
||||
PlaybackSlider(value: $model.seekPosition, range: 0...max(model.duration, 0.001),
|
||||
enabled: model.canSeek, bufferedRange: buffered, intervals: intervals, onEditingChanged: { editing in
|
||||
enabled: model.canSeek, bufferedRange: buffered, boundaries: model.sliderBoundaries, onEditingChanged: { editing in
|
||||
if editing { model.beginSeeking() } else { model.finishSeeking() }
|
||||
})
|
||||
.frame(height: 14)
|
||||
|
||||
Reference in New Issue
Block a user