Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add BufferedChangeEvent object
https://bugs.webkit.org/show_bug.cgi?id=256985
rdar://109532732

Reviewed by Youenn Fablet.

Pass to `bufferedchange` a dictionary containing the range added and removed
from the last operation on the source buffer.

Fly-by: remove the need for code to be conditional on both ENABLE(MEDIA_SOURCE) and
ENABLE(MANAGED_MEDIA_SOURCE), assume that if the latter is true, then
so is the first, as doing otherwise would be totally nonsensical.

* LayoutTests/media/media-source/media-managedmse-bufferedchange-expected.txt:
* LayoutTests/media/media-source/media-managedmse-bufferedchange.html: Check that object received is of type BufferedChangeEvent
* LayoutTests/media/media-source/mock-managedmse-bufferedchange-expected.txt: Added.
* LayoutTests/media/media-source/mock-managedmse-bufferedchange.html: Added.
* LayoutTests/media/utilities.js:
(timeRangesToString):
* Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml:
* Source/WebCore/CMakeLists.txt:
* Source/WebCore/DerivedSources-input.xcfilelist:
* Source/WebCore/DerivedSources-output.xcfilelist:
* Source/WebCore/DerivedSources.make:
* Source/WebCore/Modules/mediasource/BufferedChangeEvent.cpp: Copied from Source/WebCore/Modules/mediasource/ManagedSourceBuffer.cpp.
(WebCore::BufferedChangeEvent::BufferedChangeEvent):
(WebCore::BufferedChangeEvent::eventInterface const):
* Source/WebCore/Modules/mediasource/BufferedChangeEvent.h: Copied from Source/WebCore/Modules/mediasource/ManagedSourceBuffer.h.
* Source/WebCore/Modules/mediasource/BufferedChangeEvent.idl: Copied from Source/WebCore/Modules/mediasource/ManagedSourceBuffer.idl.
* Source/WebCore/Modules/mediasource/ManagedMediaSource.cpp:
* Source/WebCore/Modules/mediasource/ManagedMediaSource.h:
* Source/WebCore/Modules/mediasource/ManagedMediaSource.idl:
* Source/WebCore/Modules/mediasource/ManagedSourceBuffer.cpp:
* Source/WebCore/Modules/mediasource/ManagedSourceBuffer.h:
* Source/WebCore/Modules/mediasource/ManagedSourceBuffer.idl:
* Source/WebCore/Modules/mediasource/MediaSource.cpp:
* Source/WebCore/Modules/mediasource/MediaSource.h:
* Source/WebCore/Modules/mediasource/SourceBuffer.cpp:
(WebCore::SourceBuffer::buffered):
(WebCore::SourceBuffer::sourceBufferPrivateBufferedChanged):
* Source/WebCore/Modules/mediasource/SourceBuffer.h:
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/bindings/js/WebCoreBuiltinNames.h:
* Source/WebCore/dom/EventNames.in:
* Source/WebCore/html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::setBufferingPolicy): Change to be conditional on MANAGED_MEDIA_SOURCE
(WebCore::HTMLMediaElement::purgeBufferedDataIfPossible): same as above
* Source/WebCore/platform/graphics/PlatformTimeRanges.cpp:
(WebCore::PlatformTimeRanges::operator+=): Add alias to `unionWith` method for naming consistency.
(WebCore::PlatformTimeRanges::operator-=): Add operator to process calculation of range removal.
* Source/WebCore/platform/graphics/PlatformTimeRanges.h:

Canonical link: https://commits.webkit.org/264472@main
  • Loading branch information
jyavenard committed May 24, 2023
1 parent 19c290d commit 03f910f
Show file tree
Hide file tree
Showing 30 changed files with 510 additions and 16 deletions.
Expand Up @@ -9,12 +9,14 @@ EVENT(update)
Append a media segment.
RUN(sourceBuffer.appendBuffer(loader.mediaSegment(0)))
onbufferedchange called.
EXPECTED (bufferedChangeEvent.__proto__ == '[object BufferedChangeEvent]') OK
EVENT(bufferedchange)
EVENT(update)
EXPECTED (sourceBuffer.buffered.length == '1') OK
Clean sourcebuffer of all content.
RUN(sourceBuffer.remove(0, sourceBuffer.buffered.end(0)))
onbufferedchange called.
EXPECTED (bufferedChangeEvent.__proto__ == '[object BufferedChangeEvent]') OK
EVENT(bufferedchange)
EVENT(update)
EXPECTED (sourceBuffer.buffered.length == '0') OK
Expand Down
Expand Up @@ -8,6 +8,7 @@
var loader;
var source;
var sourceBuffer;
var bufferedChangeEvent;

function loaderPromise(loader) {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -38,8 +39,10 @@
testExpected('sourceBuffer.__proto__', ManagedSourceBuffer.prototype);
testExpected('sourceBuffer.onbufferedchange !== undefined', true);

sourceBuffer.onbufferedchange = () => {
sourceBuffer.onbufferedchange = (e) => {
consoleWrite('onbufferedchange called.')
bufferedChangeEvent = e;
testExpected('bufferedChangeEvent.__proto__', BufferedChangeEvent.prototype);
};

run('sourceBuffer.appendBuffer(loader.initSegment())');
Expand Down
@@ -0,0 +1,103 @@

EXPECTED (source.readyState == 'closed') OK
RUN(sourceElement = document.createElement("source"))
RUN(sourceElement.type = "video/mock; codecs=mock")
RUN(sourceElement.src = URL.createObjectURL(source))
RUN(video.appendChild(sourceElement))
RUN(sourceElement = document.createElement("source"))
RUN(sourceElement.src = "http://foo.com/playlist.m3u8")
RUN(sourceElement.type = "application/vnd.apple.mpegurl")
RUN(video.appendChild(sourceElement))
EVENT(sourceopen)
RUN(sourceBuffer = source.addSourceBuffer("video/mock; codecs=mock"))
RUN(sourceBuffer.appendBuffer(initSegment))
EVENT(updateend)
RUN(sourceBuffer.appendBuffer(syncSampleRun(5, 10)))
onbufferedchange called.
e.addedRanges = [5, 10)
e.removedRanges = null
EVENT(update)
EXPECTED (sourceBuffer.buffered.length == '1') OK
RUN(sourceBuffer.appendBuffer(syncSampleRun(0, 5)))
onbufferedchange called.
e.addedRanges = [0, 5)
e.removedRanges = null
EVENT(update)
EXPECTED (sourceBuffer.buffered.length == '1') OK
Clean sourcebuffer of all content.
RUN(sourceBuffer.remove(0, sourceBuffer.buffered.end(0)))
onbufferedchange called.
e.addedRanges = null
e.removedRanges = [0, 10)
EVENT(update)
EXPECTED (sourceBuffer.buffered.length == '0') OK
RUN(sourceBuffer.appendBuffer(syncSampleRun(0, 3)))
onbufferedchange called.
e.addedRanges = [0, 3)
e.removedRanges = null
EVENT(update)
RUN(sourceBuffer.appendBuffer(syncSampleRun(3, 6)))
onbufferedchange called.
e.addedRanges = [3, 6)
e.removedRanges = null
EVENT(update)
RUN(sourceBuffer.appendBuffer(syncSampleRun(6, 9)))
onbufferedchange called.
e.addedRanges = [6, 9)
e.removedRanges = null
EVENT(update)
Re-adding the same samples does not update the buffer
RUN(sourceBuffer.appendBuffer(syncSampleRun(0, 3)))
EVENT(update)
Removing first sync sample, remove the first 3 seconds block
RUN(sourceBuffer.remove(0, 1))
onbufferedchange called.
e.addedRanges = null
e.removedRanges = [0, 3)
EVENT(update)
Removing from 8 to infinity, remove to the end of existing buffer
RUN(sourceBuffer.remove(8, Infinity))
onbufferedchange called.
e.addedRanges = null
e.removedRanges = [8, 9)
EVENT(update)
Removing from empty range, does not update the buffer
RUN(sourceBuffer.remove(0, 2))
EVENT(update)
Removing from non-sync sample
RUN(sourceBuffer.remove(4, 5))
onbufferedchange called.
e.addedRanges = null
e.removedRanges = [4, 6)
EVENT(update)
Overriding sync sample, remove following block and only update buffer for missing samples
before: [3, 4)[6, 8)
RUN(sourceBuffer.appendBuffer(syncSampleRun(5, 7)))
onbufferedchange called.
e.addedRanges = [5, 6)
e.removedRanges = [7, 8)
EVENT(update)
after: [3, 4)[5, 7)
Only report samples added in missing intervals
RUN(sourceBuffer.appendBuffer(syncSampleRun(0, 10, 2)))
onbufferedchange called.
e.addedRanges = [0, 3)[4, 5)[7, 10)
e.removedRanges = null
EVENT(update)
RUN(sourceBuffer.remove(2, 4))
onbufferedchange called.
e.addedRanges = null
e.removedRanges = [2, 4)
EVENT(update)
RUN(sourceBuffer.remove(6, 8))
onbufferedchange called.
e.addedRanges = null
e.removedRanges = [6, 8)
EVENT(update)
RUN(sourceBuffer.remove(0, 10))
onbufferedchange called.
e.addedRanges = null
e.removedRanges = [0, 2)[4, 6)[8, 10)
EVENT(update)
END OF TEST

117 changes: 117 additions & 0 deletions LayoutTests/media/media-source/mock-managedmse-bufferedchange.html
@@ -0,0 +1,117 @@
<!DOCTYPE html> <!-- webkit-test-runner [ ManagedMediaSourceEnabled=true MediaSourceEnabled=true ] -->
<html>
<head>
<title>managedmediasource</title>
<script src="mock-media-source.js"></script>
<script src="../../media/video-test.js"></script>
<script src="../../media/utilities.js"></script>
<script>
var loader;
var source;
var sourceBuffer;
var bufferedChangeEvent;
var sourceElement;

if (window.internals)
internals.initializeMockMediaSource();

function syncSampleRun(start, end, stepSync = 9999999999) {
const samples = [];
for (let time = start; time < end; time++)
samples.push(makeASample(time, time, 1, 1, 1, (time - start) % stepSync == 0 ? SAMPLE_FLAG.SYNC : SAMPLE_FLAG.NONE));
return concatenateSamples(samples);
}

window.addEventListener('load', async event => {
findMediaElement();

source = new ManagedMediaSource();

testExpected('source.readyState', 'closed');
run('sourceElement = document.createElement("source")');
run('sourceElement.type = "video/mock; codecs=mock"');
run('sourceElement.src = URL.createObjectURL(source)');
run('video.appendChild(sourceElement)');
run('sourceElement = document.createElement("source")');
run('sourceElement.src = "http://foo.com/playlist.m3u8"');
run('sourceElement.type = "application/vnd.apple.mpegurl"');
run('video.appendChild(sourceElement)');
await waitFor(source, 'sourceopen');

run('sourceBuffer = source.addSourceBuffer("video/mock; codecs=mock")');
sourceBuffer.onbufferedchange = (e) => {
consoleWrite('onbufferedchange called.')
consoleWrite(`e.addedRanges = ${ timeRangesToString(e.addedRanges) }`);
consoleWrite(`e.removedRanges = ${ timeRangesToString(e.removedRanges) }`);
};

initSegment = makeAInit(10, [makeATrack(1, 'mock', TRACK_KIND.VIDEO)]);

run('sourceBuffer.appendBuffer(initSegment)');
await waitFor(sourceBuffer, 'updateend');

run('sourceBuffer.appendBuffer(syncSampleRun(5, 10))');
await waitFor(sourceBuffer, 'update');
testExpected('sourceBuffer.buffered.length', '1');

run('sourceBuffer.appendBuffer(syncSampleRun(0, 5))');
await waitFor(sourceBuffer, 'update');
testExpected('sourceBuffer.buffered.length', '1');

consoleWrite('Clean sourcebuffer of all content.');
run('sourceBuffer.remove(0, sourceBuffer.buffered.end(0))');
await waitFor(sourceBuffer, 'update');
testExpected('sourceBuffer.buffered.length', '0');

run('sourceBuffer.appendBuffer(syncSampleRun(0, 3))');
await waitFor(sourceBuffer, 'update');
run('sourceBuffer.appendBuffer(syncSampleRun(3, 6))');
await waitFor(sourceBuffer, 'update');
run('sourceBuffer.appendBuffer(syncSampleRun(6, 9))');
await waitFor(sourceBuffer, 'update');

consoleWrite('Re-adding the same samples does not update the buffer');
run('sourceBuffer.appendBuffer(syncSampleRun(0, 3))');
await waitFor(sourceBuffer, 'update');

consoleWrite('Removing first sync sample, remove the first 3 seconds block');
run('sourceBuffer.remove(0, 1)');
await waitFor(sourceBuffer, 'update');

consoleWrite('Removing from 8 to infinity, remove to the end of existing buffer');
run('sourceBuffer.remove(8, Infinity)');
await waitFor(sourceBuffer, 'update');

consoleWrite('Removing from empty range, does not update the buffer');
run('sourceBuffer.remove(0, 2)');
await waitFor(sourceBuffer, 'update');

consoleWrite('Removing from non-sync sample');
run('sourceBuffer.remove(4, 5)');
await waitFor(sourceBuffer, 'update');

consoleWrite('Overriding sync sample, remove following block and only update buffer for missing samples');
consoleWrite(`before: ${ timeRangesToString(sourceBuffer.buffered) }`);
run('sourceBuffer.appendBuffer(syncSampleRun(5, 7))');
await waitFor(sourceBuffer, 'update');
consoleWrite(`after: ${ timeRangesToString(sourceBuffer.buffered) }`);

consoleWrite('Only report samples added in missing intervals');
run('sourceBuffer.appendBuffer(syncSampleRun(0, 10, 2))');
await waitFor(sourceBuffer, 'update');

run('sourceBuffer.remove(2, 4)');
await waitFor(sourceBuffer, 'update');
run('sourceBuffer.remove(6, 8)');
await waitFor(sourceBuffer, 'update');
run('sourceBuffer.remove(0, 10)');
await waitFor(sourceBuffer, 'update');

endTest();
});
</script>
</head>
<body>
<video controls></video>
</body>
</html>
12 changes: 12 additions & 0 deletions LayoutTests/media/utilities.js
Expand Up @@ -78,3 +78,15 @@ function waitForVideoFrameUntil(video, time, cb) {
p.then(cb);
return p;
}

function timeRangesToString(ranges) {
var str = "";
if (!!!ranges) {
str += "null";
return str;
}
for (var i = 0; i < ranges.length; i++) {
str += "[" + ranges.start(i) + ", " + ranges.end(i) + ")";
}
return str;
}
4 changes: 2 additions & 2 deletions Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml
Expand Up @@ -3911,7 +3911,7 @@ ManagedMediaSourceEnabled:
category: media
humanReadableName: "Managed Media Source API"
humanReadableDescription: "Managed Media Source API"
condition: ENABLE(MANAGED_MEDIA_SOURCE) && ENABLE(MEDIA_SOURCE)
condition: ENABLE(MANAGED_MEDIA_SOURCE)
defaultValue:
WebKitLegacy:
default: false
Expand Down Expand Up @@ -3946,7 +3946,7 @@ ManagedMediaSourceNeedsAirPlay:
category: media
humanReadableName: "Managed Media Source Requires AirPlay source"
humanReadableDescription: "Managed Media Source Requires AirPlay source"
condition: ENABLE(MANAGED_MEDIA_SOURCE) && ENABLE(MEDIA_SOURCE) && ENABLE(WIRELESS_PLAYBACK_TARGET)
condition: ENABLE(MANAGED_MEDIA_SOURCE) && ENABLE(WIRELESS_PLAYBACK_TARGET)
defaultValue:
WebKitLegacy:
default: false
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/CMakeLists.txt
Expand Up @@ -407,6 +407,7 @@ set(WebCore_NON_SVG_IDL_FILES
Modules/mediasession/Navigator+MediaSession.idl

Modules/mediasource/AudioTrack+MediaSource.idl
Modules/mediasource/BufferedChangeEvent.idl
Modules/mediasource/DOMURL+MediaSource.idl
Modules/mediasource/ManagedMediaSource.idl
Modules/mediasource/ManagedSourceBuffer.idl
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/DerivedSources-input.xcfilelist
Expand Up @@ -393,6 +393,7 @@ $(PROJECT_DIR)/Modules/mediasession/MediaSessionPlaylistMixin.idl
$(PROJECT_DIR)/Modules/mediasession/MediaSessionReadyState.idl
$(PROJECT_DIR)/Modules/mediasession/Navigator+MediaSession.idl
$(PROJECT_DIR)/Modules/mediasource/AudioTrack+MediaSource.idl
$(PROJECT_DIR)/Modules/mediasource/BufferedChangeEvent.idl
$(PROJECT_DIR)/Modules/mediasource/DOMURL+MediaSource.idl
$(PROJECT_DIR)/Modules/mediasource/ManagedMediaSource.idl
$(PROJECT_DIR)/Modules/mediasource/ManagedSourceBuffer.idl
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/DerivedSources-output.xcfilelist
Expand Up @@ -341,6 +341,8 @@ $(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSBlobPropertyBag.cpp
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSBlobPropertyBag.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSBroadcastChannel.cpp
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSBroadcastChannel.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSBufferedChangeEvent.cpp
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSBufferedChangeEvent.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSByteLengthQueuingStrategy.cpp
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSByteLengthQueuingStrategy.h
$(BUILT_PRODUCTS_DIR)/DerivedSources/WebCore/JSCDATASection.cpp
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/DerivedSources.make
Expand Up @@ -396,6 +396,7 @@ JS_BINDING_IDLS := \
$(WebCore)/Modules/mediarecorder/MediaRecorder.idl \
$(WebCore)/Modules/mediarecorder/MediaRecorderErrorEvent.idl \
$(WebCore)/Modules/mediasource/AudioTrack+MediaSource.idl \
$(WebCore)/Modules/mediasource/BufferedChangeEvent.idl \
$(WebCore)/Modules/mediasource/DOMURL+MediaSource.idl \
$(WebCore)/Modules/mediasource/ManagedMediaSource.idl \
$(WebCore)/Modules/mediasource/ManagedSourceBuffer.idl \
Expand Down
63 changes: 63 additions & 0 deletions Source/WebCore/Modules/mediasource/BufferedChangeEvent.cpp
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "config.h"

#if ENABLE(MANAGED_MEDIA_SOURCE)
#include "BufferedChangeEvent.h"

#include "Event.h"
#include "EventNames.h"
#include "TimeRanges.h"
#include <wtf/IsoMallocInlines.h>

namespace WebCore {

WTF_MAKE_ISO_ALLOCATED_IMPL(BufferedChangeEvent);

BufferedChangeEvent::BufferedChangeEvent(RefPtr<TimeRanges>&& added, RefPtr<TimeRanges>&& removed)
: Event(eventNames().bufferedchangeEvent, CanBubble::No, IsCancelable::No)
, m_added(WTFMove(added))
, m_removed(WTFMove(removed))
{
}

BufferedChangeEvent::BufferedChangeEvent(const AtomString& type, Init&& init)
: Event(type, init, IsTrusted::No)
, m_added(WTFMove(init.addedRanges))
, m_removed(WTFMove(init.removedRanges))
{
}

BufferedChangeEvent::~BufferedChangeEvent() = default;

EventInterface BufferedChangeEvent::eventInterface() const
{
return BufferedChangeEventInterfaceType;
}

} // namespace WebCore

#endif

0 comments on commit 03f910f

Please sign in to comment.