Skip to content

Commit cf98ff8

Browse files
committed
[MSE][GStreamer] Support markEndOfStream() before appendBuffer()
https://bugs.webkit.org/show_bug.cgi?id=278726 Reviewed by Xabier Rodriguez-Calvar. MediaSource::markEndOfStream() causes the SourceBuffer TrackBuffers to push EOS to playback pipeline through WebKitMediaSrc. When that happens without any SourceBuffer::appendBuffer() call, the pipeline can't finish autoplugging and parsebin triggers an unrecoverable error. That's on GStreamer 1.18.6 at least. On GStreamer 1.24 no error is triggered, but still, playback (of no data) never finishes. While it's certainly possible to change parsebin to not trigger the error, the truth is that this seems legitimate GStreamer behaviour that shouldn't be altered (and also, doesn't fix the problem by itself, I've checked it, playback doesn't finish, see previous paragraph). This commit adds support for temporarily ignoring the error when using an affected GStreamer version, and asking the pipeline to change to READY state. It also notifies HTMLMediaElement about timeChanged, so the "ended" event can be triggered (after all, there's no other position to go beyond 0, as there are no samples and 0 is already the duration, so technically playback is ended). See: #1366 * Source/WebCore/html/HTMLMediaElement.cpp: (WebCore::HTMLMediaElement::mediaPlayerTimeChanged): Allow time change processing when duration is zero and current time is zero. * Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp: (WebCore::MediaPlayerPrivateGStreamer::handleMessage): Ignore errors when the m_ignoreErrors flag is enabled. * Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h: Add m_ignoreErrors flag. * Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp: (WebCore::MediaPlayerPrivateGStreamerMSE::updateStates): Report time changed on EOS with no buffer. (WebCore::MediaPlayerPrivateGStreamerMSE::setEosWithNoBuffers): Set the m_isEosWithNoBuffer flag. Change the pipeline to READY when enabled (with errors disabled on older GStreamer versions). * Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.h: Added m_isEosWithNoBuffer flag. * Source/WebCore/platform/graphics/gstreamer/mse/MediaSourcePrivateGStreamer.cpp: (WebCore::MediaSourcePrivateGStreamer::markEndOfStream): Detect the "EOS with no buffers" condition and report it to the player private. (WebCore::MediaSourcePrivateGStreamer::unmarkEndOfStream): Disable the "EOS with no buffers" condition. * Source/WebCore/platform/graphics/gstreamer/mse/MediaSourcePrivateGStreamer.h: Added unmarkEndOfStream(). Canonical link: https://commits.webkit.org/282958@main
1 parent d325573 commit cf98ff8

6 files changed

Lines changed: 37 additions & 3 deletions

File tree

Source/WebCore/html/HTMLMediaElement.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5167,7 +5167,7 @@ void HTMLMediaElement::mediaPlayerTimeChanged()
51675167
double playbackRate = requestedPlaybackRate();
51685168

51695169
// When the current playback position reaches the end of the media resource then the user agent must follow these steps:
5170-
if (dur && dur.isValid() && !dur.isPositiveInfinite() && !dur.isNegativeInfinite()) {
5170+
if ((dur || (!dur && !now)) && dur.isValid() && !dur.isPositiveInfinite() && !dur.isNegativeInfinite()) {
51715171
// If the media element has a loop attribute specified and does not have a current media controller,
51725172
if (loop() && !m_mediaController && playbackRate > 0) {
51735173
m_sentEndEvent = false;

Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1814,7 +1814,7 @@ void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
18141814
gst_message_parse_error(message, &err.outPtr(), &debug.outPtr());
18151815
GST_ERROR_OBJECT(pipeline(), "%s (url=%s) (code=%d)", err->message, m_url.string().utf8().data(), err->code);
18161816

1817-
if (m_shouldResetPipeline || !m_missingPluginCallbacks.isEmpty() || m_didErrorOccur)
1817+
if (m_shouldResetPipeline || !m_missingPluginCallbacks.isEmpty() || m_didErrorOccur || m_ignoreErrors)
18181818
break;
18191819

18201820
m_errorMessage = String::fromLatin1(err->message);

Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateInterface
449449
#endif
450450

451451
std::optional<GstVideoDecoderPlatform> m_videoDecoderPlatform;
452+
bool m_ignoreErrors { false };
452453

453454
String errorMessage() const override { return m_errorMessage; }
454455

Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,11 @@ void MediaPlayerPrivateGStreamerMSE::updateStates()
429429
} else if (!isSeeking && !shouldBePlaying && m_isPipelinePlaying) {
430430
if (changePipelineState(GST_STATE_PAUSED) == ChangePipelineStateResult::Failed)
431431
GST_ERROR_OBJECT(pipeline(), "Setting the pipeline to PAUSED failed");
432+
} else if (m_isEosWithNoBuffers) {
433+
if (m_player) {
434+
// Trigger playback end detection in HTMLMediaElement.
435+
m_player->timeChanged();
436+
}
432437
}
433438
}
434439

@@ -476,6 +481,23 @@ void MediaPlayerPrivateGStreamerMSE::startSource(const Vector<RefPtr<MediaSource
476481
webKitMediaSrcEmitStreams(WEBKIT_MEDIA_SRC(m_source.get()), m_tracks);
477482
}
478483

484+
void MediaPlayerPrivateGStreamerMSE::setEosWithNoBuffers(bool eosWithNoBuffers)
485+
{
486+
m_isEosWithNoBuffers = eosWithNoBuffers;
487+
// Parsebin will trigger an error, instruct MediaPlayerPrivateGStreamer to ignore it.
488+
if (eosWithNoBuffers) {
489+
// On GStreamer 1.18.6, EOS with no buffers causes a parsebin error here:
490+
// https://github.com/GStreamer/gst-plugins-base/blob/1.18.6/gst/playback/gstparsebin.c#L3495
491+
// On GStreamer 1.24 (at least) that doesn't happen. Let's play safe and protect against the
492+
// error in lower versions.
493+
if (!webkitGstCheckVersion(1, 24, 0))
494+
m_ignoreErrors = true;
495+
changePipelineState(GST_STATE_READY);
496+
if (!webkitGstCheckVersion(1, 24, 0))
497+
m_ignoreErrors = false;
498+
}
499+
}
500+
479501
void MediaPlayerPrivateGStreamerMSE::getSupportedTypes(HashSet<String, ASCIICaseInsensitiveHash>& types)
480502
{
481503
GStreamerRegistryScannerMSE::getSupportedDecodingTypes(types);

Source/WebCore/platform/graphics/gstreamer/mse/MediaPlayerPrivateGStreamerMSE.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class MediaPlayerPrivateGStreamerMSE : public MediaPlayerPrivateGStreamer {
8484
void startSource(const Vector<RefPtr<MediaSourceTrackGStreamer>>& tracks);
8585
WebKitMediaSrc* webKitMediaSrc() { return reinterpret_cast<WebKitMediaSrc*>(m_source.get()); }
8686

87+
void setEosWithNoBuffers(bool);
88+
8789
#if !RELEASE_LOG_DISABLED
8890
WTFLogChannel& logChannel() const final { return WebCore::LogMediaSource; }
8991
#endif
@@ -112,6 +114,7 @@ class MediaPlayerPrivateGStreamerMSE : public MediaPlayerPrivateGStreamer {
112114
Vector<RefPtr<MediaSourceTrackGStreamer>> m_tracks;
113115

114116
bool m_isWaitingForPreroll = true;
117+
bool m_isEosWithNoBuffers = false;
115118
MediaPlayer::ReadyState m_mediaSourceReadyState = MediaPlayer::ReadyState::HaveNothing;
116119
MediaPlayer::NetworkState m_mediaSourceNetworkState = MediaPlayer::NetworkState::Empty;
117120
};

Source/WebCore/platform/graphics/gstreamer/mse/MediaSourcePrivateGStreamer.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,23 @@ void MediaSourcePrivateGStreamer::markEndOfStream(EndOfStreamStatus endOfStreamS
136136
}
137137
GST_DEBUG_OBJECT(m_playerPrivate.pipeline(), "Marking EOS, status is %s", statusString);
138138
#endif
139-
if (endOfStreamStatus == EosNoError)
139+
if (endOfStreamStatus == EosNoError) {
140140
m_playerPrivate.setNetworkState(MediaPlayer::NetworkState::Loaded);
141+
142+
auto bufferedRanges = buffered();
143+
if (!bufferedRanges || !bufferedRanges->length()) {
144+
GST_DEBUG("EOS with no buffers");
145+
m_playerPrivate.setEosWithNoBuffers(true);
146+
}
147+
}
141148
m_isEnded = true;
142149
}
143150

144151
void MediaSourcePrivateGStreamer::unmarkEndOfStream()
145152
{
146153
ASSERT(isMainThread());
147154
GST_DEBUG_OBJECT(m_playerPrivate.pipeline(), "Un-marking EOS");
155+
m_playerPrivate.setEosWithNoBuffers(false);
148156
m_isEnded = false;
149157
}
150158

0 commit comments

Comments
 (0)