Skip to content
Permalink
Browse files
Playback stops although the progress bar moves
https://bugs.webkit.org/show_bug.cgi?id=248585
rdar://102846199

Reviewed by Eric Carlson.

When playback has stalled due to insufficient data being buffered, the effective rate
should be 0 as time is no longer progressing.
While the GPU process would indicate that the rate has changed once the player has
stalled, the effective rate reported would remain the same causing the current time
position to continue moving as it's estimated based on the effective rate.

Fly-By fix: set the new rate in HTMLMediaElement if previously the requested rate
is different.

* LayoutTests/http/tests/media/video-play-stall.html: Update test to ensure currentTime doesn't progress
(WebCore::HTMLMediaElement::updatePlaybackRate):
* Source/WebCore/platform/graphics/avfoundation/MediaPlayerPrivateAVFoundation.cpp:
(WebCore::MediaPlayerPrivateAVFoundation::updateStates):
* Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
(WebCore::MediaPlayerPrivateAVFoundationObjC::effectiveRate const):

Canonical link: https://commits.webkit.org/257403@main
  • Loading branch information
jyavenard committed Dec 6, 2022
1 parent 6b01beb commit 384fb5b098b89ed16edca08da12f59edd924a952
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 20 deletions.
@@ -4,11 +4,10 @@ Test that a stalled event is sent when media loading stalls.
EVENT(durationchange)
EVENT(loadedmetadata)
EVENT(loadeddata)
EVENT(canplay)
RUN(video.play())
EVENT(stalled)
EXPECTED (video.currentTime != '0') OK
TEST(video.currentTime <= bufferedend) OK
TEST(gotCanplay ? video.paused == false : video.paused == true) OK
EXPECTED (video.playbackRate === '1') OK
EXPECTED (video.paused === 'false') OK
TEST(video.duration > bufferedend) OK
END OF TEST

@@ -4,36 +4,45 @@
<title>'stalled' event test</title>
<script src=../../media-resources/media-file.js></script>
<script src=../../media-resources/video-test.js></script>
<script src=../../media-resources/utilities.js></script>
<script>

var playCount = 0;
var bufferedend = 0;
var gotCanplay = false;

function start()
function start()
{
findMediaElement();
waitForEvent('durationchange');
waitForEvent('loadedmetadata');
waitForEvent('loadeddata');

mediaElement.addEventListener('canplay', function () {
// 'stalled' takes three seconds to fire, so 'canplay' may fire more than once
// depending on how the engine parses incoming media data.
if (++playCount > 1)
return;
consoleWrite("EVENT(canplay)");
run("video.play()");
} );
once(mediaElement, 'canplay', () => {
video.play()
gotCanplay = true;
});

waitForEvent('stalled', function () {
testExpected("video.currentTime", 0, "!=");
// The test simulates a load stalled by blocking the http server for several seconds.
// Under loads, it is possible that we failed to retrieve any content at all.
// The aim of the test being that we do emit the stalled event and that currentTime
// didn't progress we output data that would pass the expectations under both conditions.
if (video.buffered.length > 0)
bufferedend = video.buffered.end(0);
test("video.currentTime <= bufferedend", false);
// If we loaded some content, playback has started
test("gotCanplay ? video.paused == false : video.paused == true");
testExpected("video.playbackRate", 1, "===");
testExpected("video.paused", false, "===");
test("video.duration > bufferedend", false);
// TestRunner waits for the complete frame download before finishing, so having the video load stalled
// prevents TestRunner from finishing. Changing the src will abort the download and let the test finish.
video.src = "";
endTest();
} );

var mediaFile = findMediaFile("video", "../../../../media/content/long-test");
var mimeType = mimeTypeForFile(mediaFile);
video.src = "http://127.0.0.1:8000/media/resources/serve_video.py?name=" + mediaFile + "&type=" + mimeType + "&stallOffset=1000000&stallDuration=60&chunkSize=1024";
video.src = "http://127.0.0.1:8000/media/resources/serve_video.py?name=" + mediaFile + "&type=" + mimeType + "&stallOffset=50000&stallDuration=20&chunkSize=1024";
}

</script>
@@ -3873,7 +3873,7 @@ void HTMLMediaElement::setPlaybackRate(double rate)
void HTMLMediaElement::updatePlaybackRate()
{
double requestedRate = requestedPlaybackRate();
if (m_player && potentiallyPlaying() && m_player->effectiveRate() != requestedRate)
if (m_player && potentiallyPlaying() && m_player->rate() != requestedRate)
m_player->setRate(requestedRate);
}

@@ -524,7 +524,7 @@ void MediaPlayerPrivateAVFoundation::updateStates()
// -loadValuesAsynchronouslyForKeys:completionHandler: has invoked its handler; test status of keys and determine state.
AssetStatus assetStatus = this->assetStatus();
ItemStatus itemStatus = playerItemStatus();

m_assetIsPlayable = (assetStatus == MediaPlayerAVAssetStatusPlayable);
if (m_readyState < MediaPlayer::ReadyState::HaveMetadata && assetStatus > MediaPlayerAVAssetStatusLoading) {
if (m_assetIsPlayable) {
@@ -1687,7 +1687,7 @@ static URL conformFragmentIdentifierForURL(const URL& url)
if (!metaDataAvailable())
return 0;

return m_cachedRate;
return m_cachedTimeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate ? 0.0 : m_cachedRate;
}

double MediaPlayerPrivateAVFoundationObjC::seekableTimeRangesLastModifiedTime() const

0 comments on commit 384fb5b

Please sign in to comment.