blob: c29a7a95c5d1831dc210aa4fc82b934083113c93 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/filters/chunk_demuxer.h"
#include <algorithm>
#include <limits>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/media_switches.h"
#include "media/base/media_tracks.h"
#include "media/base/mime_util.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_codecs.h"
#include "media/base/video_decoder_config.h"
#include "media/filters/frame_processor.h"
#include "media/filters/source_buffer_stream.h"
#include "media/filters/stream_parser_factory.h"
using base::TimeDelta;
// This macro determines which SourceBufferStream member various
// ChunkDemuxerStream methods use based on the buffering API |range_api_| set at
// construction time. See https://crbug.com/718641.
#define SBSTREAM_IS_SET \
(range_api_ == RangeApi::kLegacyByDts ? stream_dts_ != nullptr \
: stream_pts_ != nullptr)
#define SBSTREAM_OP(operation) \
(range_api_ == RangeApi::kLegacyByDts ? stream_dts_->operation \
: stream_pts_->operation)
#define SBSTREAM_RESET(config, log) \
{ \
if (range_api_ == RangeApi::kLegacyByDts) { \
stream_dts_.reset( \
new SourceBufferStream<SourceBufferRangeByDts>(config, log)); \
} else { \
stream_pts_.reset( \
new SourceBufferStream<SourceBufferRangeByPts>(config, log)); \
} \
}
namespace {
// Helper to attempt construction of a StreamParser specific to |content_type|
// and |codecs|.
// TODO(wolenetz): Consider relocating this to StreamParserFactory in
// conjunction with updating StreamParserFactory's isTypeSupported() to also
// parse codecs, rather than require preparsed vector.
std::unique_ptr<media::StreamParser> CreateParserForTypeAndCodecs(
const std::string& content_type,
const std::string& codecs,
media::MediaLog* media_log) {
std::vector<std::string> parsed_codec_ids;
media::SplitCodecs(codecs, &parsed_codec_ids);
return media::StreamParserFactory::Create(content_type, parsed_codec_ids,
media_log);
}
// Helper to calculate the expected codecs parsed from initialization segments
// for a few mime types that have an implicit codec.
std::string ExpectedCodecs(const std::string& content_type,
const std::string& codecs) {
if (codecs == "" && content_type == "audio/aac")
return "aac";
if (codecs == "" &&
(content_type == "audio/mpeg" || content_type == "audio/mp3"))
return "mp3";
return codecs;
}
} // namespace
namespace media {
ChunkDemuxerStream::ChunkDemuxerStream(Type type,
MediaTrack::Id media_track_id,
RangeApi range_api)
: type_(type),
range_api_(range_api),
liveness_(DemuxerStream::LIVENESS_UNKNOWN),
media_track_id_(media_track_id),
state_(UNINITIALIZED),
is_enabled_(true) {}
void ChunkDemuxerStream::StartReturningData() {
DVLOG(1) << "ChunkDemuxerStream::StartReturningData()";
base::AutoLock auto_lock(lock_);
DCHECK(!read_cb_);
ChangeState_Locked(RETURNING_DATA_FOR_READS);
}
void ChunkDemuxerStream::AbortReads() {
DVLOG(1) << "ChunkDemuxerStream::AbortReads()";
base::AutoLock auto_lock(lock_);
ChangeState_Locked(RETURNING_ABORT_FOR_READS);
if (read_cb_)
std::move(read_cb_).Run(kAborted, NULL);
}
void ChunkDemuxerStream::CompletePendingReadIfPossible() {
base::AutoLock auto_lock(lock_);
if (!read_cb_)
return;
CompletePendingReadIfPossible_Locked();
}
void ChunkDemuxerStream::Shutdown() {
DVLOG(1) << "ChunkDemuxerStream::Shutdown()";
base::AutoLock auto_lock(lock_);
ChangeState_Locked(SHUTDOWN);
// Pass an end of stream buffer to the pending callback to signal that no more
// data will be sent.
if (read_cb_) {
std::move(read_cb_).Run(DemuxerStream::kOk,
StreamParserBuffer::CreateEOSBuffer());
}
}
bool ChunkDemuxerStream::IsSeekWaitingForData() const {
base::AutoLock auto_lock(lock_);
// This method should not be called for text tracks. See the note in
// SourceBufferState::IsSeekWaitingForData().
DCHECK_NE(type_, DemuxerStream::TEXT);
return SBSTREAM_OP(IsSeekPending());
}
void ChunkDemuxerStream::Seek(TimeDelta time) {
DVLOG(1) << "ChunkDemuxerStream::Seek(" << time.InSecondsF() << ")";
base::AutoLock auto_lock(lock_);
DCHECK(!read_cb_);
DCHECK(state_ == UNINITIALIZED || state_ == RETURNING_ABORT_FOR_READS)
<< state_;
SBSTREAM_OP(Seek(time));
}
bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) {
if (buffers.empty())
return false;
base::AutoLock auto_lock(lock_);
DCHECK_NE(state_, SHUTDOWN);
if (!SBSTREAM_OP(Append(buffers))) {
DVLOG(1) << "ChunkDemuxerStream::Append() : stream append failed";
return false;
}
if (read_cb_)
CompletePendingReadIfPossible_Locked();
return true;
}
void ChunkDemuxerStream::Remove(TimeDelta start, TimeDelta end,
TimeDelta duration) {
base::AutoLock auto_lock(lock_);
SBSTREAM_OP(Remove(start, end, duration));
}
bool ChunkDemuxerStream::EvictCodedFrames(base::TimeDelta media_time,
size_t newDataSize) {
base::AutoLock auto_lock(lock_);
// If the stream is disabled, then the renderer is not reading from it and
// thus the read position might be stale. MSE GC algorithm uses the read
// position to determine when to stop removing data from the front of buffered
// ranges, so do a Seek in order to update the read position and allow the GC
// to collect unnecessary data that is earlier than the GOP containing
// |media_time|.
if (!is_enabled_)
SBSTREAM_OP(Seek(media_time));
// Note: The direct conversion from PTS to DTS is safe here, since we don't
// need to know currentTime precisely for GC. GC only needs to know which GOP
// currentTime points to.
DecodeTimestamp media_time_dts =
DecodeTimestamp::FromPresentationTime(media_time);
return SBSTREAM_OP(GarbageCollectIfNeeded(media_time_dts, newDataSize));
}
void ChunkDemuxerStream::OnMemoryPressure(
DecodeTimestamp media_time,
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level,
bool force_instant_gc) {
base::AutoLock auto_lock(lock_);
return SBSTREAM_OP(
OnMemoryPressure(media_time, memory_pressure_level, force_instant_gc));
}
void ChunkDemuxerStream::OnSetDuration(TimeDelta duration) {
base::AutoLock auto_lock(lock_);
SBSTREAM_OP(OnSetDuration(duration));
}
Ranges<TimeDelta> ChunkDemuxerStream::GetBufferedRanges(
TimeDelta duration) const {
base::AutoLock auto_lock(lock_);
if (type_ == TEXT) {
// Since text tracks are discontinuous and the lack of cues should not block
// playback, report the buffered range for text tracks as [0, |duration|) so
// that intesections with audio & video tracks are computed correctly when
// no cues are present.
Ranges<TimeDelta> text_range;
text_range.Add(TimeDelta(), duration);
return text_range;
}
Ranges<TimeDelta> range = SBSTREAM_OP(GetBufferedTime());
if (range.size() == 0u)
return range;
// Clamp the end of the stream's buffered ranges to fit within the duration.
// This can be done by intersecting the stream's range with the valid time
// range.
Ranges<TimeDelta> valid_time_range;
valid_time_range.Add(range.start(0), duration);
return range.IntersectionWith(valid_time_range);
}
TimeDelta ChunkDemuxerStream::GetHighestPresentationTimestamp() const {
base::AutoLock auto_lock(lock_);
return SBSTREAM_OP(GetHighestPresentationTimestamp());
}
TimeDelta ChunkDemuxerStream::GetBufferedDuration() const {
base::AutoLock auto_lock(lock_);
return SBSTREAM_OP(GetBufferedDuration());
}
size_t ChunkDemuxerStream::GetBufferedSize() const {
base::AutoLock auto_lock(lock_);
return SBSTREAM_OP(GetBufferedSize());
}
void ChunkDemuxerStream::OnStartOfCodedFrameGroup(DecodeTimestamp start_dts,
base::TimeDelta start_pts) {
DVLOG(2) << "ChunkDemuxerStream::OnStartOfCodedFrameGroup(dts "
<< start_dts.InSecondsF() << ", pts " << start_pts.InSecondsF()
<< ")";
base::AutoLock auto_lock(lock_);
SBSTREAM_OP(OnStartOfCodedFrameGroup(start_dts, start_pts));
}
bool ChunkDemuxerStream::UpdateAudioConfig(const AudioDecoderConfig& config,
bool allow_codec_change,
MediaLog* media_log) {
DCHECK(config.IsValidConfig());
DCHECK_EQ(type_, AUDIO);
base::AutoLock auto_lock(lock_);
if (!SBSTREAM_IS_SET) {
DCHECK_EQ(state_, UNINITIALIZED);
SBSTREAM_RESET(config, media_log);
return true;
}
return SBSTREAM_OP(UpdateAudioConfig(config, allow_codec_change));
}
bool ChunkDemuxerStream::UpdateVideoConfig(const VideoDecoderConfig& config,
bool allow_codec_change,
MediaLog* media_log) {
DCHECK(config.IsValidConfig());
DCHECK_EQ(type_, VIDEO);
base::AutoLock auto_lock(lock_);
if (!SBSTREAM_IS_SET) {
DCHECK_EQ(state_, UNINITIALIZED);
SBSTREAM_RESET(config, media_log);
return true;
}
return SBSTREAM_OP(UpdateVideoConfig(config, allow_codec_change));
}
void ChunkDemuxerStream::UpdateTextConfig(const TextTrackConfig& config,
MediaLog* media_log) {
DCHECK_EQ(type_, TEXT);
base::AutoLock auto_lock(lock_);
DCHECK(!SBSTREAM_IS_SET);
DCHECK_EQ(state_, UNINITIALIZED);
SBSTREAM_RESET(config, media_log);
}
void ChunkDemuxerStream::MarkEndOfStream() {
base::AutoLock auto_lock(lock_);
SBSTREAM_OP(MarkEndOfStream());
}
void ChunkDemuxerStream::UnmarkEndOfStream() {
base::AutoLock auto_lock(lock_);
SBSTREAM_OP(UnmarkEndOfStream());
}
// DemuxerStream methods.
void ChunkDemuxerStream::Read(const ReadCB& read_cb) {
base::AutoLock auto_lock(lock_);
DCHECK_NE(state_, UNINITIALIZED);
DCHECK(!read_cb_);
read_cb_ = BindToCurrentLoop(read_cb);
if (!is_enabled_) {
DVLOG(1) << "Read from disabled stream, returning EOS";
std::move(read_cb_).Run(kOk, StreamParserBuffer::CreateEOSBuffer());
return;
}
CompletePendingReadIfPossible_Locked();
}
DemuxerStream::Type ChunkDemuxerStream::type() const { return type_; }
DemuxerStream::Liveness ChunkDemuxerStream::liveness() const {
base::AutoLock auto_lock(lock_);
return liveness_;
}
AudioDecoderConfig ChunkDemuxerStream::audio_decoder_config() {
CHECK_EQ(type_, AUDIO);
base::AutoLock auto_lock(lock_);
// Trying to track down crash. http://crbug.com/715761
CHECK(SBSTREAM_IS_SET);
return SBSTREAM_OP(GetCurrentAudioDecoderConfig());
}
VideoDecoderConfig ChunkDemuxerStream::video_decoder_config() {
CHECK_EQ(type_, VIDEO);
base::AutoLock auto_lock(lock_);
// Trying to track down crash. http://crbug.com/715761
CHECK(SBSTREAM_IS_SET);
return SBSTREAM_OP(GetCurrentVideoDecoderConfig());
}
bool ChunkDemuxerStream::SupportsConfigChanges() { return true; }
bool ChunkDemuxerStream::IsEnabled() const {
base::AutoLock auto_lock(lock_);
return is_enabled_;
}
void ChunkDemuxerStream::SetEnabled(bool enabled, base::TimeDelta timestamp) {
base::AutoLock auto_lock(lock_);
if (enabled == is_enabled_)
return;
is_enabled_ = enabled;
if (enabled) {
DCHECK(SBSTREAM_IS_SET);
SBSTREAM_OP(Seek(timestamp));
} else if (read_cb_) {
DVLOG(1) << "Read from disabled stream, returning EOS";
std::move(read_cb_).Run(kOk, StreamParserBuffer::CreateEOSBuffer());
}
}
TextTrackConfig ChunkDemuxerStream::text_track_config() {
CHECK_EQ(type_, TEXT);
base::AutoLock auto_lock(lock_);
return SBSTREAM_OP(GetCurrentTextTrackConfig());
}
void ChunkDemuxerStream::SetStreamMemoryLimit(size_t memory_limit) {
base::AutoLock auto_lock(lock_);
SBSTREAM_OP(set_memory_limit(memory_limit));
}
void ChunkDemuxerStream::SetLiveness(Liveness liveness) {
base::AutoLock auto_lock(lock_);
liveness_ = liveness;
}
void ChunkDemuxerStream::ChangeState_Locked(State state) {
lock_.AssertAcquired();
DVLOG(1) << "ChunkDemuxerStream::ChangeState_Locked() : "
<< "type " << type_
<< " - " << state_ << " -> " << state;
state_ = state;
}
ChunkDemuxerStream::~ChunkDemuxerStream() = default;
void ChunkDemuxerStream::CompletePendingReadIfPossible_Locked() {
lock_.AssertAcquired();
DCHECK(read_cb_);
DemuxerStream::Status status = DemuxerStream::kAborted;
scoped_refptr<StreamParserBuffer> buffer;
switch (state_) {
case UNINITIALIZED:
NOTREACHED();
return;
case RETURNING_DATA_FOR_READS:
switch (SBSTREAM_OP(GetNextBuffer(&buffer))) {
case SourceBufferStreamStatus::kSuccess:
status = DemuxerStream::kOk;
DVLOG(2) << __func__ << ": returning kOk, type " << type_ << ", dts "
<< buffer->GetDecodeTimestamp().InSecondsF() << ", pts "
<< buffer->timestamp().InSecondsF() << ", dur "
<< buffer->duration().InSecondsF() << ", key "
<< buffer->is_key_frame();
break;
case SourceBufferStreamStatus::kNeedBuffer:
// Return early without calling |read_cb_| since we don't have
// any data to return yet.
DVLOG(2) << __func__ << ": returning kNeedBuffer, type " << type_;
return;
case SourceBufferStreamStatus::kEndOfStream:
status = DemuxerStream::kOk;
buffer = StreamParserBuffer::CreateEOSBuffer();
DVLOG(2) << __func__ << ": returning kOk with EOS buffer, type "
<< type_;
break;
case SourceBufferStreamStatus::kConfigChange:
status = kConfigChanged;
buffer = NULL;
DVLOG(2) << __func__ << ": returning kConfigChange, type " << type_;
break;
}
break;
case RETURNING_ABORT_FOR_READS:
// Null buffers should be returned in this state since we are waiting
// for a seek. Any buffers in the SourceBuffer should NOT be returned
// because they are associated with the seek.
status = DemuxerStream::kAborted;
buffer = NULL;
DVLOG(2) << __func__ << ": returning kAborted, type " << type_;
break;
case SHUTDOWN:
status = DemuxerStream::kOk;
buffer = StreamParserBuffer::CreateEOSBuffer();
DVLOG(2) << __func__ << ": returning kOk with EOS buffer, type " << type_;
break;
}
std::move(read_cb_).Run(status, buffer);
}
ChunkDemuxer::ChunkDemuxer(
const base::Closure& open_cb,
const base::Closure& progress_cb,
const EncryptedMediaInitDataCB& encrypted_media_init_data_cb,
MediaLog* media_log)
: state_(WAITING_FOR_INIT),
cancel_next_seek_(false),
host_(NULL),
open_cb_(open_cb),
progress_cb_(progress_cb),
encrypted_media_init_data_cb_(encrypted_media_init_data_cb),
media_log_(media_log),
duration_(kNoTimestamp),
user_specified_duration_(-1),
liveness_(DemuxerStream::LIVENESS_UNKNOWN),
detected_audio_track_count_(0),
detected_video_track_count_(0),
detected_text_track_count_(0),
buffering_by_pts_(base::FeatureList::IsEnabled(kMseBufferByPts)) {
DCHECK(open_cb_);
DCHECK(encrypted_media_init_data_cb_);
MEDIA_LOG(INFO, media_log_)
<< GetDisplayName()
<< (buffering_by_pts_ ? ": buffering by PTS" : ": buffering by DTS");
}
std::string ChunkDemuxer::GetDisplayName() const {
return "ChunkDemuxer";
}
void ChunkDemuxer::Initialize(DemuxerHost* host,
const PipelineStatusCB& init_cb) {
DVLOG(1) << "Init(), buffering_by_pts_=" << buffering_by_pts_;
TRACE_EVENT_ASYNC_BEGIN0("media", "ChunkDemuxer::Initialize", this);
base::AutoLock auto_lock(lock_);
if (state_ == SHUTDOWN) {
// Init cb must only be run after this method returns, so post.
init_cb_ = BindToCurrentLoop(init_cb);
RunInitCB_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
return;
}
DCHECK_EQ(state_, WAITING_FOR_INIT);
host_ = host;
// Do not post init_cb once this function returns because if there is an
// error after initialization, the error might be reported before init_cb
// has a chance to run. This is because ChunkDemuxer::ReportError_Locked
// directly calls DemuxerHost::OnDemuxerError: crbug.com/633016.
init_cb_ = init_cb;
ChangeState_Locked(INITIALIZING);
std::move(open_cb_).Run();
}
void ChunkDemuxer::Stop() {
DVLOG(1) << "Stop()";
Shutdown();
}
void ChunkDemuxer::Seek(TimeDelta time, const PipelineStatusCB& cb) {
DVLOG(1) << "Seek(" << time.InSecondsF() << ")";
DCHECK(time >= TimeDelta());
TRACE_EVENT_ASYNC_BEGIN0("media", "ChunkDemuxer::Seek", this);
base::AutoLock auto_lock(lock_);
DCHECK(!seek_cb_);
seek_cb_ = BindToCurrentLoop(cb);
if (state_ != INITIALIZED && state_ != ENDED) {
RunSeekCB_Locked(PIPELINE_ERROR_INVALID_STATE);
return;
}
if (cancel_next_seek_) {
cancel_next_seek_ = false;
RunSeekCB_Locked(PIPELINE_OK);
return;
}
SeekAllSources(time);
StartReturningData();
if (IsSeekWaitingForData_Locked()) {
DVLOG(1) << "Seek() : waiting for more data to arrive.";
return;
}
RunSeekCB_Locked(PIPELINE_OK);
}
// Demuxer implementation.
base::Time ChunkDemuxer::GetTimelineOffset() const {
return timeline_offset_;
}
std::vector<DemuxerStream*> ChunkDemuxer::GetAllStreams() {
base::AutoLock auto_lock(lock_);
std::vector<DemuxerStream*> result;
// Put enabled streams at the beginning of the list so that
// MediaResource::GetFirstStream returns the enabled stream if there is one.
// TODO(servolk): Revisit this after media track switching is supported.
for (const auto& stream : audio_streams_) {
if (stream->IsEnabled())
result.push_back(stream.get());
}
for (const auto& stream : video_streams_) {
if (stream->IsEnabled())
result.push_back(stream.get());
}
// Put disabled streams at the end of the vector.
for (const auto& stream : audio_streams_) {
if (!stream->IsEnabled())
result.push_back(stream.get());
}
for (const auto& stream : video_streams_) {
if (!stream->IsEnabled())
result.push_back(stream.get());
}
return result;
}
TimeDelta ChunkDemuxer::GetStartTime() const {
return TimeDelta();
}
int64_t ChunkDemuxer::GetMemoryUsage() const {
base::AutoLock auto_lock(lock_);
int64_t mem = 0;
for (const auto& s : audio_streams_)
mem += s->GetBufferedSize();
for (const auto& s : video_streams_)
mem += s->GetBufferedSize();
return mem;
}
void ChunkDemuxer::AbortPendingReads() {
base::AutoLock auto_lock(lock_);
DCHECK(state_ == INITIALIZED || state_ == ENDED || state_ == SHUTDOWN ||
state_ == PARSE_ERROR)
<< state_;
if (state_ == SHUTDOWN || state_ == PARSE_ERROR)
return;
AbortPendingReads_Locked();
}
void ChunkDemuxer::StartWaitingForSeek(TimeDelta seek_time) {
DVLOG(1) << "StartWaitingForSeek()";
base::AutoLock auto_lock(lock_);
DCHECK(state_ == INITIALIZED || state_ == ENDED || state_ == SHUTDOWN ||
state_ == PARSE_ERROR) << state_;
DCHECK(!seek_cb_);
if (state_ == SHUTDOWN || state_ == PARSE_ERROR)
return;
AbortPendingReads_Locked();
SeekAllSources(seek_time);
// Cancel state set in CancelPendingSeek() since we want to
// accept the next Seek().
cancel_next_seek_ = false;
}
void ChunkDemuxer::CancelPendingSeek(TimeDelta seek_time) {
base::AutoLock auto_lock(lock_);
DCHECK_NE(state_, INITIALIZING);
DCHECK(!seek_cb_ || IsSeekWaitingForData_Locked());
if (cancel_next_seek_)
return;
AbortPendingReads_Locked();
SeekAllSources(seek_time);
if (!seek_cb_) {
cancel_next_seek_ = true;
return;
}
RunSeekCB_Locked(PIPELINE_OK);
}
ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
const std::string& content_type,
const std::string& codecs) {
DVLOG(1) << __func__ << " id=" << id << " content_type=" << content_type
<< " codecs=" << codecs;
base::AutoLock auto_lock(lock_);
if ((state_ != WAITING_FOR_INIT && state_ != INITIALIZING) || IsValidId(id))
return kReachedIdLimit;
// TODO(wolenetz): Change to DCHECK once less verification in release build is
// needed. See https://crbug.com/786975.
CHECK(init_cb_);
std::unique_ptr<media::StreamParser> stream_parser(
CreateParserForTypeAndCodecs(content_type, codecs, media_log_));
if (!stream_parser) {
DVLOG(1) << __func__ << " failed: unsupported content_type=" << content_type
<< " codecs=" << codecs;
return ChunkDemuxer::kNotSupported;
}
std::unique_ptr<FrameProcessor> frame_processor(new FrameProcessor(
base::Bind(&ChunkDemuxer::IncreaseDurationIfNecessary,
base::Unretained(this)),
media_log_,
buffering_by_pts_ ? ChunkDemuxerStream::RangeApi::kNewByPts
: ChunkDemuxerStream::RangeApi::kLegacyByDts));
std::unique_ptr<SourceBufferState> source_state(new SourceBufferState(
std::move(stream_parser), std::move(frame_processor),
base::Bind(&ChunkDemuxer::CreateDemuxerStream, base::Unretained(this),
id),
media_log_));
SourceBufferState::NewTextTrackCB new_text_track_cb;
// TODO(wolenetz): Change these to DCHECKs or switch to returning
// kReachedIdLimit once less verification in release build is needed. See
// https://crbug.com/786975.
CHECK(pending_source_init_ids_.find(id) == pending_source_init_ids_.end());
auto insert_result = pending_source_init_ids_.insert(id);
CHECK(insert_result.first != pending_source_init_ids_.end());
CHECK(*insert_result.first == id);
CHECK(insert_result.second); // Only true if insertion succeeded.
source_state->Init(base::BindOnce(&ChunkDemuxer::OnSourceInitDone,
base::Unretained(this), id),
ExpectedCodecs(content_type, codecs),
encrypted_media_init_data_cb_, new_text_track_cb);
// TODO(wolenetz): Change to DCHECKs once less verification in release build
// is needed. See https://crbug.com/786975.
CHECK(!IsValidId(id));
source_state_map_[id] = std::move(source_state);
CHECK(IsValidId(id));
return kOk;
}
void ChunkDemuxer::SetTracksWatcher(
const std::string& id,
const MediaTracksUpdatedCB& tracks_updated_cb) {
base::AutoLock auto_lock(lock_);
CHECK(IsValidId(id));
source_state_map_[id]->SetTracksWatcher(tracks_updated_cb);
}
void ChunkDemuxer::SetParseWarningCallback(
const std::string& id,
const SourceBufferParseWarningCB& parse_warning_cb) {
base::AutoLock auto_lock(lock_);
CHECK(IsValidId(id));
source_state_map_[id]->SetParseWarningCallback(parse_warning_cb);
}
void ChunkDemuxer::RemoveId(const std::string& id) {
DVLOG(1) << __func__ << " id=" << id;
base::AutoLock auto_lock(lock_);
CHECK(IsValidId(id));
source_state_map_.erase(id);
pending_source_init_ids_.erase(id);
// Remove demuxer streams created for this id.
for (const ChunkDemuxerStream* s : id_to_streams_map_[id]) {
bool stream_found = false;
for (size_t i = 0; i < audio_streams_.size(); ++i) {
if (audio_streams_[i].get() == s) {
stream_found = true;
removed_streams_.push_back(std::move(audio_streams_[i]));
audio_streams_.erase(audio_streams_.begin() + i);
break;
}
}
if (stream_found)
continue;
for (size_t i = 0; i < video_streams_.size(); ++i) {
if (video_streams_[i].get() == s) {
stream_found = true;
removed_streams_.push_back(std::move(video_streams_[i]));
video_streams_.erase(video_streams_.begin() + i);
break;
}
}
CHECK(stream_found);
}
id_to_streams_map_.erase(id);
}
Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges(const std::string& id) const {
base::AutoLock auto_lock(lock_);
DCHECK(!id.empty());
auto itr = source_state_map_.find(id);
DCHECK(itr != source_state_map_.end());
return itr->second->GetBufferedRanges(duration_, state_ == ENDED);
}
base::TimeDelta ChunkDemuxer::GetHighestPresentationTimestamp(
const std::string& id) const {
base::AutoLock auto_lock(lock_);
DCHECK(!id.empty());
auto itr = source_state_map_.find(id);
DCHECK(itr != source_state_map_.end());
return itr->second->GetHighestPresentationTimestamp();
}
void ChunkDemuxer::FindAndEnableProperTracks(
const std::vector<MediaTrack::Id>& track_ids,
base::TimeDelta curr_time,
DemuxerStream::Type track_type,
TrackChangeCB change_completed_cb) {
base::AutoLock auto_lock(lock_);
std::set<ChunkDemuxerStream*> enabled_streams;
for (const auto& id : track_ids) {
auto it = track_id_to_demux_stream_map_.find(id);
if (it == track_id_to_demux_stream_map_.end())
continue;
ChunkDemuxerStream* stream = it->second;
DCHECK(stream);
DCHECK_EQ(track_type, stream->type());
// TODO(servolk): Remove after multiple enabled audio tracks are supported
// by the media::RendererImpl.
if (!enabled_streams.empty()) {
MEDIA_LOG(INFO, media_log_)
<< "Only one enabled track is supported, ignoring track " << id;
continue;
}
enabled_streams.insert(stream);
stream->SetEnabled(true, curr_time);
}
bool is_audio = track_type == DemuxerStream::AUDIO;
for (const auto& stream : is_audio ? audio_streams_ : video_streams_) {
if (stream && enabled_streams.find(stream.get()) == enabled_streams.end()) {
DVLOG(1) << __func__ << ": disabling stream " << stream.get();
stream->SetEnabled(false, curr_time);
}
}
std::vector<DemuxerStream*> streams(enabled_streams.begin(),
enabled_streams.end());
std::move(change_completed_cb).Run(track_type, streams);
}
void ChunkDemuxer::OnEnabledAudioTracksChanged(
const std::vector<MediaTrack::Id>& track_ids,
base::TimeDelta curr_time,
TrackChangeCB change_completed_cb) {
FindAndEnableProperTracks(track_ids, curr_time, DemuxerStream::AUDIO,
std::move(change_completed_cb));
}
void ChunkDemuxer::OnSelectedVideoTrackChanged(
const std::vector<MediaTrack::Id>& track_ids,
base::TimeDelta curr_time,
TrackChangeCB change_completed_cb) {
FindAndEnableProperTracks(track_ids, curr_time, DemuxerStream::VIDEO,
std::move(change_completed_cb));
}
void ChunkDemuxer::OnMemoryPressure(
base::TimeDelta currentMediaTime,
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level,
bool force_instant_gc) {
DecodeTimestamp media_time_dts =
DecodeTimestamp::FromPresentationTime(currentMediaTime);
base::AutoLock auto_lock(lock_);
for (const auto& itr : source_state_map_) {
itr.second->OnMemoryPressure(media_time_dts, memory_pressure_level,
force_instant_gc);
}
}
bool ChunkDemuxer::EvictCodedFrames(const std::string& id,
base::TimeDelta currentMediaTime,
size_t newDataSize) {
DVLOG(1) << __func__ << "(" << id << ")"
<< " media_time=" << currentMediaTime.InSecondsF()
<< " newDataSize=" << newDataSize;
base::AutoLock auto_lock(lock_);
DCHECK(!id.empty());
auto itr = source_state_map_.find(id);
if (itr == source_state_map_.end()) {
LOG(WARNING) << __func__ << " stream " << id << " not found";
return false;
}
return itr->second->EvictCodedFrames(currentMediaTime, newDataSize);
}
void ChunkDemuxer::AddBytesReceivedCallback(BytesReceivedCB bytes_received_cb) {
bytes_received_cb_ = bytes_received_cb;
}
bool ChunkDemuxer::AppendData(const std::string& id,
const uint8_t* data,
size_t length,
TimeDelta append_window_start,
TimeDelta append_window_end,
TimeDelta* timestamp_offset) {
DVLOG(1) << "AppendData(" << id << ", " << length << ")";
DCHECK(!id.empty());
DCHECK(timestamp_offset);
Ranges<TimeDelta> ranges;
{
base::AutoLock auto_lock(lock_);
DCHECK_NE(state_, ENDED);
// Capture if any of the SourceBuffers are waiting for data before we start
// parsing.
bool old_waiting_for_data = IsSeekWaitingForData_Locked();
if (length == 0u)
return true;
if (bytes_received_cb_)
bytes_received_cb_.Run(length);
DCHECK(data);
switch (state_) {
case INITIALIZING:
case INITIALIZED:
DCHECK(IsValidId(id));
if (!source_state_map_[id]->Append(data, length, append_window_start,
append_window_end,
timestamp_offset)) {
ReportError_Locked(CHUNK_DEMUXER_ERROR_APPEND_FAILED);
return false;
}
break;
case PARSE_ERROR:
case WAITING_FOR_INIT:
case ENDED:
case SHUTDOWN:
DVLOG(1) << "AppendData(): called in unexpected state " << state_;
return false;
}
// Check to see if data was appended at the pending seek point. This
// indicates we have parsed enough data to complete the seek. Work is still
// in progress at this point, but it's okay since |seek_cb_| will post.
if (old_waiting_for_data && !IsSeekWaitingForData_Locked() && seek_cb_)
RunSeekCB_Locked(PIPELINE_OK);
ranges = GetBufferedRanges_Locked();
}
host_->OnBufferedTimeRangesChanged(ranges);
progress_cb_.Run();
return true;
}
void ChunkDemuxer::ResetParserState(const std::string& id,
TimeDelta append_window_start,
TimeDelta append_window_end,
TimeDelta* timestamp_offset) {
DVLOG(1) << "ResetParserState(" << id << ")";
base::AutoLock auto_lock(lock_);
DCHECK(!id.empty());
CHECK(IsValidId(id));
bool old_waiting_for_data = IsSeekWaitingForData_Locked();
source_state_map_[id]->ResetParserState(append_window_start,
append_window_end,
timestamp_offset);
// ResetParserState can possibly emit some buffers.
// Need to check whether seeking can be completed.
if (old_waiting_for_data && !IsSeekWaitingForData_Locked() && seek_cb_)
RunSeekCB_Locked(PIPELINE_OK);
}
void ChunkDemuxer::Remove(const std::string& id, TimeDelta start,
TimeDelta end) {
DVLOG(1) << "Remove(" << id << ", " << start.InSecondsF()
<< ", " << end.InSecondsF() << ")";
base::AutoLock auto_lock(lock_);
DCHECK(!id.empty());
CHECK(IsValidId(id));
DCHECK(start >= base::TimeDelta()) << start.InSecondsF();
DCHECK(start < end) << "start " << start.InSecondsF()
<< " end " << end.InSecondsF();
DCHECK(duration_ != kNoTimestamp);
DCHECK(start <= duration_) << "start " << start.InSecondsF()
<< " duration " << duration_.InSecondsF();
if (start == duration_)
return;
source_state_map_[id]->Remove(start, end, duration_);
host_->OnBufferedTimeRangesChanged(GetBufferedRanges_Locked());
}
bool ChunkDemuxer::CanChangeType(const std::string& id,
const std::string& content_type,
const std::string& codecs) {
// Note, Chromium currently will not compare content_type and codecs, if any,
// with previous content_type and codecs of the SourceBuffer.
// TODO(wolenetz): Consider returning false if the codecs parameters are ever
// made to be precise such that they signal that the number of tracks of
// various media types differ from the first initialization segment (if
// received already). Switching to an audio-only container, when the first
// initialization segment only contained non-audio tracks, is one example we
// could enforce earlier here.
DVLOG(1) << __func__ << " id=" << id << " content_type=" << content_type
<< " codecs=" << codecs;
base::AutoLock auto_lock(lock_);
DCHECK(IsValidId(id));
// CanChangeType() doesn't care if there has or hasn't been received a first
// initialization segment for the source buffer corresponding to |id|.
std::unique_ptr<media::StreamParser> stream_parser(
CreateParserForTypeAndCodecs(content_type, codecs, media_log_));
return !!stream_parser;
}
void ChunkDemuxer::ChangeType(const std::string& id,
const std::string& content_type,
const std::string& codecs) {
DVLOG(1) << __func__ << " id=" << id << " content_type=" << content_type
<< " codecs=" << codecs;
base::AutoLock auto_lock(lock_);
DCHECK(state_ == INITIALIZING || state_ == INITIALIZED) << state_;
DCHECK(IsValidId(id));
std::unique_ptr<media::StreamParser> stream_parser(
CreateParserForTypeAndCodecs(content_type, codecs, media_log_));
// Caller should query CanChangeType() first to protect from failing this.
DCHECK(stream_parser);
source_state_map_[id]->ChangeType(std::move(stream_parser),
ExpectedCodecs(content_type, codecs));
}
double ChunkDemuxer::GetDuration() {
base::AutoLock auto_lock(lock_);
return GetDuration_Locked();
}
double ChunkDemuxer::GetDuration_Locked() {
lock_.AssertAcquired();
if (duration_ == kNoTimestamp)
return std::numeric_limits<double>::quiet_NaN();
// Return positive infinity if the resource is unbounded.
// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration
if (duration_ == kInfiniteDuration)
return std::numeric_limits<double>::infinity();
if (user_specified_duration_ >= 0)
return user_specified_duration_;
return duration_.InSecondsF();
}
void ChunkDemuxer::SetDuration(double duration) {
base::AutoLock auto_lock(lock_);
DVLOG(1) << "SetDuration(" << duration << ")";
DCHECK_GE(duration, 0);
if (duration == GetDuration_Locked())
return;
// Compute & bounds check the TimeDelta representation of duration.
// This can be different if the value of |duration| doesn't fit the range or
// precision of TimeDelta.
TimeDelta min_duration = TimeDelta::FromInternalValue(1);
// Don't use TimeDelta::Max() here, as we want the largest finite time delta.
TimeDelta max_duration =
TimeDelta::FromInternalValue(std::numeric_limits<int64_t>::max() - 1);
double min_duration_in_seconds = min_duration.InSecondsF();
double max_duration_in_seconds = max_duration.InSecondsF();
TimeDelta duration_td;
if (duration == std::numeric_limits<double>::infinity()) {
duration_td = media::kInfiniteDuration;
} else if (duration < min_duration_in_seconds) {
duration_td = min_duration;
} else if (duration > max_duration_in_seconds) {
duration_td = max_duration;
} else {
duration_td = TimeDelta::FromMicroseconds(
duration * base::Time::kMicrosecondsPerSecond);
}
DCHECK(duration_td > TimeDelta());
user_specified_duration_ = duration;
duration_ = duration_td;
host_->SetDuration(duration_);
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->OnSetDuration(duration_);
}
}
bool ChunkDemuxer::IsParsingMediaSegment(const std::string& id) {
base::AutoLock auto_lock(lock_);
DVLOG(1) << "IsParsingMediaSegment(" << id << ")";
CHECK(IsValidId(id));
return source_state_map_[id]->parsing_media_segment();
}
bool ChunkDemuxer::GetGenerateTimestampsFlag(const std::string& id) {
base::AutoLock auto_lock(lock_);
DVLOG(1) << "GetGenerateTimestampsFlag(" << id << ")";
CHECK(IsValidId(id));
return source_state_map_[id]->generate_timestamps_flag();
}
void ChunkDemuxer::SetSequenceMode(const std::string& id,
bool sequence_mode) {
base::AutoLock auto_lock(lock_);
DVLOG(1) << "SetSequenceMode(" << id << ", " << sequence_mode << ")";
CHECK(IsValidId(id));
DCHECK_NE(state_, ENDED);
source_state_map_[id]->SetSequenceMode(sequence_mode);
}
void ChunkDemuxer::SetGroupStartTimestampIfInSequenceMode(
const std::string& id,
base::TimeDelta timestamp_offset) {
base::AutoLock auto_lock(lock_);
DVLOG(1) << "SetGroupStartTimestampIfInSequenceMode(" << id << ", "
<< timestamp_offset.InSecondsF() << ")";
CHECK(IsValidId(id));
DCHECK_NE(state_, ENDED);
source_state_map_[id]->SetGroupStartTimestampIfInSequenceMode(
timestamp_offset);
}
void ChunkDemuxer::MarkEndOfStream(PipelineStatus status) {
DVLOG(1) << "MarkEndOfStream(" << status << ")";
base::AutoLock auto_lock(lock_);
DCHECK_NE(state_, WAITING_FOR_INIT);
DCHECK_NE(state_, ENDED);
if (state_ == SHUTDOWN || state_ == PARSE_ERROR)
return;
if (state_ == INITIALIZING) {
MEDIA_LOG(ERROR, media_log_)
<< "MediaSource endOfStream before demuxer initialization completes "
"(before HAVE_METADATA) is treated as an error. This may also occur "
"as consequence of other MediaSource errors before HAVE_METADATA.";
ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
return;
}
bool old_waiting_for_data = IsSeekWaitingForData_Locked();
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->MarkEndOfStream();
}
CompletePendingReadsIfPossible();
// Give a chance to resume the pending seek process.
if (status != PIPELINE_OK) {
DCHECK(status == CHUNK_DEMUXER_ERROR_EOS_STATUS_DECODE_ERROR ||
status == CHUNK_DEMUXER_ERROR_EOS_STATUS_NETWORK_ERROR);
ReportError_Locked(status);
return;
}
ChangeState_Locked(ENDED);
DecreaseDurationIfNecessary();
if (old_waiting_for_data && !IsSeekWaitingForData_Locked() && seek_cb_)
RunSeekCB_Locked(PIPELINE_OK);
}
void ChunkDemuxer::UnmarkEndOfStream() {
DVLOG(1) << "UnmarkEndOfStream()";
base::AutoLock auto_lock(lock_);
DCHECK(state_ == ENDED || state_ == SHUTDOWN || state_ == PARSE_ERROR)
<< state_;
// At least ReportError_Locked()'s error reporting to Blink hops threads, so
// SourceBuffer may not be aware of media element error on another operation
// that might race to this point.
if (state_ == PARSE_ERROR || state_ == SHUTDOWN)
return;
ChangeState_Locked(INITIALIZED);
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->UnmarkEndOfStream();
}
}
void ChunkDemuxer::Shutdown() {
DVLOG(1) << "Shutdown()";
base::AutoLock auto_lock(lock_);
if (state_ == SHUTDOWN)
return;
ShutdownAllStreams();
ChangeState_Locked(SHUTDOWN);
if (seek_cb_)
RunSeekCB_Locked(PIPELINE_ERROR_ABORT);
}
void ChunkDemuxer::SetMemoryLimitsForTest(DemuxerStream::Type type,
size_t memory_limit) {
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->SetMemoryLimits(type, memory_limit);
}
}
void ChunkDemuxer::ChangeState_Locked(State new_state) {
lock_.AssertAcquired();
DVLOG(1) << "ChunkDemuxer::ChangeState_Locked() : "
<< state_ << " -> " << new_state;
// TODO(wolenetz): Change to DCHECK once less verification in release build is
// needed. See https://crbug.com/786975.
// Disallow changes from at or beyond PARSE_ERROR to below PARSE_ERROR.
CHECK(!(state_ >= PARSE_ERROR && new_state < PARSE_ERROR));
state_ = new_state;
}
ChunkDemuxer::~ChunkDemuxer() {
DCHECK_NE(state_, INITIALIZED);
}
void ChunkDemuxer::ReportError_Locked(PipelineStatus error) {
DVLOG(1) << "ReportError_Locked(" << error << ")";
lock_.AssertAcquired();
DCHECK_NE(error, PIPELINE_OK);
ChangeState_Locked(PARSE_ERROR);
if (init_cb_) {
RunInitCB_Locked(error);
return;
}
ShutdownAllStreams();
if (seek_cb_) {
RunSeekCB_Locked(error);
return;
}
base::AutoUnlock auto_unlock(lock_);
host_->OnDemuxerError(error);
}
bool ChunkDemuxer::IsSeekWaitingForData_Locked() const {
lock_.AssertAcquired();
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
if (itr->second->IsSeekWaitingForData())
return true;
}
return false;
}
void ChunkDemuxer::OnSourceInitDone(
const std::string& source_id,
const StreamParser::InitParameters& params) {
DVLOG(1) << "OnSourceInitDone source_id=" << source_id
<< " duration=" << params.duration.InSecondsF();
lock_.AssertAcquired();
// TODO(wolenetz): Change these to DCHECKs once less verification in release
// build is needed. See https://crbug.com/786975.
CHECK(!pending_source_init_ids_.empty());
CHECK(IsValidId(source_id));
CHECK(pending_source_init_ids_.find(source_id) !=
pending_source_init_ids_.end());
CHECK(init_cb_);
CHECK_EQ(state_, INITIALIZING);
if (audio_streams_.empty() && video_streams_.empty()) {
ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
return;
}
if (!params.duration.is_zero() && duration_ == kNoTimestamp)
UpdateDuration(params.duration);
if (!params.timeline_offset.is_null()) {
if (!timeline_offset_.is_null() &&
params.timeline_offset != timeline_offset_) {
MEDIA_LOG(ERROR, media_log_)
<< "Timeline offset is not the same across all SourceBuffers.";
ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN);
return;
}
timeline_offset_ = params.timeline_offset;
}
if (params.liveness != DemuxerStream::LIVENESS_UNKNOWN) {
for (const auto& s : audio_streams_)
s->SetLiveness(params.liveness);
for (const auto& s : video_streams_)
s->SetLiveness(params.liveness);
}
detected_audio_track_count_ += params.detected_audio_track_count;
detected_video_track_count_ += params.detected_video_track_count;
detected_text_track_count_ += params.detected_text_track_count;
// Wait until all streams have initialized.
pending_source_init_ids_.erase(source_id);
if (!pending_source_init_ids_.empty())
return;
// Record detected track counts by type corresponding to an MSE playback.
// Counts are split into 50 buckets, capped into [0,100] range.
UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Audio",
detected_audio_track_count_);
UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Video",
detected_video_track_count_);
UMA_HISTOGRAM_COUNTS_100("Media.MSE.DetectedTrackCount.Text",
detected_text_track_count_);
for (const auto& s : video_streams_) {
media_log_->RecordRapporWithSecurityOrigin(
"Media.OriginUrl.MSE.VideoCodec." +
GetCodecName(s->video_decoder_config().codec()));
}
SeekAllSources(GetStartTime());
StartReturningData();
if (duration_ == kNoTimestamp)
duration_ = kInfiniteDuration;
// The demuxer is now initialized after the |start_timestamp_| was set.
// TODO(wolenetz): Change these to DCHECKs once less verification in release
// build is needed. See https://crbug.com/786975.
CHECK_EQ(state_, INITIALIZING);
ChangeState_Locked(INITIALIZED);
RunInitCB_Locked(PIPELINE_OK);
}
// static
MediaTrack::Id ChunkDemuxer::GenerateMediaTrackId() {
static unsigned g_track_count = 0;
return base::UintToString(++g_track_count);
}
ChunkDemuxerStream* ChunkDemuxer::CreateDemuxerStream(
const std::string& source_id,
DemuxerStream::Type type) {
// New ChunkDemuxerStreams can be created only during initialization segment
// processing, which happens when a new chunk of data is appended and the
// lock_ must be held by ChunkDemuxer::AppendData.
lock_.AssertAcquired();
MediaTrack::Id media_track_id = GenerateMediaTrackId();
OwnedChunkDemuxerStreamVector* owning_vector = nullptr;
switch (type) {
case DemuxerStream::AUDIO:
owning_vector = &audio_streams_;
break;
case DemuxerStream::VIDEO:
owning_vector = &video_streams_;
break;
case DemuxerStream::TEXT:
owning_vector = &text_streams_;
break;
case DemuxerStream::UNKNOWN:
NOTREACHED();
return nullptr;
}
std::unique_ptr<ChunkDemuxerStream> stream =
std::make_unique<ChunkDemuxerStream>(
type, media_track_id,
(buffering_by_pts_ ? ChunkDemuxerStream::RangeApi::kNewByPts
: ChunkDemuxerStream::RangeApi::kLegacyByDts));
DCHECK(track_id_to_demux_stream_map_.find(media_track_id) ==
track_id_to_demux_stream_map_.end());
track_id_to_demux_stream_map_[media_track_id] = stream.get();
id_to_streams_map_[source_id].push_back(stream.get());
stream->SetEnabled(owning_vector->empty(), base::TimeDelta());
owning_vector->push_back(std::move(stream));
return owning_vector->back().get();
}
bool ChunkDemuxer::IsValidId(const std::string& source_id) const {
lock_.AssertAcquired();
return source_state_map_.count(source_id) > 0u;
}
void ChunkDemuxer::UpdateDuration(TimeDelta new_duration) {
DCHECK(duration_ != new_duration ||
user_specified_duration_ != new_duration.InSecondsF());
user_specified_duration_ = -1;
duration_ = new_duration;
host_->SetDuration(new_duration);
}
void ChunkDemuxer::IncreaseDurationIfNecessary(TimeDelta new_duration) {
DCHECK(new_duration != kNoTimestamp);
DCHECK(new_duration != kInfiniteDuration);
// Per April 1, 2014 MSE spec editor's draft:
// https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/
// media-source.html#sourcebuffer-coded-frame-processing
// 5. If the media segment contains data beyond the current duration, then run
// the duration change algorithm with new duration set to the maximum of
// the current duration and the group end timestamp.
if (new_duration <= duration_)
return;
DVLOG(2) << __func__ << ": Increasing duration: " << duration_.InSecondsF()
<< " -> " << new_duration.InSecondsF();
UpdateDuration(new_duration);
}
void ChunkDemuxer::DecreaseDurationIfNecessary() {
lock_.AssertAcquired();
TimeDelta max_duration;
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
max_duration = std::max(max_duration,
itr->second->GetMaxBufferedDuration());
}
if (max_duration.is_zero())
return;
// Note: be careful to also check |user_specified_duration_|, which may have
// higher precision than |duration_|.
if (max_duration < duration_ ||
max_duration.InSecondsF() < user_specified_duration_) {
UpdateDuration(max_duration);
}
}
Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges() const {
base::AutoLock auto_lock(lock_);
return GetBufferedRanges_Locked();
}
Ranges<TimeDelta> ChunkDemuxer::GetBufferedRanges_Locked() const {
lock_.AssertAcquired();
bool ended = state_ == ENDED;
// TODO(acolwell): When we start allowing SourceBuffers that are not active,
// we'll need to update this loop to only add ranges from active sources.
SourceBufferState::RangesList ranges_list;
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
ranges_list.push_back(itr->second->GetBufferedRanges(duration_, ended));
}
return SourceBufferState::ComputeRangesIntersection(ranges_list, ended);
}
void ChunkDemuxer::StartReturningData() {
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->StartReturningData();
}
}
void ChunkDemuxer::AbortPendingReads_Locked() {
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->AbortReads();
}
}
void ChunkDemuxer::SeekAllSources(TimeDelta seek_time) {
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->Seek(seek_time);
}
}
void ChunkDemuxer::CompletePendingReadsIfPossible() {
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->CompletePendingReadIfPossible();
}
}
void ChunkDemuxer::ShutdownAllStreams() {
for (auto itr = source_state_map_.begin(); itr != source_state_map_.end();
++itr) {
itr->second->Shutdown();
}
}
void ChunkDemuxer::RunInitCB_Locked(PipelineStatus status) {
lock_.AssertAcquired();
DCHECK(init_cb_);
TRACE_EVENT_ASYNC_END1("media", "ChunkDemuxer::Initialize", this, "status",
MediaLog::PipelineStatusToString(status));
std::move(init_cb_).Run(status);
}
void ChunkDemuxer::RunSeekCB_Locked(PipelineStatus status) {
lock_.AssertAcquired();
DCHECK(seek_cb_);
TRACE_EVENT_ASYNC_END1("media", "ChunkDemuxer::Seek", this, "status",
MediaLog::PipelineStatusToString(status));
std::move(seek_cb_).Run(status);
}
} // namespace media