2022-08-28 17:18:49 +00:00
|
|
|
import Defaults
|
|
|
|
import SwiftUI
|
|
|
|
|
|
|
|
struct Seek: View {
|
|
|
|
#if os(iOS)
|
|
|
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
|
|
|
#endif
|
|
|
|
|
2022-11-24 20:36:05 +00:00
|
|
|
@ObservedObject private var controls = PlayerControlsModel.shared
|
2022-09-01 17:00:56 +00:00
|
|
|
@StateObject private var model = SeekModel.shared
|
2022-08-28 17:18:49 +00:00
|
|
|
|
|
|
|
private var updateThrottle = Throttle(interval: 2)
|
|
|
|
|
|
|
|
@Default(.playerControlsLayout) private var regularPlayerControlsLayout
|
|
|
|
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout
|
|
|
|
|
|
|
|
var body: some View {
|
2022-09-01 23:05:31 +00:00
|
|
|
Group {
|
|
|
|
#if os(tvOS)
|
|
|
|
content
|
|
|
|
.shadow(radius: 3)
|
|
|
|
#else
|
|
|
|
Button(action: model.restoreTime) { content }
|
|
|
|
.buttonStyle(.plain)
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
.opacity(visible || YatteeApp.isForPreviews ? 1 : 0)
|
|
|
|
}
|
2022-08-28 17:18:49 +00:00
|
|
|
|
2022-09-01 23:05:31 +00:00
|
|
|
var content: some View {
|
|
|
|
VStack(spacing: playerControlsLayout.osdSpacing) {
|
|
|
|
ProgressBar(value: model.progress)
|
|
|
|
.frame(maxHeight: playerControlsLayout.osdProgressBarHeight)
|
2022-08-28 17:18:49 +00:00
|
|
|
|
2022-09-01 23:05:31 +00:00
|
|
|
timeline
|
2022-08-28 17:18:49 +00:00
|
|
|
|
2022-09-01 23:05:31 +00:00
|
|
|
if model.isSeeking {
|
|
|
|
Divider()
|
|
|
|
gestureSeekTime
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
.font(.system(size: playerControlsLayout.chapterFontSize).monospacedDigit())
|
|
|
|
.frame(height: playerControlsLayout.chapterFontSize + 5)
|
|
|
|
|
|
|
|
if let chapter = projectedChapter {
|
|
|
|
Divider()
|
|
|
|
Text(chapter.title)
|
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.font(.system(size: playerControlsLayout.chapterFontSize))
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
}
|
|
|
|
if let segment = projectedSegment {
|
|
|
|
Text(SponsorBlockAPI.categoryDescription(segment.category) ?? "Sponsor")
|
|
|
|
.font(.system(size: playerControlsLayout.segmentFontSize))
|
|
|
|
.foregroundColor(Color("AppRedColor"))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
#if !os(tvOS)
|
|
|
|
if !model.restoreSeekTime.isNil {
|
2022-08-28 17:18:49 +00:00
|
|
|
Divider()
|
2022-09-01 23:05:31 +00:00
|
|
|
Label(model.restoreSeekPlaybackTime, systemImage: "arrow.counterclockwise")
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
.font(.system(size: playerControlsLayout.chapterFontSize).monospacedDigit())
|
|
|
|
.frame(height: playerControlsLayout.chapterFontSize + 5)
|
2022-08-28 17:18:49 +00:00
|
|
|
}
|
2022-09-01 23:05:31 +00:00
|
|
|
#endif
|
|
|
|
Group {
|
|
|
|
switch model.lastSeekType {
|
|
|
|
case let .segmentSkip(category):
|
|
|
|
Divider()
|
|
|
|
Text(SponsorBlockAPI.categoryDescription(category) ?? "Sponsor")
|
2022-08-28 17:18:49 +00:00
|
|
|
.font(.system(size: playerControlsLayout.segmentFontSize))
|
|
|
|
.foregroundColor(Color("AppRedColor"))
|
2022-09-01 23:05:31 +00:00
|
|
|
default:
|
|
|
|
EmptyView()
|
2022-08-28 17:18:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-01 23:05:31 +00:00
|
|
|
.frame(maxWidth: playerControlsLayout.seekOSDWidth)
|
|
|
|
#if os(tvOS)
|
|
|
|
.padding(30)
|
|
|
|
#else
|
|
|
|
.padding(2)
|
|
|
|
.modifier(ControlBackgroundModifier())
|
|
|
|
.clipShape(RoundedRectangle(cornerRadius: 3))
|
|
|
|
#endif
|
|
|
|
|
|
|
|
.foregroundColor(.primary)
|
2022-08-28 17:18:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var timeline: some View {
|
2022-08-29 11:55:23 +00:00
|
|
|
let text = model.isSeeking ?
|
2022-08-28 17:18:49 +00:00
|
|
|
"\(model.gestureSeekDestinationPlaybackTime)/\(model.durationPlaybackTime)" :
|
|
|
|
"\(model.lastSeekPlaybackTime)/\(model.durationPlaybackTime)"
|
|
|
|
|
|
|
|
return Text(text)
|
|
|
|
.fontWeight(.bold)
|
|
|
|
.font(.system(size: playerControlsLayout.projectedTimeFontSize).monospacedDigit())
|
|
|
|
}
|
|
|
|
|
|
|
|
var gestureSeekTime: some View {
|
|
|
|
var seek = model.gestureSeekDestinationTime - model.currentTime.seconds
|
|
|
|
if seek > 0 {
|
|
|
|
seek = min(seek, model.duration.seconds - model.currentTime.seconds)
|
|
|
|
} else {
|
|
|
|
seek = min(seek, model.currentTime.seconds)
|
|
|
|
}
|
|
|
|
let timeText = abs(seek)
|
|
|
|
.formattedAsPlaybackTime(allowZero: true, forceHours: model.forceHours) ?? ""
|
|
|
|
|
|
|
|
return Label(
|
|
|
|
timeText,
|
|
|
|
systemImage: seek >= 0 ? "goforward.plus" : "gobackward.minus"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
var visible: Bool {
|
2022-08-29 11:55:23 +00:00
|
|
|
guard !(model.lastSeekTime.isNil && !model.isSeeking) else { return false }
|
2022-08-28 17:18:49 +00:00
|
|
|
if let type = model.lastSeekType, !type.presentable { return false }
|
|
|
|
|
2022-08-29 11:55:23 +00:00
|
|
|
return !controls.presentingControls && !controls.presentingOverlays && model.presentingOSD
|
2022-08-28 17:18:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var projectedChapter: Chapter? {
|
|
|
|
(model.player?.currentVideo?.chapters ?? []).last { $0.start <= model.gestureSeekDestinationTime }
|
|
|
|
}
|
|
|
|
|
|
|
|
var projectedSegment: Segment? {
|
|
|
|
(model.player?.sponsorBlock.segments ?? []).first { $0.timeInSegment(.secondsInDefaultTimescale(model.gestureSeekDestinationTime)) }
|
|
|
|
}
|
|
|
|
|
|
|
|
var playerControlsLayout: PlayerControlsLayout {
|
2022-09-01 23:05:31 +00:00
|
|
|
(model.player?.playingFullScreen ?? false) ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout
|
2022-08-28 17:18:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Seek_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
|
|
|
Seek()
|
|
|
|
}
|
|
|
|
}
|