14 Commits

Author SHA1 Message Date
6bbd86006a fix: nil AVPlayerLayer.player before closing fullscreen window
Root cause: when FullscreenPlayerView deallocates during window close,
AVPlayerLayer.player reference is released via autorelease pool drain
on main thread. CoreMedia background threads simultaneously access
sFigNotificationCenterWeakListenerLinks dictionary (weak listener
cleanup), causing use-after-free race condition.

Fix:
- toggleFullscreen(): set playerView.player = nil BEFORE fw.close()
- FullscreenPlayerView.viewWillMove(toWindow: nil): safety net to
  nil out player when view is removed from window by any means

This ensures the AVPlayer reference is released synchronously on the
main thread, before any autorelease pool drain can race with CoreMedia
internal cleanup threads.
2026-06-22 01:11:40 +08:00
0d63414214 fix: replace CoreMedia periodic time observer with pure Swift Timer to eliminate autorelease pool race
Root cause: addPeriodicTimeObserver's internal FigNotificationCenter weak
listener mechanism races with autorelease pool drain on main thread, causing
double-free of weak reference wrappers (KERN_INVALID_ADDRESS).

Changes:
- Replace addPeriodicTimeObserver with Timer.scheduledTimer (bypasses CoreMedia
  weak listener infrastructure entirely)
- Remove ALL Task { @MainActor in } from observer callbacks — these created
  unstructured tasks whose weak ref wrappers conflicted with CoreMedia internals
- Use DispatchQueue.main.async for KVO callbacks (may fire from non-main thread)
- Direct calls for queue: .main callbacks (NotificationCenter, end observer)
- Add isTearingDown flag to prevent callbacks firing during cleanup
- Fix cleanup() order: timer → KVO → notifications → replaceCurrentItem(nil)
- Fix FullscreenPlayerView: use addSublayer instead of replacing backing layer
- Add .onDisappear { bridge.cleanup() } to ensure cleanup before dealloc
- Remove Combine import (no longer needed)
2026-06-22 01:06:39 +08:00
c336066198 fix: eliminate CoreMedia FigNotificationCenter race condition causing SIGSEGV
- Remove KVO on player.timeControlStatus (fires from CoreMedia bg threads)
- Stop accessing item.duration in timer callback (triggers FigNotificationCenter weak listener ops)
- Check player.rate in timer callback instead for isPlaying state
- Remove replaceCurrentItem(nil) from cleanup to prevent CoreMedia state inconsistency
- Use cachedDuration exclusively in time display updates
- Fix actor isolation warning in endObserver callback
2026-06-22 00:57:23 +08:00
f14d47b5c8 fix: use sublayer instead of replacing NSView backing layer to prevent autorelease crash
- PlayerNSView: AVPlayerLayer as sublayer (not layer= replacement)
  NSView internally manages a sublayer array; replacing the backing
  layer directly causes dangling refs during autorelease pool drain
- cleanup(): only pause + remove observers, no replaceCurrentItem
  CoreMedia internal state gets corrupted when item is replaced
  during view teardown; let it release naturally with deinit
2026-06-22 00:47:05 +08:00
aa19ab9799 feat: i18n support, tri-color icon, fix crash
- Add i18n: Localization.swift + zh-Hans/en Localizable.strings
- Add MiniPlayerIcon SwiftUI view (tri-color play button + ring)
- Fix crash: isInteracting/lastInteraction no longer @Published
- Fix crash: ExitFullscreen notification wrapped in DispatchQueue.main.async
- Auto-hide toolbar uses local @State + Timer (not @Published)
- Replace emoji logo with MiniPlayerIcon
- Move icon sets out of Resources/ to avoid SPM conflicts
- Package.swift: add defaultLocalization, process Resources
2026-06-22 00:40:20 +08:00
3e3a990f5e fix: replace Combine KVO with NSKeyValueObservation to prevent autorelease crash
- Remove Combine import and cancellables
- Use NSKeyValueObservation for timeControlStatus (controlled teardown)
- Invalidate itemStatusObserver before replacing playerItem
- Store endObserver token for proper removal
- Add cleanup() method called on view disappear
- Proper deinit with direct property cleanup (nonisolated-safe)
2026-06-22 00:36:18 +08:00
1fa6f6a4bb feat: toolbar auto-hides after 60s of no interaction 2026-06-22 00:12:43 +08:00
6811572b7e fix: rewrite UI as pure SwiftUI, fix crash/fullscreen/height issues
- Skip BricksView, render video directly in SwiftUI (fixes 1/3 height)
- Fullscreen uses plain NSView+AVPlayerLayer (fixes objc_release crash)
- Remove NSApp.hide(nil) (fixes fullscreen not showing)
- Add volume +/- buttons and volume slider indicator
- Add iOS/iPadOS support with #if os guards
- ProgressSlider decoupled from BricksEngine
- PlayerBridge no longer depends on player.ui JSON
2026-06-22 00:04:06 +08:00
e4ca9bc80a fix: fullscreen crash + video size - use system toggleFullScreen, direct VideoPlayer render 2026-06-21 23:50:54 +08:00
4b94f11664 feat: hidden toolbar with logo toggle, semi-transparent controls, playlist popup window 2026-06-21 23:43:20 +08:00
4e58377582 fix: persist audio track selection across songs (preferredTrackIndex) 2026-06-21 23:33:08 +08:00
bf4cb64286 fix: video content fullscreen (borderless window), load actual duration via KVO+async 2026-06-21 23:31:12 +08:00
Hermes Agent
bc9a732809 rename player.json to player.ui to match bricks convention 2026-06-21 17:58:12 +08:00
Hermes Agent
c69ec38dc3 refactor: rewrite MiniPlayer using SwiftBricks framework
- 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
2026-06-21 17:48:06 +08:00