1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * Copyright (C) 2013-2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "MediaSource.h"
34
35#if ENABLE(MEDIA_SOURCE)
36
37#include "AudioTrackList.h"
38#include "ContentType.h"
39#include "Event.h"
40#include "EventNames.h"
41#include "HTMLMediaElement.h"
42#include "Logging.h"
43#include "MediaSourcePrivate.h"
44#include "MediaSourceRegistry.h"
45#include "SourceBuffer.h"
46#include "SourceBufferList.h"
47#include "SourceBufferPrivate.h"
48#include "TextTrackList.h"
49#include "TimeRanges.h"
50#include "VideoTrackList.h"
51#include <wtf/IsoMallocInlines.h>
52
53namespace WebCore {
54
55WTF_MAKE_ISO_ALLOCATED_IMPL(MediaSource);
56
57String convertEnumerationToString(MediaSourcePrivate::AddStatus enumerationValue)
58{
59 static const NeverDestroyed<String> values[] = {
60 MAKE_STATIC_STRING_IMPL("Ok"),
61 MAKE_STATIC_STRING_IMPL("NotSupported"),
62 MAKE_STATIC_STRING_IMPL("ReachedIdLimit"),
63 };
64 static_assert(static_cast<size_t>(MediaSourcePrivate::AddStatus::Ok) == 0, "MediaSourcePrivate::AddStatus::Ok is not 0 as expected");
65 static_assert(static_cast<size_t>(MediaSourcePrivate::AddStatus::NotSupported) == 1, "MediaSourcePrivate::AddStatus::NotSupported is not 1 as expected");
66 static_assert(static_cast<size_t>(MediaSourcePrivate::AddStatus::ReachedIdLimit) == 2, "MediaSourcePrivate::AddStatus::ReachedIdLimit is not 2 as expected");
67 ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));
68 return values[static_cast<size_t>(enumerationValue)];
69}
70
71String convertEnumerationToString(MediaSourcePrivate::EndOfStreamStatus enumerationValue)
72{
73 static const NeverDestroyed<String> values[] = {
74 MAKE_STATIC_STRING_IMPL("EosNoError"),
75 MAKE_STATIC_STRING_IMPL("EosNetworkError"),
76 MAKE_STATIC_STRING_IMPL("EosDecodeError"),
77 };
78 static_assert(static_cast<size_t>(MediaSourcePrivate::EndOfStreamStatus::EosNoError) == 0, "MediaSourcePrivate::EndOfStreamStatus::EosNoError is not 0 as expected");
79 static_assert(static_cast<size_t>(MediaSourcePrivate::EndOfStreamStatus::EosNetworkError) == 1, "MediaSourcePrivate::EndOfStreamStatus::EosNetworkError is not 1 as expected");
80 static_assert(static_cast<size_t>(MediaSourcePrivate::EndOfStreamStatus::EosDecodeError) == 2, "MediaSourcePrivate::EndOfStreamStatus::EosDecodeError is not 2 as expected");
81 ASSERT(static_cast<size_t>(enumerationValue) < WTF_ARRAY_LENGTH(values));
82 return values[static_cast<size_t>(enumerationValue)];
83}
84
85URLRegistry* MediaSource::s_registry;
86
87void MediaSource::setRegistry(URLRegistry* registry)
88{
89 ASSERT(!s_registry);
90 s_registry = registry;
91}
92
93Ref<MediaSource> MediaSource::create(ScriptExecutionContext& context)
94{
95 auto mediaSource = adoptRef(*new MediaSource(context));
96 mediaSource->suspendIfNeeded();
97 return mediaSource;
98}
99
100MediaSource::MediaSource(ScriptExecutionContext& context)
101 : ActiveDOMObject(&context)
102 , m_duration(MediaTime::invalidTime())
103 , m_pendingSeekTime(MediaTime::invalidTime())
104 , m_asyncEventQueue(*this)
105#if !RELEASE_LOG_DISABLED
106 , m_logger(downcast<Document>(context).logger())
107#endif
108{
109 m_sourceBuffers = SourceBufferList::create(scriptExecutionContext());
110 m_activeSourceBuffers = SourceBufferList::create(scriptExecutionContext());
111}
112
113MediaSource::~MediaSource()
114{
115 ALWAYS_LOG(LOGIDENTIFIER);
116 ASSERT(isClosed());
117}
118
119void MediaSource::setPrivateAndOpen(Ref<MediaSourcePrivate>&& mediaSourcePrivate)
120{
121 DEBUG_LOG(LOGIDENTIFIER);
122 ASSERT(!m_private);
123 ASSERT(m_mediaElement);
124 m_private = WTFMove(mediaSourcePrivate);
125
126 // 2.4.1 Attaching to a media element
127 // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#mediasource-attach
128
129 // ↳ If readyState is NOT set to "closed"
130 // Run the "If the media data cannot be fetched at all, due to network errors, causing the user agent to give up trying
131 // to fetch the resource" steps of the resource fetch algorithm's media data processing steps list.
132 if (!isClosed()) {
133 m_mediaElement->mediaLoadingFailedFatally(MediaPlayer::NetworkError);
134 return;
135 }
136
137 // ↳ Otherwise
138 // 1. Set the media element's delaying-the-load-event-flag to false.
139 m_mediaElement->setShouldDelayLoadEvent(false);
140
141 // 2. Set the readyState attribute to "open".
142 // 3. Queue a task to fire a simple event named sourceopen at the MediaSource.
143 setReadyState(ReadyState::Open);
144
145 // 4. Continue the resource fetch algorithm by running the remaining "Otherwise (mode is local)" steps,
146 // with these clarifications:
147 // NOTE: This is handled in HTMLMediaElement.
148}
149
150void MediaSource::addedToRegistry()
151{
152 DEBUG_LOG(LOGIDENTIFIER);
153 setPendingActivity(*this);
154}
155
156void MediaSource::removedFromRegistry()
157{
158 DEBUG_LOG(LOGIDENTIFIER);
159 unsetPendingActivity(*this);
160}
161
162MediaTime MediaSource::duration() const
163{
164 return m_duration;
165}
166
167void MediaSource::durationChanged(const MediaTime& duration)
168{
169 ALWAYS_LOG(LOGIDENTIFIER, duration);
170 m_duration = duration;
171}
172
173MediaTime MediaSource::currentTime() const
174{
175 return m_mediaElement ? m_mediaElement->currentMediaTime() : MediaTime::zeroTime();
176}
177
178std::unique_ptr<PlatformTimeRanges> MediaSource::buffered() const
179{
180 if (m_buffered && m_activeSourceBuffers->length() && std::all_of(m_activeSourceBuffers->begin(), m_activeSourceBuffers->end(), [](auto& buffer) { return !buffer->isBufferedDirty(); }))
181 return std::make_unique<PlatformTimeRanges>(*m_buffered);
182
183 m_buffered = std::make_unique<PlatformTimeRanges>();
184 for (auto& sourceBuffer : *m_activeSourceBuffers)
185 sourceBuffer->setBufferedDirty(false);
186
187 // Implements MediaSource algorithm for HTMLMediaElement.buffered.
188 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#htmlmediaelement-extensions
189 Vector<PlatformTimeRanges> activeRanges = this->activeRanges();
190
191 // 1. If activeSourceBuffers.length equals 0 then return an empty TimeRanges object and abort these steps.
192 if (activeRanges.isEmpty())
193 return std::make_unique<PlatformTimeRanges>(*m_buffered);
194
195 // 2. Let active ranges be the ranges returned by buffered for each SourceBuffer object in activeSourceBuffers.
196 // 3. Let highest end time be the largest range end time in the active ranges.
197 MediaTime highestEndTime = MediaTime::zeroTime();
198 for (auto& ranges : activeRanges) {
199 unsigned length = ranges.length();
200 if (length)
201 highestEndTime = std::max(highestEndTime, ranges.end(length - 1));
202 }
203
204 // Return an empty range if all ranges are empty.
205 if (!highestEndTime)
206 return std::make_unique<PlatformTimeRanges>(*m_buffered);
207
208 // 4. Let intersection ranges equal a TimeRange object containing a single range from 0 to highest end time.
209 m_buffered->add(MediaTime::zeroTime(), highestEndTime);
210
211 // 5. For each SourceBuffer object in activeSourceBuffers run the following steps:
212 bool ended = readyState() == ReadyState::Ended;
213 for (auto& sourceRanges : activeRanges) {
214 // 5.1 Let source ranges equal the ranges returned by the buffered attribute on the current SourceBuffer.
215 // 5.2 If readyState is "ended", then set the end time on the last range in source ranges to highest end time.
216 if (ended && sourceRanges.length())
217 sourceRanges.add(sourceRanges.start(sourceRanges.length() - 1), highestEndTime);
218
219 // 5.3 Let new intersection ranges equal the intersection between the intersection ranges and the source ranges.
220 // 5.4 Replace the ranges in intersection ranges with the new intersection ranges.
221 m_buffered->intersectWith(sourceRanges);
222 }
223
224 return std::make_unique<PlatformTimeRanges>(*m_buffered);
225}
226
227void MediaSource::seekToTime(const MediaTime& time)
228{
229 if (isClosed())
230 return;
231
232 ALWAYS_LOG(LOGIDENTIFIER, time);
233
234 // 2.4.3 Seeking
235 // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#mediasource-seeking
236
237 m_pendingSeekTime = time;
238
239 // Run the following steps as part of the "Wait until the user agent has established whether or not the
240 // media data for the new playback position is available, and, if it is, until it has decoded enough data
241 // to play back that position" step of the seek algorithm:
242 // ↳ If new playback position is not in any TimeRange of HTMLMediaElement.buffered
243 if (!hasBufferedTime(time)) {
244 // 1. If the HTMLMediaElement.readyState attribute is greater than HAVE_METADATA,
245 // then set the HTMLMediaElement.readyState attribute to HAVE_METADATA.
246 m_private->setReadyState(MediaPlayer::HaveMetadata);
247
248 // 2. The media element waits until an appendBuffer() or an appendStream() call causes the coded
249 // frame processing algorithm to set the HTMLMediaElement.readyState attribute to a value greater
250 // than HAVE_METADATA.
251 m_private->waitForSeekCompleted();
252 return;
253 }
254 // ↳ Otherwise
255 // Continue
256
257// https://bugs.webkit.org/show_bug.cgi?id=125157 broke seek on MediaPlayerPrivateGStreamerMSE
258#if !USE(GSTREAMER)
259 m_private->waitForSeekCompleted();
260#endif
261 completeSeek();
262}
263
264void MediaSource::completeSeek()
265{
266 if (isClosed())
267 return;
268
269 // 2.4.3 Seeking, ctd.
270 // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#mediasource-seeking
271
272 ASSERT(m_pendingSeekTime.isValid());
273
274 ALWAYS_LOG(LOGIDENTIFIER, m_pendingSeekTime);
275
276 // 2. The media element resets all decoders and initializes each one with data from the appropriate
277 // initialization segment.
278 // 3. The media element feeds coded frames from the active track buffers into the decoders starting
279 // with the closest random access point before the new playback position.
280 MediaTime pendingSeekTime = m_pendingSeekTime;
281 m_pendingSeekTime = MediaTime::invalidTime();
282 for (auto& sourceBuffer : *m_activeSourceBuffers)
283 sourceBuffer->seekToTime(pendingSeekTime);
284
285 // 4. Resume the seek algorithm at the "Await a stable state" step.
286 m_private->seekCompleted();
287
288 monitorSourceBuffers();
289}
290
291Ref<TimeRanges> MediaSource::seekable()
292{
293 // 6. HTMLMediaElement Extensions, seekable
294 // W3C Editor's Draft 16 September 2016
295 // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#htmlmediaelement-extensions
296
297 // ↳ If duration equals NaN:
298 // Return an empty TimeRanges object.
299 if (m_duration.isInvalid())
300 return TimeRanges::create();
301
302 // ↳ If duration equals positive Infinity:
303 if (m_duration.isPositiveInfinite()) {
304 auto buffered = this->buffered();
305 // If live seekable range is not empty:
306 if (m_liveSeekable && m_liveSeekable->length()) {
307 // Let union ranges be the union of live seekable range and the HTMLMediaElement.buffered attribute.
308 buffered->unionWith(*m_liveSeekable);
309 // Return a single range with a start time equal to the earliest start time in union ranges
310 // and an end time equal to the highest end time in union ranges and abort these steps.
311 buffered->add(buffered->start(0), buffered->maximumBufferedTime());
312 return TimeRanges::create(*buffered);
313 }
314
315 // If the HTMLMediaElement.buffered attribute returns an empty TimeRanges object, then return
316 // an empty TimeRanges object and abort these steps.
317 if (!buffered->length())
318 return TimeRanges::create();
319
320 // Return a single range with a start time of 0 and an end time equal to the highest end time
321 // reported by the HTMLMediaElement.buffered attribute.
322 return TimeRanges::create({MediaTime::zeroTime(), buffered->maximumBufferedTime()});
323 }
324
325 // ↳ Otherwise:
326 // Return a single range with a start time of 0 and an end time equal to duration.
327 return TimeRanges::create({MediaTime::zeroTime(), m_duration});
328}
329
330ExceptionOr<void> MediaSource::setLiveSeekableRange(double start, double end)
331{
332 // W3C Editor's Draft 16 September 2016
333 // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-mediasource-setliveseekablerange
334
335 ALWAYS_LOG(LOGIDENTIFIER, "start = ", start, ", end = ", end);
336
337 // If the readyState attribute is not "open" then throw an InvalidStateError exception and abort these steps.
338 if (!isOpen())
339 return Exception { InvalidStateError };
340
341 // If start is negative or greater than end, then throw a TypeError exception and abort these steps.
342 if (start < 0 || start > end)
343 return Exception { TypeError };
344
345 // Set live seekable range to be a new normalized TimeRanges object containing a single range
346 // whose start position is start and end position is end.
347 m_liveSeekable = std::make_unique<PlatformTimeRanges>(MediaTime::createWithDouble(start), MediaTime::createWithDouble(end));
348
349 return { };
350}
351
352ExceptionOr<void> MediaSource::clearLiveSeekableRange()
353{
354 // W3C Editor's Draft 16 September 2016
355 // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#dom-mediasource-clearliveseekablerange
356
357 ALWAYS_LOG(LOGIDENTIFIER);
358
359 // If the readyState attribute is not "open" then throw an InvalidStateError exception and abort these steps.
360 if (!isOpen())
361 return Exception { InvalidStateError };
362 m_liveSeekable = nullptr;
363 return { };
364}
365
366const MediaTime& MediaSource::currentTimeFudgeFactor()
367{
368 // Allow hasCurrentTime() to be off by as much as the length of two 24fps video frames
369 static NeverDestroyed<MediaTime> fudgeFactor(2002, 24000);
370 return fudgeFactor;
371}
372
373bool MediaSource::contentTypeShouldGenerateTimestamps(const ContentType& contentType)
374{
375 return contentType.containerType() == "audio/aac" || contentType.containerType() == "audio/mpeg";
376}
377
378bool MediaSource::hasBufferedTime(const MediaTime& time)
379{
380 if (time > duration())
381 return false;
382
383 auto ranges = buffered();
384 if (!ranges->length())
385 return false;
386
387 return abs(ranges->nearest(time) - time) <= currentTimeFudgeFactor();
388}
389
390bool MediaSource::hasCurrentTime()
391{
392 return hasBufferedTime(currentTime());
393}
394
395bool MediaSource::hasFutureTime()
396{
397 MediaTime currentTime = this->currentTime();
398 MediaTime duration = this->duration();
399
400 if (currentTime >= duration)
401 return true;
402
403 auto ranges = buffered();
404 MediaTime nearest = ranges->nearest(currentTime);
405 if (abs(nearest - currentTime) > currentTimeFudgeFactor())
406 return false;
407
408 size_t found = ranges->find(nearest);
409 if (found == notFound)
410 return false;
411
412 MediaTime localEnd = ranges->end(found);
413 if (localEnd == duration)
414 return true;
415
416 return localEnd - currentTime > currentTimeFudgeFactor();
417}
418
419void MediaSource::monitorSourceBuffers()
420{
421 if (isClosed())
422 return;
423
424 // 2.4.4 SourceBuffer Monitoring
425 // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#buffer-monitoring
426
427 // Note, the behavior if activeSourceBuffers is empty is undefined.
428 if (!m_activeSourceBuffers) {
429 m_private->setReadyState(MediaPlayer::HaveNothing);
430 return;
431 }
432
433 // ↳ If the HTMLMediaElement.readyState attribute equals HAVE_NOTHING:
434 if (mediaElement()->readyState() == HTMLMediaElement::HAVE_NOTHING) {
435 // 1. Abort these steps.
436 return;
437 }
438
439 // ↳ If HTMLMediaElement.buffered does not contain a TimeRange for the current playback position:
440 if (!hasCurrentTime()) {
441 // 1. Set the HTMLMediaElement.readyState attribute to HAVE_METADATA.
442 // 2. If this is the first transition to HAVE_METADATA, then queue a task to fire a simple event
443 // named loadedmetadata at the media element.
444 m_private->setReadyState(MediaPlayer::HaveMetadata);
445
446 // 3. Abort these steps.
447 return;
448 }
449
450 // ↳ If HTMLMediaElement.buffered contains a TimeRange that includes the current
451 // playback position and enough data to ensure uninterrupted playback:
452 auto ranges = buffered();
453 if (std::all_of(m_activeSourceBuffers->begin(), m_activeSourceBuffers->end(), [&](auto& sourceBuffer) {
454 return sourceBuffer->canPlayThroughRange(*ranges);
455 })) {
456 // 1. Set the HTMLMediaElement.readyState attribute to HAVE_ENOUGH_DATA.
457 // 2. Queue a task to fire a simple event named canplaythrough at the media element.
458 // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA.
459 m_private->setReadyState(MediaPlayer::HaveEnoughData);
460
461 if (m_pendingSeekTime.isValid())
462 completeSeek();
463
464 // 4. Abort these steps.
465 return;
466 }
467
468 // ↳ If HTMLMediaElement.buffered contains a TimeRange that includes the current playback
469 // position and some time beyond the current playback position, then run the following steps:
470 if (hasFutureTime()) {
471 // 1. Set the HTMLMediaElement.readyState attribute to HAVE_FUTURE_DATA.
472 // 2. If the previous value of HTMLMediaElement.readyState was less than HAVE_FUTURE_DATA, then queue a task to fire a simple event named canplay at the media element.
473 // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA.
474 m_private->setReadyState(MediaPlayer::HaveFutureData);
475
476 if (m_pendingSeekTime.isValid())
477 completeSeek();
478
479 // 4. Abort these steps.
480 return;
481 }
482
483 // ↳ If HTMLMediaElement.buffered contains a TimeRange that ends at the current playback position and does not have a range covering the time immediately after the current position:
484 // NOTE: Logically, !(all objects do not contain currentTime) == (some objects contain current time)
485
486 // 1. Set the HTMLMediaElement.readyState attribute to HAVE_CURRENT_DATA.
487 // 2. If this is the first transition to HAVE_CURRENT_DATA, then queue a task to fire a simple
488 // event named loadeddata at the media element.
489 // 3. Playback is suspended at this point since the media element doesn't have enough data to
490 // advance the media timeline.
491 m_private->setReadyState(MediaPlayer::HaveCurrentData);
492
493 if (m_pendingSeekTime.isValid())
494 completeSeek();
495
496 // 4. Abort these steps.
497}
498
499ExceptionOr<void> MediaSource::setDuration(double duration)
500{
501 // 2.1 Attributes - Duration
502 // https://www.w3.org/TR/2016/REC-media-source-20161117/#attributes
503
504 ALWAYS_LOG(LOGIDENTIFIER, duration);
505
506 // On setting, run the following steps:
507 // 1. If the value being set is negative or NaN then throw a TypeError exception and abort these steps.
508 if (duration < 0.0 || std::isnan(duration))
509 return Exception { TypeError };
510
511 // 2. If the readyState attribute is not "open" then throw an InvalidStateError exception and abort these steps.
512 if (!isOpen())
513 return Exception { InvalidStateError };
514
515 // 3. If the updating attribute equals true on any SourceBuffer in sourceBuffers, then throw an InvalidStateError
516 // exception and abort these steps.
517 for (auto& sourceBuffer : *m_sourceBuffers) {
518 if (sourceBuffer->updating())
519 return Exception { InvalidStateError };
520 }
521
522 // 4. Run the duration change algorithm with new duration set to the value being assigned to this attribute.
523 return setDurationInternal(MediaTime::createWithDouble(duration));
524}
525
526ExceptionOr<void> MediaSource::setDurationInternal(const MediaTime& duration)
527{
528 // 2.4.6 Duration Change
529 // https://www.w3.org/TR/2016/REC-media-source-20161117/#duration-change-algorithm
530
531 MediaTime newDuration = duration;
532
533 // 1. If the current value of duration is equal to new duration, then return.
534 if (newDuration == m_duration)
535 return { };
536
537 // 2. If new duration is less than the highest presentation timestamp of any buffered coded frames
538 // for all SourceBuffer objects in sourceBuffers, then throw an InvalidStateError exception and
539 // abort these steps.
540 // 3. Let highest end time be the largest track buffer ranges end time across all the track buffers
541 // across all SourceBuffer objects in sourceBuffers.
542 MediaTime highestPresentationTimestamp;
543 MediaTime highestEndTime;
544 for (auto& sourceBuffer : *m_sourceBuffers) {
545 highestPresentationTimestamp = std::max(highestPresentationTimestamp, sourceBuffer->highestPresentationTimestamp());
546 highestEndTime = std::max(highestEndTime, sourceBuffer->bufferedInternal().ranges().maximumBufferedTime());
547 }
548 if (highestPresentationTimestamp.isValid() && newDuration < highestPresentationTimestamp)
549 return Exception { InvalidStateError };
550
551 // 4. If new duration is less than highest end time, then
552 // 4.1. Update new duration to equal highest end time.
553 if (highestEndTime.isValid() && newDuration < highestEndTime)
554 newDuration = highestEndTime;
555
556 // 5. Update duration to new duration.
557 m_duration = newDuration;
558 ALWAYS_LOG(LOGIDENTIFIER, duration);
559
560 // 6. Update the media duration to new duration and run the HTMLMediaElement duration change algorithm.
561 m_private->durationChanged();
562
563 return { };
564}
565
566void MediaSource::setReadyState(ReadyState state)
567{
568 auto oldState = readyState();
569 if (oldState == state)
570 return;
571
572 m_readyState = state;
573
574 onReadyStateChange(oldState, state);
575}
576
577ExceptionOr<void> MediaSource::endOfStream(Optional<EndOfStreamError> error)
578{
579 ALWAYS_LOG(LOGIDENTIFIER);
580
581 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-MediaSource-endOfStream-void-EndOfStreamError-error
582 // 1. If the readyState attribute is not in the "open" state then throw an
583 // InvalidStateError exception and abort these steps.
584 if (!isOpen())
585 return Exception { InvalidStateError };
586
587 // 2. If the updating attribute equals true on any SourceBuffer in sourceBuffers, then throw an
588 // InvalidStateError exception and abort these steps.
589 if (std::any_of(m_sourceBuffers->begin(), m_sourceBuffers->end(), [](auto& sourceBuffer) { return sourceBuffer->updating(); }))
590 return Exception { InvalidStateError };
591
592 // 3. Run the end of stream algorithm with the error parameter set to error.
593 streamEndedWithError(error);
594
595 return { };
596}
597
598void MediaSource::streamEndedWithError(Optional<EndOfStreamError> error)
599{
600#if !RELEASE_LOG_DISABLED
601 if (error)
602 ALWAYS_LOG(LOGIDENTIFIER, error.value());
603 else
604 ALWAYS_LOG(LOGIDENTIFIER);
605#endif
606
607 if (isClosed())
608 return;
609
610 // 2.4.7 https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#end-of-stream-algorithm
611
612 // 1. Change the readyState attribute value to "ended".
613 // 2. Queue a task to fire a simple event named sourceended at the MediaSource.
614 setReadyState(ReadyState::Ended);
615
616 // 3.
617 if (!error) {
618 // ↳ If error is not set, is null, or is an empty string
619 // 1. Run the duration change algorithm with new duration set to the highest end time reported by
620 // the buffered attribute across all SourceBuffer objects in sourceBuffers.
621 MediaTime maxEndTime;
622 for (auto& sourceBuffer : *m_sourceBuffers) {
623 if (auto length = sourceBuffer->bufferedInternal().length())
624 maxEndTime = std::max(sourceBuffer->bufferedInternal().ranges().end(length - 1), maxEndTime);
625 }
626 setDurationInternal(maxEndTime);
627
628 // 2. Notify the media element that it now has all of the media data.
629 for (auto& sourceBuffer : *m_sourceBuffers)
630 sourceBuffer->trySignalAllSamplesEnqueued();
631 m_private->markEndOfStream(MediaSourcePrivate::EosNoError);
632 } else if (error == EndOfStreamError::Network) {
633 // ↳ If error is set to "network"
634 ASSERT(m_mediaElement);
635 if (m_mediaElement->readyState() == HTMLMediaElement::HAVE_NOTHING) {
636 // ↳ If the HTMLMediaElement.readyState attribute equals HAVE_NOTHING
637 // Run the "If the media data cannot be fetched at all, due to network errors, causing
638 // the user agent to give up trying to fetch the resource" steps of the resource fetch algorithm.
639 // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailed().
640 m_mediaElement->mediaLoadingFailed(MediaPlayer::NetworkError);
641 } else {
642 // ↳ If the HTMLMediaElement.readyState attribute is greater than HAVE_NOTHING
643 // Run the "If the connection is interrupted after some media data has been received, causing the
644 // user agent to give up trying to fetch the resource" steps of the resource fetch algorithm.
645 // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailedFatally().
646 m_mediaElement->mediaLoadingFailedFatally(MediaPlayer::NetworkError);
647 }
648 } else {
649 // ↳ If error is set to "decode"
650 ASSERT(error == EndOfStreamError::Decode);
651 ASSERT(m_mediaElement);
652 if (m_mediaElement->readyState() == HTMLMediaElement::HAVE_NOTHING) {
653 // ↳ If the HTMLMediaElement.readyState attribute equals HAVE_NOTHING
654 // Run the "If the media data can be fetched but is found by inspection to be in an unsupported
655 // format, or can otherwise not be rendered at all" steps of the resource fetch algorithm.
656 // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailed().
657 m_mediaElement->mediaLoadingFailed(MediaPlayer::FormatError);
658 } else {
659 // ↳ If the HTMLMediaElement.readyState attribute is greater than HAVE_NOTHING
660 // Run the media data is corrupted steps of the resource fetch algorithm.
661 // NOTE: This step is handled by HTMLMediaElement::mediaLoadingFailedFatally().
662 m_mediaElement->mediaLoadingFailedFatally(MediaPlayer::DecodeError);
663 }
664 }
665}
666
667ExceptionOr<Ref<SourceBuffer>> MediaSource::addSourceBuffer(const String& type)
668{
669 DEBUG_LOG(LOGIDENTIFIER, type);
670
671 // 2.2 http://www.w3.org/TR/media-source/#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type
672 // When this method is invoked, the user agent must run the following steps:
673
674 // 1. If type is an empty string then throw a TypeError exception and abort these steps.
675 if (type.isEmpty())
676 return Exception { TypeError };
677
678 // 2. If type contains a MIME type that is not supported ..., then throw a
679 // NotSupportedError exception and abort these steps.
680 if (!isTypeSupported(type))
681 return Exception { NotSupportedError };
682
683 // 4. If the readyState attribute is not in the "open" state then throw an
684 // InvalidStateError exception and abort these steps.
685 if (!isOpen())
686 return Exception { InvalidStateError };
687
688 // 5. Create a new SourceBuffer object and associated resources.
689 ContentType contentType(type);
690 auto sourceBufferPrivate = createSourceBufferPrivate(contentType);
691
692 if (sourceBufferPrivate.hasException()) {
693 // 2. If type contains a MIME type that is not supported ..., then throw a NotSupportedError exception and abort these steps.
694 // 3. If the user agent can't handle any more SourceBuffer objects then throw a QuotaExceededError exception and abort these steps
695 return sourceBufferPrivate.releaseException();
696 }
697
698 auto buffer = SourceBuffer::create(sourceBufferPrivate.releaseReturnValue(), this);
699 DEBUG_LOG(LOGIDENTIFIER, "created SourceBuffer");
700
701 // 6. Set the generate timestamps flag on the new object to the value in the "Generate Timestamps Flag"
702 // column of the byte stream format registry [MSE-REGISTRY] entry that is associated with type.
703 // NOTE: In the current byte stream format registry <http://www.w3.org/2013/12/byte-stream-format-registry/>
704 // only the "MPEG Audio Byte Stream Format" has the "Generate Timestamps Flag" value set.
705 bool shouldGenerateTimestamps = contentTypeShouldGenerateTimestamps(contentType);
706 buffer->setShouldGenerateTimestamps(shouldGenerateTimestamps);
707
708 // 7. If the generate timestamps flag equals true:
709 // ↳ Set the mode attribute on the new object to "sequence".
710 // Otherwise:
711 // ↳ Set the mode attribute on the new object to "segments".
712 buffer->setMode(shouldGenerateTimestamps ? SourceBuffer::AppendMode::Sequence : SourceBuffer::AppendMode::Segments);
713
714 // 8. Add the new object to sourceBuffers and fire a addsourcebuffer on that object.
715 m_sourceBuffers->add(buffer.copyRef());
716 regenerateActiveSourceBuffers();
717
718 // 9. Return the new object to the caller.
719 return buffer;
720}
721
722ExceptionOr<void> MediaSource::removeSourceBuffer(SourceBuffer& buffer)
723{
724 DEBUG_LOG(LOGIDENTIFIER);
725
726 Ref<SourceBuffer> protect(buffer);
727
728 // 2. If sourceBuffer specifies an object that is not in sourceBuffers then
729 // throw a NotFoundError exception and abort these steps.
730 if (!m_sourceBuffers->length() || !m_sourceBuffers->contains(buffer))
731 return Exception { NotFoundError };
732
733 // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ...
734 buffer.abortIfUpdating();
735
736 ASSERT(scriptExecutionContext());
737 if (!scriptExecutionContext()->activeDOMObjectsAreStopped()) {
738 // 4. Let SourceBuffer audioTracks list equal the AudioTrackList object returned by sourceBuffer.audioTracks.
739 auto& audioTracks = buffer.audioTracks();
740
741 // 5. If the SourceBuffer audioTracks list is not empty, then run the following steps:
742 if (audioTracks.length()) {
743 // 5.1 Let HTMLMediaElement audioTracks list equal the AudioTrackList object returned by the audioTracks
744 // attribute on the HTMLMediaElement.
745 // 5.2 Let the removed enabled audio track flag equal false.
746 bool removedEnabledAudioTrack = false;
747
748 // 5.3 For each AudioTrack object in the SourceBuffer audioTracks list, run the following steps:
749 while (audioTracks.length()) {
750 auto& track = *audioTracks.lastItem();
751
752 // 5.3.1 Set the sourceBuffer attribute on the AudioTrack object to null.
753 track.setSourceBuffer(nullptr);
754
755 // 5.3.2 If the enabled attribute on the AudioTrack object is true, then set the removed enabled
756 // audio track flag to true.
757 if (track.enabled())
758 removedEnabledAudioTrack = true;
759
760 // 5.3.3 Remove the AudioTrack object from the HTMLMediaElement audioTracks list.
761 // 5.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
762 // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement audioTracks list.
763 if (mediaElement())
764 mediaElement()->removeAudioTrack(track);
765
766 // 5.3.5 Remove the AudioTrack object from the SourceBuffer audioTracks list.
767 // 5.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
768 // cancelable, and that uses the TrackEvent interface, at the SourceBuffer audioTracks list.
769 audioTracks.remove(track);
770 }
771
772 // 5.4 If the removed enabled audio track flag equals true, then queue a task to fire a simple event
773 // named change at the HTMLMediaElement audioTracks list.
774 if (removedEnabledAudioTrack)
775 mediaElement()->ensureAudioTracks().scheduleChangeEvent();
776 }
777
778 // 6. Let SourceBuffer videoTracks list equal the VideoTrackList object returned by sourceBuffer.videoTracks.
779 auto& videoTracks = buffer.videoTracks();
780
781 // 7. If the SourceBuffer videoTracks list is not empty, then run the following steps:
782 if (videoTracks.length()) {
783 // 7.1 Let HTMLMediaElement videoTracks list equal the VideoTrackList object returned by the videoTracks
784 // attribute on the HTMLMediaElement.
785 // 7.2 Let the removed selected video track flag equal false.
786 bool removedSelectedVideoTrack = false;
787
788 // 7.3 For each VideoTrack object in the SourceBuffer videoTracks list, run the following steps:
789 while (videoTracks.length()) {
790 auto& track = *videoTracks.lastItem();
791
792 // 7.3.1 Set the sourceBuffer attribute on the VideoTrack object to null.
793 track.setSourceBuffer(nullptr);
794
795 // 7.3.2 If the selected attribute on the VideoTrack object is true, then set the removed selected
796 // video track flag to true.
797 if (track.selected())
798 removedSelectedVideoTrack = true;
799
800 // 7.3.3 Remove the VideoTrack object from the HTMLMediaElement videoTracks list.
801 // 7.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
802 // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement videoTracks list.
803 if (mediaElement())
804 mediaElement()->removeVideoTrack(track);
805
806 // 7.3.5 Remove the VideoTrack object from the SourceBuffer videoTracks list.
807 // 7.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
808 // cancelable, and that uses the TrackEvent interface, at the SourceBuffer videoTracks list.
809 videoTracks.remove(track);
810 }
811
812 // 7.4 If the removed selected video track flag equals true, then queue a task to fire a simple event
813 // named change at the HTMLMediaElement videoTracks list.
814 if (removedSelectedVideoTrack)
815 mediaElement()->ensureVideoTracks().scheduleChangeEvent();
816 }
817
818 // 8. Let SourceBuffer textTracks list equal the TextTrackList object returned by sourceBuffer.textTracks.
819 auto& textTracks = buffer.textTracks();
820
821 // 9. If the SourceBuffer textTracks list is not empty, then run the following steps:
822 if (textTracks.length()) {
823 // 9.1 Let HTMLMediaElement textTracks list equal the TextTrackList object returned by the textTracks
824 // attribute on the HTMLMediaElement.
825 // 9.2 Let the removed enabled text track flag equal false.
826 bool removedEnabledTextTrack = false;
827
828 // 9.3 For each TextTrack object in the SourceBuffer textTracks list, run the following steps:
829 while (textTracks.length()) {
830 auto& track = *textTracks.lastItem();
831
832 // 9.3.1 Set the sourceBuffer attribute on the TextTrack object to null.
833 track.setSourceBuffer(nullptr);
834
835 // 9.3.2 If the mode attribute on the TextTrack object is set to "showing" or "hidden", then
836 // set the removed enabled text track flag to true.
837 if (track.mode() == TextTrack::Mode::Showing || track.mode() == TextTrack::Mode::Hidden)
838 removedEnabledTextTrack = true;
839
840 // 9.3.3 Remove the TextTrack object from the HTMLMediaElement textTracks list.
841 // 9.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
842 // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement textTracks list.
843 if (mediaElement())
844 mediaElement()->removeTextTrack(track);
845
846 // 9.3.5 Remove the TextTrack object from the SourceBuffer textTracks list.
847 // 9.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not
848 // cancelable, and that uses the TrackEvent interface, at the SourceBuffer textTracks list.
849 textTracks.remove(track);
850 }
851
852 // 9.4 If the removed enabled text track flag equals true, then queue a task to fire a simple event
853 // named change at the HTMLMediaElement textTracks list.
854 if (removedEnabledTextTrack)
855 mediaElement()->ensureTextTracks().scheduleChangeEvent();
856 }
857 }
858
859 // 10. If sourceBuffer is in activeSourceBuffers, then remove sourceBuffer from activeSourceBuffers ...
860 m_activeSourceBuffers->remove(buffer);
861
862 // 11. Remove sourceBuffer from sourceBuffers and fire a removesourcebuffer event
863 // on that object.
864 m_sourceBuffers->remove(buffer);
865
866 // 12. Destroy all resources for sourceBuffer.
867 buffer.removedFromMediaSource();
868
869 return { };
870}
871
872bool MediaSource::isTypeSupported(const String& type)
873{
874 // Section 2.2 isTypeSupported() method steps.
875 // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#widl-MediaSource-isTypeSupported-boolean-DOMString-type
876 // 1. If type is an empty string, then return false.
877 if (type.isNull() || type.isEmpty())
878 return false;
879
880 ContentType contentType(type);
881 String codecs = contentType.parameter("codecs");
882
883 // 2. If type does not contain a valid MIME type string, then return false.
884 if (contentType.containerType().isEmpty())
885 return false;
886
887 // 3. If type contains a media type or media subtype that the MediaSource does not support, then return false.
888 // 4. If type contains at a codec that the MediaSource does not support, then return false.
889 // 5. If the MediaSource does not support the specified combination of media type, media subtype, and codecs then return false.
890 // 6. Return true.
891 MediaEngineSupportParameters parameters;
892 parameters.type = contentType;
893 parameters.isMediaSource = true;
894 MediaPlayer::SupportsType supported = MediaPlayer::supportsType(parameters);
895
896 if (codecs.isEmpty())
897 return supported != MediaPlayer::IsNotSupported;
898
899 return supported == MediaPlayer::IsSupported;
900}
901
902bool MediaSource::isOpen() const
903{
904 return readyState() == ReadyState::Open;
905}
906
907bool MediaSource::isClosed() const
908{
909 return readyState() == ReadyState::Closed;
910}
911
912bool MediaSource::isEnded() const
913{
914 return readyState() == ReadyState::Ended;
915}
916
917void MediaSource::detachFromElement(HTMLMediaElement& element)
918{
919 ALWAYS_LOG(LOGIDENTIFIER);
920
921 ASSERT_UNUSED(element, m_mediaElement == &element);
922
923 // 2.4.2 Detaching from a media element
924 // https://rawgit.com/w3c/media-source/45627646344eea0170dd1cbc5a3d508ca751abb8/media-source-respec.html#mediasource-detach
925
926 // 1. Set the readyState attribute to "closed".
927 // 7. Queue a task to fire a simple event named sourceclose at the MediaSource.
928 setReadyState(ReadyState::Closed);
929
930 // 2. Update duration to NaN.
931 m_duration = MediaTime::invalidTime();
932
933 // 3. Remove all the SourceBuffer objects from activeSourceBuffers.
934 // 4. Queue a task to fire a simple event named removesourcebuffer at activeSourceBuffers.
935 while (m_activeSourceBuffers->length())
936 removeSourceBuffer(*m_activeSourceBuffers->item(0));
937
938 // 5. Remove all the SourceBuffer objects from sourceBuffers.
939 // 6. Queue a task to fire a simple event named removesourcebuffer at sourceBuffers.
940 while (m_sourceBuffers->length())
941 removeSourceBuffer(*m_sourceBuffers->item(0));
942
943 m_private = nullptr;
944 m_mediaElement = nullptr;
945}
946
947void MediaSource::sourceBufferDidChangeActiveState(SourceBuffer&, bool)
948{
949 regenerateActiveSourceBuffers();
950}
951
952bool MediaSource::attachToElement(HTMLMediaElement& element)
953{
954 if (m_mediaElement)
955 return false;
956
957 ASSERT(isClosed());
958
959 m_mediaElement = &element;
960 return true;
961}
962
963void MediaSource::openIfInEndedState()
964{
965 if (m_readyState != ReadyState::Ended)
966 return;
967
968 ALWAYS_LOG(LOGIDENTIFIER);
969
970 setReadyState(ReadyState::Open);
971 m_private->unmarkEndOfStream();
972}
973
974bool MediaSource::hasPendingActivity() const
975{
976 return m_private || m_asyncEventQueue.hasPendingEvents()
977 || ActiveDOMObject::hasPendingActivity();
978}
979
980void MediaSource::suspend(ReasonForSuspension reason)
981{
982 ALWAYS_LOG(LOGIDENTIFIER, static_cast<int>(reason));
983
984 switch (reason) {
985 case ReasonForSuspension::PageCache:
986 case ReasonForSuspension::PageWillBeSuspended:
987 m_asyncEventQueue.suspend();
988 break;
989 case ReasonForSuspension::JavaScriptDebuggerPaused:
990 case ReasonForSuspension::WillDeferLoading:
991 // Do nothing, we don't pause media playback in these cases.
992 break;
993 }
994}
995
996void MediaSource::resume()
997{
998 ALWAYS_LOG(LOGIDENTIFIER);
999
1000 m_asyncEventQueue.resume();
1001}
1002
1003void MediaSource::stop()
1004{
1005 ALWAYS_LOG(LOGIDENTIFIER);
1006
1007 m_asyncEventQueue.close();
1008 if (m_mediaElement)
1009 m_mediaElement->detachMediaSource();
1010 m_readyState = ReadyState::Closed;
1011 m_private = nullptr;
1012}
1013
1014bool MediaSource::canSuspendForDocumentSuspension() const
1015{
1016 return isClosed() && !m_asyncEventQueue.hasPendingEvents();
1017}
1018
1019const char* MediaSource::activeDOMObjectName() const
1020{
1021 return "MediaSource";
1022}
1023
1024void MediaSource::onReadyStateChange(ReadyState oldState, ReadyState newState)
1025{
1026 ALWAYS_LOG(LOGIDENTIFIER, "old state = ", oldState, ", new state = ", newState);
1027
1028 for (auto& buffer : *m_sourceBuffers)
1029 buffer->readyStateChanged();
1030
1031 if (isOpen()) {
1032 scheduleEvent(eventNames().sourceopenEvent);
1033 return;
1034 }
1035
1036 if (oldState == ReadyState::Open && newState == ReadyState::Ended) {
1037 scheduleEvent(eventNames().sourceendedEvent);
1038 return;
1039 }
1040
1041 ASSERT(isClosed());
1042 scheduleEvent(eventNames().sourcecloseEvent);
1043}
1044
1045Vector<PlatformTimeRanges> MediaSource::activeRanges() const
1046{
1047 Vector<PlatformTimeRanges> activeRanges;
1048 for (auto& sourceBuffer : *m_activeSourceBuffers)
1049 activeRanges.append(sourceBuffer->bufferedInternal().ranges());
1050 return activeRanges;
1051}
1052
1053ExceptionOr<Ref<SourceBufferPrivate>> MediaSource::createSourceBufferPrivate(const ContentType& type)
1054{
1055 RefPtr<SourceBufferPrivate> sourceBufferPrivate;
1056 switch (m_private->addSourceBuffer(type, sourceBufferPrivate)) {
1057 case MediaSourcePrivate::Ok:
1058 return sourceBufferPrivate.releaseNonNull();
1059 case MediaSourcePrivate::NotSupported:
1060 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type
1061 // Step 2: If type contains a MIME type ... that is not supported with the types
1062 // specified for the other SourceBuffer objects in sourceBuffers, then throw
1063 // a NotSupportedError exception and abort these steps.
1064 return Exception { NotSupportedError };
1065 case MediaSourcePrivate::ReachedIdLimit:
1066 // 2.2 https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-MediaSource-addSourceBuffer-SourceBuffer-DOMString-type
1067 // Step 3: If the user agent can't handle any more SourceBuffer objects then throw
1068 // a QuotaExceededError exception and abort these steps.
1069 return Exception { QuotaExceededError };
1070 }
1071
1072 ASSERT_NOT_REACHED();
1073 return Exception { QuotaExceededError };
1074}
1075
1076void MediaSource::scheduleEvent(const AtomString& eventName)
1077{
1078 DEBUG_LOG(LOGIDENTIFIER, "scheduling '", eventName, "'");
1079
1080 auto event = Event::create(eventName, Event::CanBubble::No, Event::IsCancelable::No);
1081 event->setTarget(this);
1082
1083 m_asyncEventQueue.enqueueEvent(WTFMove(event));
1084}
1085
1086ScriptExecutionContext* MediaSource::scriptExecutionContext() const
1087{
1088 return ActiveDOMObject::scriptExecutionContext();
1089}
1090
1091EventTargetInterface MediaSource::eventTargetInterface() const
1092{
1093 return MediaSourceEventTargetInterfaceType;
1094}
1095
1096URLRegistry& MediaSource::registry() const
1097{
1098 return MediaSourceRegistry::registry();
1099}
1100
1101void MediaSource::regenerateActiveSourceBuffers()
1102{
1103 Vector<RefPtr<SourceBuffer>> newList;
1104 for (auto& sourceBuffer : *m_sourceBuffers) {
1105 if (sourceBuffer->active())
1106 newList.append(sourceBuffer);
1107 }
1108 m_activeSourceBuffers->swap(newList);
1109 for (auto& sourceBuffer : *m_activeSourceBuffers)
1110 sourceBuffer->setBufferedDirty(true);
1111}
1112
1113#if !RELEASE_LOG_DISABLED
1114void MediaSource::setLogIdentifier(const void* identifier)
1115{
1116 m_logIdentifier = identifier;
1117 ALWAYS_LOG(LOGIDENTIFIER);
1118}
1119
1120WTFLogChannel& MediaSource::logChannel() const
1121{
1122 return LogMediaSource;
1123}
1124#endif
1125
1126}
1127
1128#endif
1129