Skip to content

Commit

Permalink
Add state setters to MediaSourceAttachment (#3801)
Browse files Browse the repository at this point in the history
In preparation for cross-thread MediaSource objects, a small amount of
state and associated setters are being added to the
MediaSourceAttachment interface. This will allow for a push model,
instead of a pull model, for state such as recent media timestamps, and
the error status of the HTMLMediaElement.

Usage of these setters is gated through the
`"MediaElement.EnableUsingMediaSourceAttachmentMethods"` H5VCC flag.
This flag needs to be coordinated between a `HTMLMediaElement`, the
attached `MediaSource`, and all `SourceBuffers` used. As such, the value
of the flag when the `HTMLMediaElement` was created is used to
coordinate the state of the rest of the related objects. This prevents a
flag mismatch, such as if the H5VCC value is updated after the
`HTMLMediaElement` has been created but before the `MediaSource` is
created.

This is based off of the following Chromium changes:

*  https://chromium-review.googlesource.com/c/chromium/src/+/2391934
*  https://chromium-review.googlesource.com/c/chromium/src/+/2401808

b/338425449
  • Loading branch information
at-ninja committed Sep 11, 2024
1 parent 3950b01 commit e462633
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 40 deletions.
46 changes: 40 additions & 6 deletions cobalt/dom/html_media_element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,11 @@ void HTMLMediaElement::set_current_time(
LOG(ERROR) << "invalid state error";
web::DOMException::Raise(web::DOMException::kInvalidStateErr,
exception_state);
return;
} else {
Seek(time);
}
Seek(time);

ReportCurrentTimeToMediaSource();
}

double HTMLMediaElement::duration() const {
Expand Down Expand Up @@ -816,7 +818,7 @@ void HTMLMediaElement::PrepareForLoad() {
set_playback_rate(default_playback_rate());

// 6 - Set the error attribute to null and the autoplaying flag to true.
error_ = NULL;
SetError(NULL);
autoplaying_ = true;

// 7 - Invoke the media element's resource selection algorithm.
Expand Down Expand Up @@ -1050,8 +1052,8 @@ void HTMLMediaElement::NoneSupported(const std::string& message) {

// 6.1 - Set the error attribute to a new MediaError object whose code
// attribute is set to MEDIA_ERR_SRC_NOT_SUPPORTED.
error_ = new MediaError(MediaError::kMediaErrSrcNotSupported,
message.empty() ? "Source not supported." : message);
SetError(new MediaError(MediaError::kMediaErrSrcNotSupported,
message.empty() ? "Source not supported." : message));
// 6.2 - Forget the media element's media-resource-specific text tracks.

// 6.3 - Set the element's networkState attribute to the kNetworkNoSource
Expand Down Expand Up @@ -1129,6 +1131,10 @@ void HTMLMediaElement::OnPlaybackProgressTimer() {
}

ScheduleTimeupdateEvent(true);

// The playback progress timer is used here to provide a steady clock that
// allows the attached MediaSource to have access to a recent media time.
ReportCurrentTimeToMediaSource();
}

void HTMLMediaElement::StartPlaybackProgressTimer() {
Expand Down Expand Up @@ -1511,6 +1517,8 @@ void HTMLMediaElement::UpdatePlayState() {
AddPlayedRange(last_seek_time_, time);
}
}

ReportCurrentTimeToMediaSource();
}

bool HTMLMediaElement::PotentiallyPlaying() const {
Expand Down Expand Up @@ -1575,6 +1583,32 @@ void HTMLMediaElement::ConfigureMediaControls() {
DLOG_IF(WARNING, controls_) << "media control is not supported";
}

void HTMLMediaElement::ReportCurrentTimeToMediaSource() {
if (!is_using_media_source_attachment_methods_) {
return;
}

if (!media_source_attachment_) {
return;
}

media_source_attachment_->OnElementTimeUpdate(current_time(NULL));
}

void HTMLMediaElement::SetError(scoped_refptr<MediaError> error) {
error_ = error;

if (!is_using_media_source_attachment_methods_) {
return;
}

if (!error || !media_source_attachment_) {
return;
}

media_source_attachment_->OnElementError();
}

void HTMLMediaElement::MediaEngineError(scoped_refptr<MediaError> error) {
if (error->message().empty()) {
LOG(WARNING) << "HTMLMediaElement::MediaEngineError " << error->code();
Expand All @@ -1589,7 +1623,7 @@ void HTMLMediaElement::MediaEngineError(scoped_refptr<MediaError> error) {

// 2 - Set the error attribute to a new MediaError object whose code attribute
// is set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
error_ = error;
SetError(error);

// 3 - Queue a task to fire a simple event named error at the media element.
ScheduleOwnEvent(base::Tokens::error());
Expand Down
7 changes: 7 additions & 0 deletions cobalt/dom/html_media_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,13 @@ class HTMLMediaElement : public HTMLElement,

void ConfigureMediaControls();

// Pushes the current media time to the attached MediaSourceAttachment to
// avoid having to asynchronously pull media time from a cross-thread
// MediaSource object.
void ReportCurrentTimeToMediaSource();

// Error report
void SetError(scoped_refptr<MediaError> error);
void MediaEngineError(scoped_refptr<MediaError> error);

// WebMediaPlayerClient methods
Expand Down Expand Up @@ -313,6 +319,7 @@ class HTMLMediaElement : public HTMLElement,
// Time has not changed since sending an "ended" event.
bool sent_end_event_;

// See SetError().
scoped_refptr<MediaError> error_;

// Helper object to reduce the image capacity while a video is playing.
Expand Down
23 changes: 8 additions & 15 deletions cobalt/dom/media_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,6 @@ bool IsMediaElementUsingMediaSourceBufferedRangeEnabled(
.value_or(false);
}

// If this function returns true, MediaSource will proxy calls to the
// attached HTMLMediaElement object through the MediaSourceAttachment interface
// instead of directly calling against the HTMLMediaElement object.
// The default value is false.
bool IsMediaElementUsingMediaSourceAttachmentMethodsEnabled(
web::EnvironmentSettings* settings) {
return GetMediaSettings(settings)
.IsMediaElementUsingMediaSourceAttachmentMethodsEnabled()
.value_or(false);
}

} // namespace

MediaSource::MediaSource(script::EnvironmentSettings* settings)
Expand All @@ -170,9 +159,6 @@ MediaSource::MediaSource(script::EnvironmentSettings* settings)
chunk_demuxer_(NULL),
ready_state_(kMediaSourceReadyStateClosed),
ALLOW_THIS_IN_INITIALIZER_LIST(event_queue_(this)),
is_using_media_source_attachment_methods_(
IsMediaElementUsingMediaSourceAttachmentMethodsEnabled(
environment_settings())),
source_buffers_(new SourceBufferList(settings, &event_queue_)),
active_source_buffers_(new SourceBufferList(settings, &event_queue_)),
live_seekable_range_(new TimeRanges) {
Expand Down Expand Up @@ -294,7 +280,8 @@ scoped_refptr<SourceBuffer> MediaSource::AddSourceBuffer(
switch (status) {
case ChunkDemuxer::kOk:
source_buffer =
new SourceBuffer(settings, guid, this, chunk_demuxer_, &event_queue_);
new SourceBuffer(settings, guid, this, chunk_demuxer_, &event_queue_,
is_using_media_source_attachment_methods_);
break;
case ChunkDemuxer::kNotSupported:
web::DOMException::Raise(web::DOMException::kNotSupportedErr,
Expand Down Expand Up @@ -439,6 +426,8 @@ bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings,

bool MediaSource::StartAttachingToMediaElement(
HTMLMediaElement* media_element) {
is_using_media_source_attachment_methods_ = false;

if (attached_element_) {
return false;
}
Expand Down Expand Up @@ -472,6 +461,8 @@ bool MediaSource::StartAttachingToMediaElement(

bool MediaSource::StartAttachingToMediaElement(
MediaSourceAttachmentSupplement* media_source_attachment) {
is_using_media_source_attachment_methods_ = true;

if (media_source_attachment_) {
return false;
}
Expand Down Expand Up @@ -685,10 +676,12 @@ void MediaSource::SetSourceBufferActive(SourceBuffer* source_buffer,
}

HTMLMediaElement* MediaSource::GetMediaElement() const {
DCHECK(!is_using_media_source_attachment_methods_);
return attached_element_;
}

MediaSourceAttachmentSupplement* MediaSource::GetMediaSourceAttachment() const {
DCHECK(is_using_media_source_attachment_methods_);
return media_source_attachment_;
}

Expand Down
2 changes: 1 addition & 1 deletion cobalt/dom/media_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class MediaSource : public web::EventTarget {
MediaSourceReadyState ready_state_;
EventQueue event_queue_;
// TODO(b/338425449): Remove direct references to HTMLMediaElement.
const bool is_using_media_source_attachment_methods_;
bool is_using_media_source_attachment_methods_ = false;
base::WeakPtr<HTMLMediaElement> attached_element_;
base::WeakPtr<MediaSourceAttachmentSupplement> media_source_attachment_;

Expand Down
16 changes: 16 additions & 0 deletions cobalt/dom/media_source_attachment.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ class MediaSourceAttachment
// flag MediaElement.EnableUsingMediaSourceAttachmentMethods is disabled.
virtual scoped_refptr<MediaSource> media_source() const = 0;

// Provide state updates to the MediaSource that are necessary for its
// operation. These are pushed rather than pulled to reduce complexity and
// latency, especially when the MediaSource is in a Worker context.
// OnElementTimeUpdate() gives the MediaSource a notion of the recent media
// element currentTime so that it can more effectively prevent evicting
// buffered media near to playback and/or seek target time in its heuristic.
// Alternatives such as pumping this via the media pipeline are insufficient,
// as the media pipeline may not be aware of overrides to the playback start
// position.
virtual void OnElementTimeUpdate(double time) = 0;

// Needed as a precondition in the Prepare Append algorithm, OnElementError()
// lets the MediaSource know if the attached media element has transitioned to
// having an error.
virtual void OnElementError() = 0;

private:
friend class base::RefCountedThreadSafe<MediaSourceAttachment>;

Expand Down
28 changes: 25 additions & 3 deletions cobalt/dom/same_thread_media_source_attachment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ namespace dom {
SameThreadMediaSourceAttachment::SameThreadMediaSourceAttachment(
scoped_refptr<MediaSource> media_source)
: media_source_(media_source),
task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}
task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
recent_element_time_(0.0),
element_has_error_(false) {}

void SameThreadMediaSourceAttachment::TraceMembers(script::Tracer* tracer) {
DCHECK_EQ(task_runner_, base::SequencedTaskRunner::GetCurrentDefault());
Expand Down Expand Up @@ -108,14 +110,23 @@ double SameThreadMediaSourceAttachment::GetRecentMediaTime() {
DCHECK_EQ(task_runner_, base::SequencedTaskRunner::GetCurrentDefault());
DCHECK(attached_element_);

return attached_element_->current_time(NULL);
double result = attached_element_->current_time(NULL);

DVLOG(2) << __func__ << ": recent time=" << recent_element_time_
<< ", actual current time=" << result;

return result;
}

bool SameThreadMediaSourceAttachment::GetElementError() {
DCHECK_EQ(task_runner_, base::SequencedTaskRunner::GetCurrentDefault());
DCHECK(attached_element_);

return static_cast<bool>(attached_element_->error());
bool result = static_cast<bool>(attached_element_->error());

DCHECK_EQ(result, element_has_error_);

return result;
}

scoped_refptr<AudioTrackList>
Expand All @@ -136,5 +147,16 @@ SameThreadMediaSourceAttachment::CreateVideoTrackList(
return base::MakeRefCounted<VideoTrackList>(settings, attached_element_);
}

void SameThreadMediaSourceAttachment::OnElementTimeUpdate(double time) {
recent_element_time_ = time;
}

void SameThreadMediaSourceAttachment::OnElementError() {
DCHECK(!element_has_error_)
<< "At most one transition to element error per attachment is expected";

element_has_error_ = true;
}

} // namespace dom
} // namespace cobalt
6 changes: 6 additions & 0 deletions cobalt/dom/same_thread_media_source_attachment.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class SameThreadMediaSourceAttachment : public MediaSourceAttachmentSupplement {
scoped_refptr<TimeRanges> GetBufferedRange() const override;
MediaSourceReadyState GetReadyState() const override;

void OnElementTimeUpdate(double time) override;
void OnElementError() override;

// MediaSourceAttachmentSupplement
void NotifyDurationChanged(double duration) override;
bool HasMaxVideoCapabilities() const override;
Expand Down Expand Up @@ -84,6 +87,9 @@ class SameThreadMediaSourceAttachment : public MediaSourceAttachmentSupplement {
// Used to ensure all calls are made on the thread that created this object.
base::SequencedTaskRunner* task_runner_;

double recent_element_time_; // See OnElementTimeUpdate().
bool element_has_error_; // See OnElementError().

DISALLOW_COPY_AND_ASSIGN(SameThreadMediaSourceAttachment);
};

Expand Down
17 changes: 3 additions & 14 deletions cobalt/dom/source_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,6 @@ bool IsAvoidCopyingArrayBufferEnabled(web::EnvironmentSettings* settings) {
return media_settings.IsAvoidCopyingArrayBufferEnabled().value_or(false);
}

// If this function returns true, MediaSource will proxy calls to the
// attached HTMLMediaElement object through the MediaSourceAttachment interface
// instead of directly calling against the HTMLMediaElement object.
// The default value is false.
bool IsMediaElementUsingMediaSourceAttachmentMethodsEnabled(
web::EnvironmentSettings* settings) {
return GetMediaSettings(settings)
.IsMediaElementUsingMediaSourceAttachmentMethodsEnabled()
.value_or(false);
}

} // namespace

SourceBuffer::OnInitSegmentReceivedHelper::OnInitSegmentReceivedHelper(
Expand Down Expand Up @@ -170,7 +159,8 @@ void SourceBuffer::OnInitSegmentReceivedHelper::TryToRunOnInitSegmentReceived(

SourceBuffer::SourceBuffer(script::EnvironmentSettings* settings,
const std::string& id, MediaSource* media_source,
ChunkDemuxer* chunk_demuxer, EventQueue* event_queue)
ChunkDemuxer* chunk_demuxer, EventQueue* event_queue,
const bool is_using_media_source_attachment_methods)
: web::EventTarget(settings),
on_init_segment_received_helper_(new OnInitSegmentReceivedHelper(this)),
id_(id),
Expand All @@ -181,8 +171,7 @@ SourceBuffer::SourceBuffer(script::EnvironmentSettings* settings,
chunk_demuxer_(chunk_demuxer),
event_queue_(event_queue),
is_using_media_source_attachment_methods_(
IsMediaElementUsingMediaSourceAttachmentMethodsEnabled(
environment_settings())),
is_using_media_source_attachment_methods),
audio_tracks_(
is_using_media_source_attachment_methods_
? media_source->GetMediaSourceAttachment()->CreateAudioTrackList(
Expand Down
4 changes: 3 additions & 1 deletion cobalt/dom/source_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ class SourceBuffer : public web::EventTarget {

// Custom, not in any spec.
//
// TODO(b/338425449): Remove is_using_media_source_attachment_methods.
SourceBuffer(script::EnvironmentSettings* settings, const std::string& id,
MediaSource* media_source, ChunkDemuxer* chunk_demuxer,
EventQueue* event_queue);
EventQueue* event_queue,
const bool is_using_media_source_attachment_methods);

// Web API: SourceBuffer
//
Expand Down

0 comments on commit e462633

Please sign in to comment.