From fe88e26166ee2d8073d4b5655a8fbad4824c8993 Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Wed, 8 Jul 2026 07:12:32 -0700 Subject: [PATCH] media: set sync=false on the remaining non-pacing appsinks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit follow-up to the muxl-appsink sync fix: swept every appsink in the tree for clock sync. - thumbnailFromMP4 (thumbnail.go): the flat-MP4 thumbnail pipeline was the only consumer-path appsink still defaulting to sync=true. In practice videorate re-stamps the selected frame to PTS 0 so it usually rendered immediately, but there's no reason to leave it on the clock — and its twin Thumbnail() already sets sync=false. - SelfTest (gstreamer.go): untimestamped buffers so sync never actually waited; set sync=false for consistency so the tree greps clean. - sp whip (cmd/whip.go): the ONE appsink pair where sync=true is load-bearing — the client plays a file over WHIP as a live stream and the appsink clock wait is the only realtime pacing (pion WriteSample pushes immediately). Made sync=true explicit with a comment so a future sweep doesn't "fix" it. Already sync=false (no change): packetize, media_data_parser, segment_conv, transcode, vod_pipeline, segmenter (via sink-added), Thumbnail, muxl_segment. Co-Authored-By: Claude Fable 5 --- pkg/cmd/whip.go | 9 +++++++-- pkg/media/gstreamer.go | 1 + pkg/media/thumbnail.go | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/whip.go b/pkg/cmd/whip.go index 2f67e8b66..920f47020 100644 --- a/pkg/cmd/whip.go +++ b/pkg/cmd/whip.go @@ -74,8 +74,13 @@ func (w *WHIPClient) WHIP(ctx context.Context) error { "filesrc name=filesrc ! qtdemux name=demux", "demux.video_0 ! tee name=video_tee", "demux.audio_0 ! tee name=audio_tee", - "video_tee. ! queue ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream ! appsink name=videoappsink", - "audio_tee. ! queue ! opusparse ! appsink name=audioappsink", + // sync=true (the default) is load-bearing here, unlike every other + // appsink in the tree: these sinks are what pace the file at realtime + // so it plays as a live stream — WriteSample pushes to WebRTC + // immediately, so without clock sync the whole file would blast + // through in one burst. + "video_tee. ! queue ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream ! appsink sync=true name=videoappsink", + "audio_tee. ! queue ! opusparse ! appsink sync=true name=audioappsink", // "matroskamux name=mux ! fakesink name=fakesink sync=true", // "video_tee. ! mux.video_0", // "audio_tee. ! mux.audio_0", diff --git a/pkg/media/gstreamer.go b/pkg/media/gstreamer.go index 140f9460d..f3c1f4d11 100644 --- a/pkg/media/gstreamer.go +++ b/pkg/media/gstreamer.go @@ -68,6 +68,7 @@ func SelfTest(ctx context.Context) error { sinkele, err := gst.NewElementWithProperties("appsink", map[string]interface{}{ "name": "self-test-sink", + "sync": false, }) if err != nil { return fmt.Errorf("failed to create appsink element: %w", err) diff --git a/pkg/media/thumbnail.go b/pkg/media/thumbnail.go index 1dd5a9986..7b2d0a0c6 100644 --- a/pkg/media/thumbnail.go +++ b/pkg/media/thumbnail.go @@ -71,7 +71,7 @@ func thumbnailFromMP4(ctx context.Context, flat []byte, w io.Writer, format stri pipeline, err := gst.NewPipelineFromString(strings.Join([]string{ "appsrc name=src ! decodebin ! videoconvert ! videoscale ! videorate ! capsfilter caps=video/x-raw,width=[1,1280],height=[1,720],pixel-aspect-ratio=1/1,framerate=1/999999 ! ", encoder, - " ! appsink name=appsink", + " ! appsink sync=false name=appsink", }, "\n")) if err != nil { return fmt.Errorf("create thumbnail pipeline: %w", err) -- 2.51.2