fix: center controller content and autoplay opened files
This commit is contained in:
@@ -14,11 +14,11 @@ make run
|
|||||||
|
|
||||||
The local app is `.build/Player.app`. It links the installed FFmpeg libraries; it is not a standalone distributable bundle. After upgrading FFmpeg, rebuild with `make -B`.
|
The local app is `.build/Player.app`. It links the installed FFmpeg libraries; it is not a standalone distributable bundle. After upgrading FFmpeg, rebuild with `make -B`.
|
||||||
|
|
||||||
Open a local file with **⌘O**, drop it onto the window, or use Finder's **Open With**. Files open paused. **⇧⌘W** closes the file; closing the window quits and joins the worker.
|
Open a local file with **⌘O**, drop it onto the window, or use Finder's **Open With**. Successfully opened files start playback automatically, including when replacing the current file. **⇧⌘W** closes the file; closing the window quits and joins the worker.
|
||||||
|
|
||||||
## Playback UI
|
## Playback UI
|
||||||
|
|
||||||
The layout follows [IINA's video-first window](https://iina.io/img/screenshot.png) and the supplied screenshots: a 32-point frosted titlebar with native traffic lights and file icon, plus a dark frosted 440 × 68-point floating controller. AppKit materials blur the video beneath the chrome; muted icons, an orange volume track, a narrow seek thumb, and zero-padded time labels follow the reference. The controller starts centered with a four-percent bottom inset and keeps its relative position when the window changes size.
|
The layout follows [IINA's video-first window](https://iina.io/img/screenshot.png) and the supplied screenshots: a 32-point frosted titlebar with native traffic lights and file icon, plus a dark frosted 440 × 68-point floating controller. AppKit materials blur the video beneath the chrome; muted icons, an orange volume track, a narrow seek thumb, and zero-padded time labels follow the reference. Shared vertical padding and an optical offset for symbol/slider insets balance the controller's visible top and bottom margins in both play and pause states. The controller starts centered with a four-percent bottom inset and keeps its relative position when the window changes size.
|
||||||
|
|
||||||
Opening video fits the window to its display aspect ratio, including non-square pixels, and constrains subsequent window resizing. Fullscreen retains the normal letterboxed renderer; audio-only playback and close release the aspect constraint. Extreme ratios that cannot fit both the screen and usable controls keep a freely resizable letterboxed window.
|
Opening video fits the window to its display aspect ratio, including non-square pixels, and constrains subsequent window resizing. Fullscreen retains the normal letterboxed renderer; audio-only playback and close release the aspect constraint. Extreme ratios that cannot fit both the screen and usable controls keep a freely resizable letterboxed window.
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ Only implemented actions have controls; there are no placeholder playlist, subti
|
|||||||
- `Sources/Core/player.h`: the C interface and authoritative state snapshot. Playback commands enqueue work; poll state for completion. `seek_pending` is set synchronously when a seek or frame step is accepted and clears when all queued seeks/steps on that timeline finish. `player_step_frame` accepts only `-1` or `+1`, requires ready video, and publishes the selected frame's timestamp as `position`. Volume set/get is synchronous and uses a lock-free target gain. Callers own the opaque handle and must stop using it before destruction.
|
- `Sources/Core/player.h`: the C interface and authoritative state snapshot. Playback commands enqueue work; poll state for completion. `seek_pending` is set synchronously when a seek or frame step is accepted and clears when all queued seeks/steps on that timeline finish. `player_step_frame` accepts only `-1` or `+1`, requires ready video, and publishes the selected frame's timestamp as `position`. Volume set/get is synchronous and uses a lock-free target gain. Callers own the opaque handle and must stop using it before destruction.
|
||||||
- `Sources/Core/player.cpp`: single demux/decode worker, bounded buffers, timestamps, audio-master synchronization, paused previews, exact frame stepping, decoder/resampler drain, seeking, errors, and shutdown. A seek stops output and flushes all old timeline state before resuming.
|
- `Sources/Core/player.cpp`: single demux/decode worker, bounded buffers, timestamps, audio-master synchronization, paused previews, exact frame stepping, decoder/resampler drain, seeking, errors, and shutdown. A seek stops output and flushes all old timeline state before resuming.
|
||||||
- `Sources/Core/output.hpp` and `Sources/macOS/Output.mm`: native output contract, allocation-free/nonblocking Core Audio callback and bounded audio ring, latency-aware presentation clock, retained frame mailbox, and Metal YUV conversion. The view can outlive the player safely.
|
- `Sources/Core/output.hpp` and `Sources/macOS/Output.mm`: native output contract, allocation-free/nonblocking Core Audio callback and bounded audio ring, latency-aware presentation clock, retained frame mailbox, and Metal YUV conversion. The view can outlive the player safely.
|
||||||
- `Sources/App/PlayerApp.swift`: MainActor state polling, file panel/Finder/drop handling, video host, AppKit glass and native slider cells, floating controls, visibility, video-fitted sizing, and native window integration.
|
- `Sources/App/PlayerApp.swift`: MainActor state polling, file panel/Finder/drop handling, autoplay, video host, AppKit glass and native slider cells, floating controls, visibility, video-fitted sizing, and native window integration. The shared `PlayerModel.open` path enqueues play after an accepted open, on the new file's command timeline; the C interface still opens paused for callers that need explicit playback control.
|
||||||
- `Makefile`: compiler/linker commands and app bundle construction. No generated Xcode project or additional build dependency.
|
- `Makefile`: compiler/linker commands and app bundle construction. No generated Xcode project or additional build dependency.
|
||||||
|
|
||||||
## Runnable check
|
## Runnable check
|
||||||
|
|||||||
@@ -95,12 +95,11 @@ final class PlayerModel: ObservableObject {
|
|||||||
commandError = "Could not queue the file to open. Please try again."
|
commandError = "Could not queue the file to open. Please try again."
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Open is always paused; never queue play against the previous file.
|
|
||||||
pendingRevision = revision
|
pendingRevision = revision
|
||||||
fileName = url.lastPathComponent
|
fileName = url.lastPathComponent
|
||||||
fileIcon = NSWorkspace.shared.icon(forFile: url.path)
|
fileIcon = NSWorkspace.shared.icon(forFile: url.path)
|
||||||
needsVideoFit = true
|
needsVideoFit = true
|
||||||
commandError = nil
|
commandError = player_play(handle) ? nil : "Could not queue playback. Please try again."
|
||||||
isSeeking = false
|
isSeeking = false
|
||||||
seekPending = false
|
seekPending = false
|
||||||
seekPosition = 0
|
seekPosition = 0
|
||||||
@@ -528,7 +527,6 @@ private struct PlayerContent: View {
|
|||||||
.frame(width: 106, alignment: .trailing)
|
.frame(width: 106, alignment: .trailing)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.top, 14)
|
|
||||||
|
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
Text(time(model.seekPosition)).frame(minWidth: 32, alignment: .trailing)
|
Text(time(model.seekPosition)).frame(minWidth: 32, alignment: .trailing)
|
||||||
@@ -553,8 +551,10 @@ private struct PlayerContent: View {
|
|||||||
}
|
}
|
||||||
.font(.system(size: 11).monospacedDigit())
|
.font(.system(size: 11).monospacedDigit())
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.bottom, 8)
|
|
||||||
}
|
}
|
||||||
|
.padding(.vertical, 11)
|
||||||
|
// Optical centering accounts for symbol insets and the native seek thumb.
|
||||||
|
.offset(y: -1.5)
|
||||||
.foregroundStyle(.white.opacity(0.6))
|
.foregroundStyle(.white.opacity(0.6))
|
||||||
.onHover(perform: model.setControlsHovered)
|
.onHover(perform: model.setControlsHovered)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user