feat: fullscreen uses SwiftUI with icon+toolbar, double-click toggle, single-click play/pause
This commit is contained in:
parent
4879f56ef1
commit
f303832a1b
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user