feat: fullscreen uses SwiftUI with icon+toolbar, double-click toggle, single-click play/pause

This commit is contained in:
yumoqing 2026-06-23 09:02:08 +08:00
parent 4879f56ef1
commit f303832a1b
2 changed files with 34 additions and 42 deletions

View File

@ -162,15 +162,13 @@ class PlayerLayerHostView: NSView {
}
}
// MARK: - BricksView wrapper
struct BricksAppView: View {
// MARK: -
struct PlayerContentView: View {
@ObservedObject var bridge: PlayerBridge
@ObservedObject var engine: BricksEngine
@State private var showToolbar = false
@State private var isHoveringIcon = false
@State private var hideTimer: Timer?
@State private var isHoveringToolbar = false
@State private var jsonString: String?
private func resetHideTimer() {
hideTimer?.invalidate()
@ -188,6 +186,12 @@ struct BricksAppView: View {
// AVPlayerLayer AVPlayerView
PlayerLayerView(player: bridge.player)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture(count: 2) {
bridge.toggleFullscreen()
}
.onTapGesture {
bridge.togglePlayPause()
}
//
MiniPlayerIcon()
@ -239,24 +243,25 @@ struct BricksAppView: View {
.onDisappear {
hideTimer?.invalidate()
}
.sheet(isPresented: $bridge.showURLDialog) {
URLInputDialog(bridge: bridge)
}
.sheet(isPresented: $bridge.showTrackDialog) {
TrackSelectDialog(bridge: bridge)
}
.sheet(isPresented: $bridge.showPlaylistSheet) {
PlaylistWindowView(bridge: bridge)
}
}
}
// MARK: - BricksView wrapper
struct BricksAppView: View {
@ObservedObject var bridge: PlayerBridge
@ObservedObject var engine: BricksEngine
private func loadJSONString() -> String {
if let url = Bundle.module.url(forResource: "miniplayer", withExtension: "ui"),
let data = try? Data(contentsOf: url),
let str = String(data: data, encoding: .utf8) {
return str
}
return "{\"widgettype\":\"VideoPlayer\",\"id\":\"videoPlayer\",\"options\":{\"width\":\"100%\",\"height\":\"100%\"}}"
var body: some View {
PlayerContentView(bridge: bridge)
.sheet(isPresented: $bridge.showURLDialog) {
URLInputDialog(bridge: bridge)
}
.sheet(isPresented: $bridge.showTrackDialog) {
TrackSelectDialog(bridge: bridge)
}
.sheet(isPresented: $bridge.showPlaylistSheet) {
PlaylistWindowView(bridge: bridge)
}
}
}

View File

@ -279,7 +279,7 @@ final class PlayerBridge: ObservableObject {
// MARK: -
// orderFront/orderOut
// AVPlayerView CoreMedia weak listener
// 使 NSHostingView + SwiftUI
func toggleFullscreen() {
#if os(macOS)
if isFullscreen {
@ -294,7 +294,7 @@ final class PlayerBridge: ObservableObject {
return
}
// AVPlayerView
// SwiftUI
if fullscreenWindow == nil {
guard let screen = NSScreen.main else { return }
let win = NSWindow(contentRect: screen.frame, styleMask: .borderless, backing: .buffered, defer: false)
@ -304,11 +304,10 @@ final class PlayerBridge: ObservableObject {
win.hasShadow = false
win.isReleasedWhenClosed = false
let playerView = AVPlayerView(frame: screen.frame)
playerView.player = player
playerView.controlsStyle = .none
playerView.videoGravity = .resizeAspect
win.contentView = playerView
// SwiftUI PlayerContentView +
let hostingView = NSHostingView(rootView: PlayerContentView(bridge: self))
hostingView.frame = screen.frame
win.contentView = hostingView
fullscreenWindow = win
}
@ -316,25 +315,13 @@ final class PlayerBridge: ObservableObject {
NSApp.activate(ignoringOtherApps: true)
isFullscreen = true
// 退/Escape退
// Escape 退
if fullscreenEventMonitor == nil {
fullscreenEventMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .keyDown]) { [weak self] event in
fullscreenEventMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { [weak self] event in
guard let self = self, self.isFullscreen else { return event }
guard event.window === self.fullscreenWindow else { return event }
if event.type == .leftMouseDown {
if event.clickCount >= 2 {
DispatchQueue.main.async { self.toggleFullscreen() }
return nil
} else {
if self.player.timeControlStatus == .playing {
self.player.pause()
} else {
self.player.play()
}
return nil
}
} else if event.type == .keyDown && event.keyCode == 53 {
if event.type == .keyDown && event.keyCode == 53 { // Escape
DispatchQueue.main.async { self.toggleFullscreen() }
return nil
}