From 9c801e4910f2c8f4402439b1db9a518ab6fd373f Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Fri, 9 Jan 2026 19:22:08 -0500 Subject: [PATCH] fix(player): ensure GStreamer produces and displays frames properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical fixes for GStreamer playback: 1. Add preroll waiting in GStreamer.Load(): - Wait for ASYNC_DONE message after setting to PAUSED - Ensures first frame is ready before playback - Prevents black screen on load 2. Fix frameDisplayLoop to always pull frames: - Remove paused check that blocked frame extraction - Frames now pulled even when paused (enables scrubbing) - Only update progress bar when playing 3. Add comprehensive logging: - Log each frame update with size and timestamp - Debug frame pull errors - Track paused state during updates 4. Fix initial paused state: - Explicitly set paused=true after load - Matches GStreamer's PAUSED state These changes fix: - Black screen issue (no frames displaying) - Scrubbing not working (frames not available when paused) - Fast duration counting (progress only updates when playing) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- internal/player/gstreamer_player.go | 13 +++++++++++++ main.go | 22 +++++++++++++--------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/internal/player/gstreamer_player.go b/internal/player/gstreamer_player.go index 586a0e3..3899d96 100644 --- a/internal/player/gstreamer_player.go +++ b/internal/player/gstreamer_player.go @@ -153,7 +153,20 @@ func (p *GStreamerPlayer) Load(path string, offset time.Duration) error { p.appsink = appsink p.paused = true + // Set to PAUSED to preroll (loads first frame) C.gst_element_set_state(playbin, C.GST_STATE_PAUSED) + + // Wait for preroll to complete (first frame ready) + bus := C.gst_element_get_bus(playbin) + if bus != nil { + defer C.gst_object_unref(C.gpointer(bus)) + // Wait up to 5 seconds for preroll + msg := C.gst_bus_timed_pop_filtered(bus, 5000000000, C.GST_MESSAGE_ASYNC_DONE|C.GST_MESSAGE_ERROR) + if msg != nil { + C.gst_message_unref(msg) + } + } + if offset > 0 { _ = p.seekLocked(offset) } diff --git a/main.go b/main.go index ac1f2c7..f2e05ee 100644 --- a/main.go +++ b/main.go @@ -11247,9 +11247,14 @@ func newPlaySession(path string, w, h int, fps, duration float64, targetW, targe return nil } + logging.Info(logging.CatPlayer, "GStreamer loaded video: %s (%.2f fps, %dx%d)", path, fps, targetW, targetH) + // Start frame display loop go sess.frameDisplayLoop() + // Pause initially (GStreamer is already paused after Load) + sess.paused = true + return sess } @@ -11356,12 +11361,12 @@ func (p *playSession) frameDisplayLoop() { defer ticker.Stop() frameCount := 0 - logging.Debug(logging.CatPlayer, "playSession: frameDisplayLoop started (fps=%.2f)", p.fps) + logging.Info(logging.CatPlayer, "playSession: frameDisplayLoop started (fps=%.2f, interval=%v)", p.fps, frameDuration) for { select { case <-p.stop: - logging.Debug(logging.CatPlayer, "playSession: frameDisplayLoop stopped") + logging.Info(logging.CatPlayer, "playSession: frameDisplayLoop stopped") return case <-ticker.C: @@ -11369,12 +11374,7 @@ func (p *playSession) frameDisplayLoop() { continue } - // Skip frame updates when paused - if p.paused { - continue - } - - // Get current frame from GStreamer + // Get current frame from GStreamer (even when paused, for seeking) frame, err := p.gstPlayer.GetFrameImage() if err != nil { logging.Debug(logging.CatPlayer, "Frame read error: %v", err) @@ -11382,6 +11382,7 @@ func (p *playSession) frameDisplayLoop() { } if frame == nil { + // No frame available yet - pipeline may be buffering continue } @@ -11391,6 +11392,7 @@ func (p *playSession) frameDisplayLoop() { p.frameN = frameCount currentTime := p.gstPlayer.GetCurrentTime() p.current = currentTime.Seconds() + isPaused := p.paused p.mu.Unlock() // Update UI on main thread @@ -11398,8 +11400,10 @@ func (p *playSession) frameDisplayLoop() { if p.img != nil { p.img.Image = frame p.img.Refresh() + logging.Debug(logging.CatPlayer, "Frame %d updated (%.2fs, paused=%v, size=%dx%d)", + frameCount, p.current, isPaused, frame.Bounds().Dx(), frame.Bounds().Dy()) } - if p.prog != nil { + if p.prog != nil && !isPaused { p.prog(p.current) } if p.frameFunc != nil {