- UI defined in player.json (Bricks JSON schema) - Custom widgets: VideoPlayer (AVPlayer layer), ProgressSlider (seek bar) - PlayerBridge connects AVPlayer to BricksEngine event bus - All interactions via binds/events (no imperative UI code) - Depends on SwiftBricks SPM package
73 lines
2.5 KiB
Swift
73 lines
2.5 KiB
Swift
import SwiftUI
|
|
import AVFoundation
|
|
import SwiftBricks
|
|
|
|
/// ProgressSlider自定义Widget — 播放进度条+拖动seek
|
|
struct ProgressSliderWidget: View {
|
|
let bridge: PlayerBridge
|
|
let schema: ControlSchema
|
|
@ObservedObject var engine: BricksEngine
|
|
|
|
@State private var progress: Double = 0
|
|
@State private var duration: Double = 0
|
|
@State private var isDragging: Bool = false
|
|
|
|
var body: some View {
|
|
GeometryReader { geo in
|
|
ZStack(alignment: .leading) {
|
|
// 背景轨道
|
|
RoundedRectangle(cornerRadius: 2)
|
|
.fill(Color.secondary.opacity(0.3))
|
|
.frame(height: 4)
|
|
|
|
// 已播放进度
|
|
RoundedRectangle(cornerRadius: 2)
|
|
.fill(Color.accentColor)
|
|
.frame(width: geo.size.width * progressRatio, height: 4)
|
|
|
|
// 拖动滑块
|
|
Circle()
|
|
.fill(Color.accentColor)
|
|
.frame(width: 14, height: 14)
|
|
.offset(x: geo.size.width * progressRatio - 7)
|
|
.shadow(radius: 2)
|
|
}
|
|
.frame(height: 20)
|
|
.contentShape(Rectangle())
|
|
.gesture(
|
|
DragGesture(minimumDistance: 0)
|
|
.onChanged { value in
|
|
isDragging = true
|
|
let ratio = max(0, min(1, value.location.x / geo.size.width))
|
|
if duration > 0 {
|
|
let seekTime = ratio * duration
|
|
bridge.player.seek(to: CMTime(seconds: seekTime, preferredTimescale: 600))
|
|
}
|
|
}
|
|
.onEnded { _ in
|
|
isDragging = false
|
|
}
|
|
)
|
|
}
|
|
.frame(height: 20)
|
|
.onReceive(engine.store.$values) { values in
|
|
if let val = values["progress_slider"] as? String {
|
|
let parts = val.split(separator: "/")
|
|
if parts.count == 2,
|
|
let cur = Double(parts[0]),
|
|
let total = Double(parts[1]) {
|
|
if !isDragging {
|
|
progress = cur
|
|
duration = total
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var progressRatio: Double {
|
|
guard duration > 0 else { return 0 }
|
|
return max(0, min(1, progress / duration))
|
|
}
|
|
}
|