swiftbricks/Sources/SwiftBricks/Controls/VideoPlayerControl.swift

190 lines
5.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
import AVKit
/// VideoPlayer widget
/// 使 AVKit SwiftBricks
///
/// JSON Schema :
/// ```json
/// {
/// "widgettype": "VideoPlayer",
/// "options": {
/// "url": "https://example.com/video.mp4",
/// "autoplay": true,
/// "controls": true
/// }
/// }
/// ```
///
/// :
/// - play:
/// - pause:
/// - toggle: /
/// - seek: (: time)
/// - setVolume: (: volume)
struct VideoPlayerControl: View {
let schema: ControlSchema
@ObservedObject var engine: BricksEngine
@State private var player: AVPlayer?
@State private var isPlaying: Bool = false
var body: some View {
Group {
#if os(macOS)
macOSPlayerView
#else
iOSPlayerView
#endif
}
.onAppear {
setupPlayer()
setupEventListeners()
}
.onDisappear {
player?.pause()
}
}
// MARK: - Platform-specific views
#if os(macOS)
private var macOSPlayerView: some View {
VideoPlayerView(player: player)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private struct VideoPlayerView: NSViewRepresentable {
let player: AVPlayer?
func makeNSView(context: Context) -> NSView {
let view = AVPlayerView()
view.player = player
view.controlsStyle = .default
return view
}
func updateNSView(_ nsView: NSView, context: Context) {
if let playerView = nsView as? AVPlayerView {
playerView.player = player
}
}
}
#else
private var iOSPlayerView: some View {
VideoPlayer(player: player)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
#endif
// MARK: - Setup
private func setupPlayer() {
// Store AVPlayer PlayerBridge
let widgetId = schema.effectiveId
if let sharedPlayer = engine.store.getValue(id: "\(widgetId).player") as? AVPlayer {
player = sharedPlayer
isPlaying = sharedPlayer.rate > 0
return
}
// Fallback: schema options url player
guard let urlString = schema.options.url else { return }
if let url = URL(string: urlString) {
player = AVPlayer(url: url)
if schema.options.autoplay == true {
player?.play()
isPlaying = true
}
}
}
private func setupEventListeners() {
let widgetId = schema.effectiveId
//
engine.eventBus.on("\(widgetId).play") { [weak engine] data in
self.player?.play()
self.isPlaying = true
await engine?.eventBus.dispatch("\(widgetId).playing", data: EventData())
}
//
engine.eventBus.on("\(widgetId).pause") { [weak engine] data in
self.player?.pause()
self.isPlaying = false
await engine?.eventBus.dispatch("\(widgetId).paused", data: EventData())
}
//
engine.eventBus.on("\(widgetId).toggle") { [weak engine] data in
if self.isPlaying {
self.player?.pause()
self.isPlaying = false
await engine?.eventBus.dispatch("\(widgetId).paused", data: EventData())
} else {
self.player?.play()
self.isPlaying = true
await engine?.eventBus.dispatch("\(widgetId).playing", data: EventData())
}
}
//
engine.eventBus.on("\(widgetId).seek") { data in
if let timeStr = data["time"] as? String, let time = Double(timeStr) {
self.player?.seek(to: CMTime(seconds: time, preferredTimescale: 600))
} else if let time = data["time"] as? Double {
self.player?.seek(to: CMTime(seconds: time, preferredTimescale: 600))
}
}
//
engine.eventBus.on("\(widgetId).setVolume") { data in
if let volStr = data["volume"] as? String, let vol = Float(volStr) {
self.player?.volume = vol
} else if let vol = data["volume"] as? Float {
self.player?.volume = vol
}
}
//
engine.eventBus.on("\(widgetId).setURL") { data in
if let urlString = data["url"] as? String, let url = URL(string: urlString) {
let wasPlaying = self.isPlaying
self.player?.pause()
self.player = AVPlayer(url: url)
if wasPlaying {
self.player?.play()
}
}
}
}
// MARK: - Helpers
private func resolveWidth(_ opts: ControlOptions) -> CGFloat? {
if let w = opts.width {
if w == "100%" { return nil }
if let px = parsePixels(w) { return px }
}
return nil
}
private func resolveHeight(_ opts: ControlOptions) -> CGFloat? {
if let h = opts.height {
if h == "100%" { return nil }
if let px = parsePixels(h) { return px }
}
return nil
}
private func parsePixels(_ str: String) -> CGFloat? {
let cleaned = str.replacingOccurrences(of: "px", with: "")
return CGFloat(Double(cleaned) ?? 0)
}
}