| /* |
| * Copyright (C) 2013 Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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. |
| */ |
| |
| // Modifications Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/dom/media_source.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <limits> |
| #include <vector> |
| |
| #include "base/compiler_specific.h" |
| #include "base/guid.h" |
| #include "base/logging.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/base/tokens.h" |
| #include "cobalt/dom/dom_settings.h" |
| #include "cobalt/dom/media_settings.h" |
| #include "cobalt/web/context.h" |
| #include "cobalt/web/dom_exception.h" |
| #include "cobalt/web/event.h" |
| #include "starboard/media.h" |
| #include "third_party/chromium/media/base/pipeline_status.h" |
| |
| namespace cobalt { |
| namespace dom { |
| |
| namespace { |
| |
| using ::media::CHUNK_DEMUXER_ERROR_EOS_STATUS_DECODE_ERROR; |
| using ::media::CHUNK_DEMUXER_ERROR_EOS_STATUS_NETWORK_ERROR; |
| using ::media::PIPELINE_OK; |
| using ::media::PipelineStatus; |
| |
| const MediaSettings& GetMediaSettings(web::EnvironmentSettings* settings) { |
| DCHECK(settings); |
| DCHECK(settings->context()); |
| DCHECK(settings->context()->web_settings()); |
| |
| const auto& web_settings = settings->context()->web_settings(); |
| return web_settings->media_settings(); |
| } |
| |
| // If the system has more processors than the specified value, SourceBuffer |
| // append and remove algorithm will be offloaded to a non-web thread to reduce |
| // the load on the web thread. |
| // The default value is 1024, which effectively disable offloading by default. |
| // Setting to a reasonably low value (say 0 or 2) will enable algorithm |
| // offloading. |
| bool IsAlgorithmOffloadEnabled(web::EnvironmentSettings* settings) { |
| int min_process_count_to_offload = |
| GetMediaSettings(settings) |
| .GetMinimumProcessorCountToOffloadAlgorithm() |
| .value_or(1024); |
| DCHECK_GE(min_process_count_to_offload, 0); |
| return SbSystemGetNumberOfProcessors() >= min_process_count_to_offload; |
| } |
| |
| // If this function returns true, SourceBuffer will reduce asynchronous |
| // behaviors. For example, queued events will be dispatached immediately when |
| // possible. |
| // The default value is false. |
| bool IsAsynchronousReductionEnabled(web::EnvironmentSettings* settings) { |
| return GetMediaSettings(settings).IsAsynchronousReductionEnabled().value_or( |
| false); |
| } |
| |
| // If this function returns true, MediaSource::EndOfStreamAlgorithm() will call |
| // SetReadyState(kMediaSourceReadyStateEnded) even if MediaSource object is |
| // closed. |
| // The default value is false. |
| bool IsCallingEndedWhenClosedEnabled(web::EnvironmentSettings* settings) { |
| return GetMediaSettings(settings).IsCallingEndedWhenClosedEnabled().value_or( |
| false); |
| } |
| |
| // If the size of a job that is part of an algorithm is less than or equal to |
| // the return value of this function, the implementation will run the job |
| // immediately instead of scheduling it to run later to reduce latency. |
| // NOTE: This is currently only enabled for buffer append. |
| // The default value is 0 KB, which disables immediate job completely. |
| int GetMaxSizeForImmediateJob(web::EnvironmentSettings* settings) { |
| const int kDefaultMaxSize = 0; |
| auto max_size = |
| GetMediaSettings(settings).GetMaxSizeForImmediateJob().value_or( |
| kDefaultMaxSize); |
| DCHECK_GE(max_size, 0); |
| return max_size; |
| } |
| |
| } // namespace |
| |
| MediaSource::MediaSource(script::EnvironmentSettings* settings) |
| : web::EventTarget(settings), |
| algorithm_offload_enabled_( |
| IsAlgorithmOffloadEnabled(environment_settings())), |
| asynchronous_reduction_enabled_( |
| IsAsynchronousReductionEnabled(environment_settings())), |
| max_size_for_immediate_job_( |
| GetMaxSizeForImmediateJob(environment_settings())), |
| default_algorithm_runner_(asynchronous_reduction_enabled_), |
| chunk_demuxer_(NULL), |
| ready_state_(kMediaSourceReadyStateClosed), |
| ALLOW_THIS_IN_INITIALIZER_LIST(event_queue_(this)), |
| source_buffers_(new SourceBufferList(settings, &event_queue_)), |
| active_source_buffers_(new SourceBufferList(settings, &event_queue_)), |
| live_seekable_range_(new TimeRanges) { |
| LOG(INFO) << "Algorithm offloading is " |
| << (algorithm_offload_enabled_ ? "enabled" : "disabled"); |
| LOG(INFO) << "Asynchronous reduction is " |
| << (asynchronous_reduction_enabled_ ? "enabled" : "disabled"); |
| LOG(INFO) << "Max size of immediate job is set to " |
| << max_size_for_immediate_job_; |
| } |
| |
| MediaSource::~MediaSource() { SetReadyState(kMediaSourceReadyStateClosed); } |
| |
| scoped_refptr<SourceBufferList> MediaSource::source_buffers() const { |
| return source_buffers_; |
| } |
| |
| scoped_refptr<SourceBufferList> MediaSource::active_source_buffers() const { |
| return active_source_buffers_; |
| } |
| |
| MediaSourceReadyState MediaSource::ready_state() const { return ready_state_; } |
| |
| double MediaSource::duration(script::ExceptionState* exception_state) const { |
| if (ready_state_ == kMediaSourceReadyStateClosed) { |
| return std::numeric_limits<float>::quiet_NaN(); |
| } |
| |
| DCHECK(chunk_demuxer_); |
| return chunk_demuxer_->GetDuration(); |
| } |
| |
| void MediaSource::set_duration(double duration, |
| script::ExceptionState* exception_state) { |
| if (duration < 0.0 || std::isnan(duration)) { |
| web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state); |
| return; |
| } |
| if (!IsOpen() || IsUpdating()) { |
| web::DOMException::Raise(web::DOMException::kInvalidStateErr, |
| exception_state); |
| return; |
| } |
| |
| // Run the duration change algorithm |
| if (duration == this->duration(NULL)) { |
| return; |
| } |
| |
| double highest_buffered_presentation_timestamp = 0; |
| for (uint32 i = 0; i < source_buffers_->length(); ++i) { |
| highest_buffered_presentation_timestamp = |
| std::max(highest_buffered_presentation_timestamp, |
| source_buffers_->Item(i)->GetHighestPresentationTimestamp()); |
| } |
| |
| if (duration < highest_buffered_presentation_timestamp) { |
| web::DOMException::Raise(web::DOMException::kInvalidStateErr, |
| exception_state); |
| return; |
| } |
| |
| // 3. Set old duration to the current value of duration. |
| double old_duration = this->duration(NULL); |
| DCHECK_LE(highest_buffered_presentation_timestamp, |
| std::isnan(old_duration) ? 0 : old_duration); |
| |
| // 4. Update duration to new duration. |
| bool request_seek = attached_element_->current_time(NULL) > duration; |
| chunk_demuxer_->SetDuration(duration); |
| |
| // 5. If a user agent is unable to partially render audio frames or text cues |
| // that start before and end after the duration, then run the following |
| // steps: |
| // NOTE: Currently we assume that the media engine is able to render |
| // partial frames/cues. If a media engine gets added that doesn't support |
| // this, then we'll need to add logic to handle the substeps. |
| |
| // 6. Update the media controller duration to new duration and run the |
| // HTMLMediaElement duration change algorithm. |
| attached_element_->DurationChanged(duration, request_seek); |
| } |
| |
| scoped_refptr<SourceBuffer> MediaSource::AddSourceBuffer( |
| script::EnvironmentSettings* settings, const std::string& type, |
| script::ExceptionState* exception_state) { |
| TRACE_EVENT1("cobalt::dom", "MediaSource::AddSourceBuffer()", "type", type); |
| LOG(INFO) << "add SourceBuffer with type " << type; |
| |
| if (type.empty()) { |
| web::DOMException::Raise(web::DOMException::kInvalidAccessErr, |
| exception_state); |
| // Return value should be ignored. |
| return NULL; |
| } |
| |
| if (!IsTypeSupported(settings, type)) { |
| web::DOMException::Raise(web::DOMException::kNotSupportedErr, |
| exception_state); |
| return NULL; |
| } |
| |
| if (!IsOpen()) { |
| web::DOMException::Raise(web::DOMException::kInvalidStateErr, |
| exception_state); |
| return NULL; |
| } |
| |
| std::string guid = base::GenerateGUID(); |
| scoped_refptr<SourceBuffer> source_buffer; |
| ChunkDemuxer::Status status = chunk_demuxer_->AddId(guid, type); |
| switch (status) { |
| case ChunkDemuxer::kOk: |
| source_buffer = |
| new SourceBuffer(settings, guid, this, chunk_demuxer_, &event_queue_); |
| break; |
| case ChunkDemuxer::kNotSupported: |
| web::DOMException::Raise(web::DOMException::kNotSupportedErr, |
| exception_state); |
| return NULL; |
| case ChunkDemuxer::kReachedIdLimit: |
| web::DOMException::Raise(web::DOMException::kQuotaExceededErr, |
| exception_state); |
| return NULL; |
| } |
| |
| DCHECK(source_buffer); |
| source_buffers_->Add(source_buffer); |
| return source_buffer; |
| } |
| |
| void MediaSource::RemoveSourceBuffer( |
| const scoped_refptr<SourceBuffer>& source_buffer, |
| script::ExceptionState* exception_state) { |
| TRACE_EVENT0("cobalt::dom", "MediaSource::RemoveSourceBuffer()"); |
| if (source_buffer.get() == NULL) { |
| web::DOMException::Raise(web::DOMException::kInvalidAccessErr, |
| exception_state); |
| return; |
| } |
| |
| if (source_buffers_->length() == 0 || |
| !source_buffers_->Contains(source_buffer)) { |
| web::DOMException::Raise(web::DOMException::kNotFoundErr, exception_state); |
| return; |
| } |
| |
| source_buffer->OnRemovedFromMediaSource(); |
| |
| active_source_buffers_->Remove(source_buffer); |
| source_buffers_->Remove(source_buffer); |
| } |
| |
| void MediaSource::EndOfStreamAlgorithm(MediaSourceEndOfStreamError error) { |
| if (IsClosed()) { |
| if (IsCallingEndedWhenClosedEnabled(environment_settings())) { |
| LOG(INFO) << "Setting state to ended when MediaSource object is closed"; |
| // Calling the function below here leads to ANR in production, as |
| // EndOfStreamAlgorithm() can be called by SetReadyState(). |
| // Calling SetReadyState() nestedly leads to re-entrance of Abort() on |
| // the SourceBuffer algorithm handle, where a mutex gets re-acquired. |
| // Keep this code path here so we have the option to revert it to the |
| // original behavior in production. |
| SetReadyState(kMediaSourceReadyStateEnded); |
| } else { |
| LOG(INFO) |
| << "Skip setting state to ended when MediaSource object is closed"; |
| } |
| } else { |
| SetReadyState(kMediaSourceReadyStateEnded); |
| } |
| |
| PipelineStatus pipeline_status = PIPELINE_OK; |
| |
| if (error == kMediaSourceEndOfStreamErrorNetwork) { |
| pipeline_status = CHUNK_DEMUXER_ERROR_EOS_STATUS_NETWORK_ERROR; |
| } else if (error == kMediaSourceEndOfStreamErrorDecode) { |
| pipeline_status = CHUNK_DEMUXER_ERROR_EOS_STATUS_DECODE_ERROR; |
| } |
| chunk_demuxer_->MarkEndOfStream(pipeline_status); |
| } |
| |
| void MediaSource::EndOfStream(script::ExceptionState* exception_state) { |
| TRACE_EVENT0("cobalt::dom", "MediaSource::EndOfStream()"); |
| // If there is no error string provided, treat it as empty. |
| EndOfStream(kMediaSourceEndOfStreamErrorNoError, exception_state); |
| } |
| |
| void MediaSource::EndOfStream(MediaSourceEndOfStreamError error, |
| script::ExceptionState* exception_state) { |
| TRACE_EVENT1("cobalt::dom", "MediaSource::EndOfStream()", "error", error); |
| if (!IsOpen() || IsUpdating()) { |
| web::DOMException::Raise(web::DOMException::kInvalidStateErr, |
| exception_state); |
| return; |
| } |
| EndOfStreamAlgorithm(error); |
| } |
| |
| void MediaSource::SetLiveSeekableRange( |
| double start, double end, script::ExceptionState* exception_state) { |
| TRACE_EVENT2("cobalt::dom", "MediaSource::SetLiveSeekableRange()", "start", |
| start, "end", end); |
| if (!IsOpen()) { |
| web::DOMException::Raise(web::DOMException::kInvalidStateErr, |
| exception_state); |
| return; |
| } |
| |
| if (start < 0 || start > end) { |
| web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state); |
| return; |
| } |
| |
| live_seekable_range_ = new TimeRanges(start, end); |
| } |
| |
| void MediaSource::ClearLiveSeekableRange( |
| script::ExceptionState* exception_state) { |
| TRACE_EVENT0("cobalt::dom", "MediaSource::ClearLiveSeekableRange()"); |
| if (!IsOpen()) { |
| web::DOMException::Raise(web::DOMException::kInvalidStateErr, |
| exception_state); |
| return; |
| } |
| |
| if (live_seekable_range_->length() != 0) { |
| live_seekable_range_ = new TimeRanges; |
| } |
| } |
| |
| // static |
| bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings, |
| const std::string& type) { |
| TRACE_EVENT1("cobalt::dom", "MediaSource::IsTypeSupported()", "type", type); |
| DCHECK(settings); |
| DOMSettings* dom_settings = |
| base::polymorphic_downcast<DOMSettings*>(settings); |
| DCHECK(dom_settings->can_play_type_handler()); |
| SbMediaSupportType support_type = |
| dom_settings->can_play_type_handler()->CanPlayAdaptive(type.c_str(), ""); |
| switch (support_type) { |
| case kSbMediaSupportTypeNotSupported: |
| return false; |
| case kSbMediaSupportTypeMaybe: |
| return true; |
| case kSbMediaSupportTypeProbably: |
| return true; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| bool MediaSource::AttachToElement(HTMLMediaElement* media_element) { |
| if (attached_element_) { |
| return false; |
| } |
| |
| DCHECK(IsClosed()); |
| DCHECK(!algorithm_process_thread_); |
| |
| attached_element_ = base::AsWeakPtr(media_element); |
| has_max_video_capabilities_ = media_element->HasMaxVideoCapabilities(); |
| |
| if (algorithm_offload_enabled_) { |
| algorithm_process_thread_.reset(new base::Thread("MSEAlgorithm")); |
| if (!algorithm_process_thread_->Start()) { |
| LOG(WARNING) << "Starting algorithm process thread failed, disable" |
| " algorithm offloading"; |
| algorithm_process_thread_.reset(); |
| } |
| } |
| |
| if (algorithm_process_thread_) { |
| LOG(INFO) << "Algorithm offloading enabled."; |
| offload_algorithm_runner_.reset( |
| new OffloadAlgorithmRunner<SourceBufferAlgorithm>( |
| algorithm_process_thread_->message_loop()->task_runner(), |
| base::ThreadTaskRunnerHandle::Get())); |
| } else { |
| LOG(INFO) << "Algorithm offloading disabled."; |
| } |
| return true; |
| } |
| |
| void MediaSource::SetChunkDemuxerAndOpen(ChunkDemuxer* chunk_demuxer) { |
| DCHECK(chunk_demuxer); |
| DCHECK(!chunk_demuxer_); |
| DCHECK(attached_element_); |
| chunk_demuxer_ = chunk_demuxer; |
| SetReadyState(kMediaSourceReadyStateOpen); |
| } |
| |
| void MediaSource::Close() { SetReadyState(kMediaSourceReadyStateClosed); } |
| |
| bool MediaSource::IsClosed() const { |
| return ready_state_ == kMediaSourceReadyStateClosed; |
| } |
| |
| scoped_refptr<TimeRanges> MediaSource::GetBufferedRange() const { |
| std::vector<scoped_refptr<TimeRanges> > ranges( |
| active_source_buffers_->length()); |
| for (uint32 i = 0; i < active_source_buffers_->length(); ++i) |
| ranges[i] = active_source_buffers_->Item(i)->buffered(NULL); |
| |
| if (ranges.empty()) { |
| return new TimeRanges; |
| } |
| |
| double highest_end_time = -1; |
| for (size_t i = 0; i < ranges.size(); ++i) { |
| uint32 length = ranges[i]->length(); |
| if (length > 0) { |
| highest_end_time = |
| std::max(highest_end_time, ranges[i]->End(length - 1, NULL)); |
| } |
| } |
| |
| // Return an empty range if all ranges are empty. |
| if (highest_end_time < 0) { |
| return new TimeRanges; |
| } |
| |
| scoped_refptr<TimeRanges> intersection_ranges = |
| new TimeRanges(0, highest_end_time); |
| |
| bool ended = ready_state() == kMediaSourceReadyStateEnded; |
| for (size_t i = 0; i < ranges.size(); ++i) { |
| scoped_refptr<TimeRanges> source_ranges = ranges[i].get(); |
| |
| if (ended && source_ranges->length()) { |
| source_ranges->Add( |
| source_ranges->Start(source_ranges->length() - 1, NULL), |
| highest_end_time); |
| } |
| |
| intersection_ranges = intersection_ranges->IntersectWith(source_ranges); |
| } |
| |
| return intersection_ranges; |
| } |
| |
| scoped_refptr<TimeRanges> MediaSource::GetSeekable() const { |
| // Implements MediaSource algorithm for HTMLMediaElement.seekable. |
| double source_duration = duration(NULL); |
| |
| if (std::isnan(source_duration)) { |
| return new TimeRanges; |
| } |
| |
| if (source_duration == std::numeric_limits<double>::infinity()) { |
| scoped_refptr<TimeRanges> buffered = attached_element_->buffered(); |
| |
| if (live_seekable_range_->length() != 0) { |
| if (buffered->length() == 0) { |
| return new TimeRanges(live_seekable_range_->Start(0, NULL), |
| live_seekable_range_->End(0, NULL)); |
| } |
| |
| return new TimeRanges( |
| std::min(live_seekable_range_->Start(0, NULL), |
| buffered->Start(0, NULL)), |
| std::max(live_seekable_range_->End(0, NULL), |
| buffered->End(buffered->length() - 1, NULL))); |
| } |
| |
| if (buffered->length() == 0) { |
| return new TimeRanges; |
| } |
| |
| return new TimeRanges(0, buffered->End(buffered->length() - 1, NULL)); |
| } |
| |
| return new TimeRanges(0, source_duration); |
| } |
| |
| void MediaSource::OnAudioTrackChanged(AudioTrack* audio_track) { |
| scoped_refptr<SourceBuffer> source_buffer = audio_track->source_buffer(); |
| |
| if (!source_buffer) { |
| return; |
| } |
| |
| DCHECK(source_buffers_->Contains(source_buffer)); |
| source_buffer->audio_tracks()->ScheduleChangeEvent(); |
| |
| bool is_active = (source_buffer->video_tracks()->selected_index() != -1) || |
| source_buffer->audio_tracks()->HasEnabledTrack(); |
| SetSourceBufferActive(source_buffer, is_active); |
| } |
| |
| void MediaSource::OnVideoTrackChanged(VideoTrack* video_track) { |
| scoped_refptr<SourceBuffer> source_buffer = video_track->source_buffer(); |
| |
| if (!source_buffer) { |
| return; |
| } |
| |
| DCHECK(source_buffers_->Contains(source_buffer)); |
| if (video_track->selected()) { |
| source_buffer->video_tracks()->OnTrackSelected(video_track->id()); |
| } |
| source_buffer->video_tracks()->ScheduleChangeEvent(); |
| |
| bool is_active = source_buffer->video_tracks()->selected_index() != -1 || |
| source_buffer->audio_tracks()->HasEnabledTrack(); |
| |
| SetSourceBufferActive(source_buffer, is_active); |
| } |
| |
| void MediaSource::OpenIfInEndedState() { |
| if (ready_state_ != kMediaSourceReadyStateEnded) { |
| return; |
| } |
| |
| SetReadyState(kMediaSourceReadyStateOpen); |
| chunk_demuxer_->UnmarkEndOfStream(); |
| } |
| |
| bool MediaSource::IsOpen() const { |
| return ready_state_ == kMediaSourceReadyStateOpen; |
| } |
| |
| void MediaSource::SetSourceBufferActive(SourceBuffer* source_buffer, |
| bool is_active) { |
| // We don't support deactivate a source buffer. |
| DCHECK(is_active); |
| DCHECK(source_buffers_->Contains(source_buffer)); |
| |
| if (!is_active) { |
| DCHECK(active_source_buffers_->Contains(source_buffer)); |
| active_source_buffers_->Remove(source_buffer); |
| return; |
| } |
| |
| if (active_source_buffers_->Contains(source_buffer)) { |
| return; |
| } |
| |
| size_t index = source_buffers_->Find(source_buffer); |
| |
| uint32 insert_position = 0; |
| while (insert_position < active_source_buffers_->length() && |
| source_buffers_->Find(active_source_buffers_->Item(insert_position)) < |
| index) { |
| ++insert_position; |
| } |
| |
| active_source_buffers_->Insert(insert_position, source_buffer); |
| } |
| |
| HTMLMediaElement* MediaSource::GetMediaElement() const { |
| return attached_element_; |
| } |
| |
| bool MediaSource::MediaElementHasMaxVideoCapabilities() const { |
| SB_DCHECK(attached_element_); |
| return has_max_video_capabilities_; |
| } |
| |
| SerializedAlgorithmRunner<SourceBufferAlgorithm>* |
| MediaSource::GetAlgorithmRunner(int job_size) { |
| if (!asynchronous_reduction_enabled_ && |
| job_size <= max_size_for_immediate_job_) { |
| // `default_algorithm_runner_` won't run jobs immediately when |
| // `asynchronous_reduction_enabled_` is false, so we use |
| // `immediate_job_algorithm_runner_` instead, which always has asynchronous |
| // reduction enabled. |
| return &immediate_job_algorithm_runner_; |
| } |
| if (!offload_algorithm_runner_) { |
| return &default_algorithm_runner_; |
| } |
| // The logic below is redundant as the code for immediate job can be |
| // consolidated with value of `asynchronous_reduction_enabled_` ignored. It's |
| // kept as is to leave existing behavior unchanged. |
| if (asynchronous_reduction_enabled_ && |
| job_size <= max_size_for_immediate_job_) { |
| // Append without posting new tasks is only supported on the default runner. |
| return &default_algorithm_runner_; |
| } |
| return offload_algorithm_runner_.get(); |
| } |
| |
| void MediaSource::TraceMembers(script::Tracer* tracer) { |
| web::EventTarget::TraceMembers(tracer); |
| |
| tracer->Trace(event_queue_); |
| tracer->Trace(attached_element_); |
| tracer->Trace(source_buffers_); |
| tracer->Trace(active_source_buffers_); |
| tracer->Trace(live_seekable_range_); |
| } |
| |
| void MediaSource::SetReadyState(MediaSourceReadyState ready_state) { |
| if (!offload_algorithm_runner_) { |
| // Setting `chunk_demuxer_` to NULL when there is an active algorithm |
| // running may cause crash. So `chunk_demuxer_` is reset later in the |
| // function. |
| // When `offload_algorithm_runner_` is null, the logic is kept as is to |
| // ensure that the behavior stays the same when offload is not enabled. |
| if (ready_state == kMediaSourceReadyStateClosed) { |
| chunk_demuxer_ = NULL; |
| } |
| } |
| |
| if (ready_state_ == ready_state) { |
| return; |
| } |
| |
| MediaSourceReadyState old_state = ready_state_; |
| ready_state_ = ready_state; |
| |
| if (IsOpen()) { |
| ScheduleEvent(base::Tokens::sourceopen()); |
| return; |
| } |
| |
| if (old_state == kMediaSourceReadyStateOpen && |
| ready_state_ == kMediaSourceReadyStateEnded) { |
| ScheduleEvent(base::Tokens::sourceended()); |
| return; |
| } |
| |
| DCHECK(IsClosed()); |
| |
| active_source_buffers_->Clear(); |
| |
| // Clear SourceBuffer references to this object. |
| for (uint32 i = 0; i < source_buffers_->length(); ++i) { |
| source_buffers_->Item(i)->OnRemovedFromMediaSource(); |
| } |
| source_buffers_->Clear(); |
| |
| attached_element_.reset(); |
| |
| ScheduleEvent(base::Tokens::sourceclose()); |
| |
| if (algorithm_process_thread_) { |
| algorithm_process_thread_->Stop(); |
| algorithm_process_thread_.reset(); |
| } |
| offload_algorithm_runner_.reset(); |
| chunk_demuxer_ = NULL; |
| } |
| |
| bool MediaSource::IsUpdating() const { |
| // Return true if any member of |source_buffers_| is updating. |
| for (uint32 i = 0; i < source_buffers_->length(); ++i) { |
| if (source_buffers_->Item(i)->updating()) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void MediaSource::ScheduleEvent(base::Token event_name) { |
| scoped_refptr<web::Event> event = new web::Event(event_name); |
| event->set_target(this); |
| event_queue_.Enqueue(event); |
| } |
| |
| } // namespace dom |
| } // namespace cobalt |