fix(player): ensure GStreamer produces and displays frames properly

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)
This commit is contained in:
Stu Leak 2026-01-09 19:22:08 -05:00
parent 00df0b3b31
commit 6a604dbb35
2 changed files with 26 additions and 9 deletions

View File

@ -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)
}

22
main.go
View File

@ -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 {