Skip to content

Commit

Permalink
Remove SourceBuffer media tracks on detach from media element
Browse files Browse the repository at this point in the history
Also added a layout test for this.

BUG=249428

Review-Url: https://codereview.chromium.org/1846863002
Cr-Commit-Position: refs/heads/master@{#390460}
  • Loading branch information
servolk authored and Commit bot committed Apr 28, 2016
1 parent beef96f commit 2f5b9a2
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,122 @@
<script src="/w3c/resources/testharness.js"></script>
<script src="/w3c/resources/testharnessreport.js"></script>
<script src="mediasource-util.js"></script>

<link rel='stylesheet' href='/w3c/resources/testharness.css'>
</head>
<body>
<div id="log"></div>
<script>
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
var initSegment = MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.init);
test.expectEvent(sourceBuffer, 'updateend', 'initSegment append ended.');
test.expectEvent(sourceBuffer.audioTracks, 'addtrack', 'sourceBuffer.videoTracks addtrack event');
test.expectEvent(sourceBuffer.videoTracks, 'addtrack', 'sourceBuffer.videoTracks addtrack event');
test.expectEvent(mediaElement.audioTracks, 'addtrack', 'mediaElement.videoTracks addtrack event');
test.expectEvent(mediaElement.videoTracks, 'addtrack', 'mediaElement.videoTracks addtrack event');
sourceBuffer.appendBuffer(initSegment);
test.waitForExpectedEvents(function()
{
assert_equals(sourceBuffer.videoTracks.length, 1, "videoTracks.length");
assert_equals(sourceBuffer.videoTracks[0].id, "1", "videoTrack.id");
assert_equals(sourceBuffer.videoTracks[0].kind, "main", "videoTrack.kind");
assert_equals(sourceBuffer.videoTracks[0].label, "", "videoTrack.label");
assert_equals(sourceBuffer.videoTracks[0].language, "eng", "videoTrack.language");
assert_equals(sourceBuffer.videoTracks[0].sourceBuffer, sourceBuffer, "videoTrack.sourceBuffer");
// The first video track is selected by default.
assert_true(sourceBuffer.videoTracks[0].selected, "sourceBuffer.videoTracks[0].selected");
function loadMediaAndVerifyAddedTracks(test, mediaElement, segmentInfo, sourceBuffer, mediaData, successCallback)
{
var initSegment = MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.init);
test.expectEvent(sourceBuffer.audioTracks, "addtrack", "sourceBuffer.audioTracks addtrack event");
test.expectEvent(sourceBuffer.videoTracks, "addtrack", "sourceBuffer.videoTracks addtrack event");
test.expectEvent(mediaElement.audioTracks, "addtrack", "mediaElement.audioTracks addtrack event");
test.expectEvent(mediaElement.videoTracks, "addtrack", "mediaElement.videoTracks addtrack event");
test.expectEvent(mediaElement, "loadedmetadata", "loadedmetadata done.");
test.expectEvent(sourceBuffer, "updateend", "initSegment append ended.");
sourceBuffer.appendBuffer(initSegment);
test.waitForExpectedEvents(function()
{
assert_equals(sourceBuffer.videoTracks.length, 1, "videoTracks.length");
assert_equals(sourceBuffer.videoTracks[0].kind, "main", "videoTrack.kind");
assert_equals(sourceBuffer.videoTracks[0].label, "", "videoTrack.label");
assert_equals(sourceBuffer.videoTracks[0].language, "eng", "videoTrack.language");
assert_equals(sourceBuffer.videoTracks[0].sourceBuffer, sourceBuffer, "videoTrack.sourceBuffer");
// The first video track is selected by default.
assert_true(sourceBuffer.videoTracks[0].selected, "sourceBuffer.videoTracks[0].selected");

assert_equals(sourceBuffer.audioTracks.length, 1, "audioTracks.length");
assert_equals(sourceBuffer.audioTracks[0].kind, "main", "audioTrack.kind");
assert_equals(sourceBuffer.audioTracks[0].label, "", "audioTrack.label");
assert_equals(sourceBuffer.audioTracks[0].language, "eng", "audioTrack.language");
assert_equals(sourceBuffer.audioTracks[0].sourceBuffer, sourceBuffer, "audioTrack.sourceBuffer");
// The first audio track is enabled by default.
assert_true(sourceBuffer.audioTracks[0].enabled, "sourceBuffer.audioTracks[0].enabled");

assert_not_equals(sourceBuffer.audioTracks[0].id, sourceBuffer.videoTracks[0].id, "track ids must be unique");

assert_equals(mediaElement.videoTracks.length, 1, "videoTracks.length");
assert_equals(mediaElement.videoTracks[0], sourceBuffer.videoTracks[0], "mediaElement.videoTrack == sourceBuffer.videoTrack");

assert_equals(mediaElement.audioTracks.length, 1, "audioTracks.length");
assert_equals(mediaElement.audioTracks[0], sourceBuffer.audioTracks[0], "mediaElement.audioTrack == sourceBuffer.audioTrack");

successCallback();
});
}

mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
loadMediaAndVerifyAddedTracks(test, mediaElement, segmentInfo, sourceBuffer, mediaData, test.step_func_done());
}, "Check that media tracks and their properties are populated properly");

function verifyTrackRemoval(test, mediaElement, mediaSource, sourceBuffer, trackRemovalAction, successCallback) {
assert_equals(sourceBuffer.audioTracks.length, 1, "audioTracks.length");
assert_equals(sourceBuffer.audioTracks[0].id, "2", "audioTrack.id");
assert_equals(sourceBuffer.audioTracks[0].kind, "main", "audioTrack.kind");
assert_equals(sourceBuffer.audioTracks[0].label, "", "audioTrack.label");
assert_equals(sourceBuffer.audioTracks[0].language, "eng", "audioTrack.language");
assert_equals(sourceBuffer.audioTracks[0].sourceBuffer, sourceBuffer, "audioTrack.sourceBuffer");
// The first audio track is enabled by default.
assert_true(sourceBuffer.audioTracks[0].enabled, "sourceBuffer.audioTracks[0].enabled");
assert_equals(sourceBuffer.videoTracks.length, 1, "videoTracks.length");
assert_true(sourceBuffer.videoTracks[0].selected, "sourceBuffer.videoTracks[0].selected");

var audioTrack = sourceBuffer.audioTracks[0];
var videoTrack = sourceBuffer.videoTracks[0];

// Verify removetrack events.
test.expectEvent(sourceBuffer.audioTracks, "removetrack", "sourceBuffer.audioTracks removetrack event");
test.expectEvent(sourceBuffer.videoTracks, "removetrack", "sourceBuffer.videoTracks removetrack event");
test.expectEvent(mediaElement.audioTracks, "removetrack", "mediaElement.audioTracks removetrack event");
test.expectEvent(mediaElement.videoTracks, "removetrack", "mediaElement.videoTracks removetrack event");

// Removing enabled audio track and selected video track should fire "change" events on mediaElement track lists.
test.expectEvent(mediaElement.audioTracks, "change", "mediaElement.audioTracks changed.");
test.expectEvent(mediaElement.videoTracks, "change", "mediaElement.videoTracks changed.");

trackRemovalAction();

test.waitForExpectedEvents(function()
{
assert_equals(mediaSource.sourceBuffers.length, 0, "mediaSource.sourceBuffers.length");
assert_equals(mediaElement.videoTracks.length, 0, "videoTracks.length");
assert_equals(mediaElement.audioTracks.length, 0, "audioTracks.length");
assert_equals(sourceBuffer.videoTracks.length, 0, "videoTracks.length");
assert_equals(sourceBuffer.audioTracks.length, 0, "audioTracks.length");
// Since audio and video tracks have been removed, their .sourceBuffer property should be null now.
assert_equals(audioTrack.sourceBuffer, null, "audioTrack.sourceBuffer");
assert_equals(videoTrack.sourceBuffer, null, "videoTrack.sourceBuffer");
successCallback();
});
}

mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
loadMediaAndVerifyAddedTracks(test, mediaElement, segmentInfo, sourceBuffer, mediaData, test.step_func(function ()
{
verifyTrackRemoval(test, mediaElement, mediaSource, sourceBuffer, test.step_func(function ()
{
mediaSource.removeSourceBuffer(sourceBuffer);
}), test.step_func_done());
}));
}, "Media tracks must be removed when the SourceBuffer is removed from the MediaSource");

assert_equals(mediaElement.videoTracks.length, 1, "videoTracks.length");
assert_equals(mediaElement.videoTracks[0], sourceBuffer.videoTracks[0], "mediaElement.videoTrack == sourceBuffer.videoTrack");
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
loadMediaAndVerifyAddedTracks(test, mediaElement, segmentInfo, sourceBuffer, mediaData, test.step_func(function ()
{
verifyTrackRemoval(test, mediaElement, mediaSource, sourceBuffer, test.step_func(function ()
{
mediaElement.src = "";
}), test.step_func_done());
}));
}, "Media tracks must be removed when the HTMLMediaElement.src is changed");

assert_equals(mediaElement.audioTracks.length, 1, "audioTracks.length");
assert_equals(mediaElement.audioTracks[0], sourceBuffer.audioTracks[0], "mediaElement.audioTrack == sourceBuffer.audioTrack");
mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData)
{
loadMediaAndVerifyAddedTracks(test, mediaElement, segmentInfo, sourceBuffer, mediaData, test.step_func(function ()
{
verifyTrackRemoval(test, mediaElement, mediaSource, sourceBuffer, test.step_func(function ()
{
mediaElement.load();
}), test.step_func_done());
}));
}, "Media tracks must be removed when the HTMLMediaElement.load() is called");

test.done();
});
}, "MediaSource media track properties");
</script>
</body>
</html>
Expand Down
12 changes: 5 additions & 7 deletions third_party/WebKit/Source/modules/mediasource/MediaSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,8 @@ void MediaSource::removeSourceBuffer(SourceBuffer* buffer, ExceptionState& excep
return;
}

// 2. If the sourceBuffer.updating attribute equals true, then run the following steps: ...
buffer->abortIfUpdating();

// Steps 3-8 are related to updating audioTracks, videoTracks, and textTracks which aren't implmented yet.
// FIXME(91649): support track selection
// Steps 2-8 are implemented by SourceBuffer::removedFromMediaSource.
buffer->removedFromMediaSource();

// 9. If sourceBuffer is in activeSourceBuffers, then remove sourceBuffer from activeSourceBuffers ...
m_activeSourceBuffers->remove(buffer);
Expand All @@ -191,7 +188,7 @@ void MediaSource::removeSourceBuffer(SourceBuffer* buffer, ExceptionState& excep
m_sourceBuffers->remove(buffer);

// 11. Destroy all resources for sourceBuffer.
buffer->removedFromMediaSource();
// This should have been done already by SourceBuffer::removedFromMediaSource (steps 2-8) above.
}

void MediaSource::onReadyStateChange(const AtomicString& oldState, const AtomicString& newState)
Expand All @@ -215,6 +212,8 @@ void MediaSource::onReadyStateChange(const AtomicString& oldState, const AtomicS
m_sourceBuffers->item(i)->removedFromMediaSource();
m_sourceBuffers->clear();

m_attachedElement.clear();

scheduleEvent(EventTypeNames::sourceclose);
}

Expand Down Expand Up @@ -450,7 +449,6 @@ void MediaSource::setReadyState(const AtomicString& state)

if (state == closedKeyword()) {
m_webMediaSource.clear();
m_attachedElement.clear();
}

if (oldState == state)
Expand Down
73 changes: 73 additions & 0 deletions third_party/WebKit/Source/modules/mediasource/SourceBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -484,12 +484,85 @@ void SourceBuffer::removedFromMediaSource()
WTF_LOG(Media, "SourceBuffer(%p)::removedFromMediaSource", this);
abortIfUpdating();

if (RuntimeEnabledFeatures::audioVideoTracksEnabled()) {
ASSERT(m_source);
if (m_source->mediaElement()->audioTracks().length() > 0
|| m_source->mediaElement()->videoTracks().length() > 0) {
removeMediaTracks();
}
}

m_webSourceBuffer->removedFromMediaSource();
m_webSourceBuffer.clear();
m_source = nullptr;
m_asyncEventQueue = nullptr;
}

void SourceBuffer::removeMediaTracks()
{
ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
// Spec: http://w3c.github.io/media-source/#widl-MediaSource-removeSourceBuffer-void-SourceBuffer-sourceBuffer
ASSERT(m_source);

HTMLMediaElement* mediaElement = m_source->mediaElement();
ASSERT(mediaElement);
// 3. Let SourceBuffer audioTracks list equal the AudioTrackList object returned by sourceBuffer.audioTracks.
// 4. If the SourceBuffer audioTracks list is not empty, then run the following steps:
// 4.1 Let HTMLMediaElement audioTracks list equal the AudioTrackList object returned by the audioTracks attribute on the HTMLMediaElement.
// 4.2 Let the removed enabled audio track flag equal false.
bool removedEnabledAudioTrack = false;
// 4.3 For each AudioTrack object in the SourceBuffer audioTracks list, run the following steps:
while (audioTracks().length() > 0) {
AudioTrack* audioTrack = audioTracks().anonymousIndexedGetter(0);
// 4.3.1 Set the sourceBuffer attribute on the AudioTrack object to null.
SourceBufferTrackBaseSupplement::setSourceBuffer(*audioTrack, nullptr);
// 4.3.2 If the enabled attribute on the AudioTrack object is true, then set the removed enabled audio track flag to true.
if (audioTrack->enabled())
removedEnabledAudioTrack = true;
// 4.3.3 Remove the AudioTrack object from the HTMLMediaElement audioTracks list.
// 4.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement audioTracks list.
mediaElement->audioTracks().remove(audioTrack->trackId());
// 4.3.5 Remove the AudioTrack object from the SourceBuffer audioTracks list.
// 4.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the SourceBuffer audioTracks list.
audioTracks().remove(audioTrack->trackId());
}
// 4.4 If the removed enabled audio track flag equals true, then queue a task to fire a simple event named change at the HTMLMediaElement audioTracks list.
if (removedEnabledAudioTrack) {
Event* event = Event::create(EventTypeNames::change);
event->setTarget(&mediaElement->audioTracks());
mediaElement->scheduleEvent(event);
}

// 5. Let SourceBuffer videoTracks list equal the VideoTrackList object returned by sourceBuffer.videoTracks.
// 6. If the SourceBuffer videoTracks list is not empty, then run the following steps:
// 6.1 Let HTMLMediaElement videoTracks list equal the VideoTrackList object returned by the videoTracks attribute on the HTMLMediaElement.
// 6.2 Let the removed selected video track flag equal false.
bool removedSelectedVideoTrack = false;
// 6.3 For each VideoTrack object in the SourceBuffer videoTracks list, run the following steps:
while (videoTracks().length() > 0) {
VideoTrack* videoTrack = videoTracks().anonymousIndexedGetter(0);
// 6.3.1 Set the sourceBuffer attribute on the VideoTrack object to null.
SourceBufferTrackBaseSupplement::setSourceBuffer(*videoTrack, nullptr);
// 6.3.2 If the selected attribute on the VideoTrack object is true, then set the removed selected video track flag to true.
if (videoTrack->selected())
removedSelectedVideoTrack = true;
// 6.3.3 Remove the VideoTrack object from the HTMLMediaElement videoTracks list.
// 6.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement videoTracks list.
mediaElement->videoTracks().remove(videoTrack->trackId());
// 6.3.5 Remove the VideoTrack object from the SourceBuffer videoTracks list.
// 6.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not cancelable, and that uses the TrackEvent interface, at the SourceBuffer videoTracks list.
videoTracks().remove(videoTrack->trackId());
}
// 6.4 If the removed selected video track flag equals true, then queue a task to fire a simple event named change at the HTMLMediaElement videoTracks list.
if (removedSelectedVideoTrack) {
Event* event = Event::create(EventTypeNames::change);
event->setTarget(&mediaElement->videoTracks());
mediaElement->scheduleEvent(event);
}

// 7-8. TODO(servolk): Remove text tracks once SourceBuffer has text tracks.
}

template<class T>
T* findExistingTrackById(const TrackListBase<T>& trackList, const String& id)
{
Expand Down
2 changes: 2 additions & 0 deletions third_party/WebKit/Source/modules/mediasource/SourceBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ class SourceBuffer final
void appendStreamDone(bool success);
void clearAppendStreamState();

void removeMediaTracks();

// FileReaderLoaderClient interface
void didStartLoading() override;
void didReceiveDataForClient(const char* data, unsigned dataLength) override;
Expand Down

0 comments on commit 2f5b9a2

Please sign in to comment.