blob: 5233ddf89756244889136e52f2708951efa95a8e [file] [log] [blame]
// Copyright 2014 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/renderers/renderer_impl.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/audio_renderer.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/media_log.h"
#include "media/base/media_resource.h"
#include "media/base/media_switches.h"
#include "media/base/renderer_client.h"
#include "media/base/time_source.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_renderer.h"
#include "media/base/wall_clock_time_source.h"
namespace media {
// See |video_underflow_threshold_|.
static const int kDefaultVideoUnderflowThresholdMs = 3000;
class RendererImpl::RendererClientInternal final : public RendererClient {
public:
RendererClientInternal(DemuxerStream::Type type, RendererImpl* renderer)
: type_(type), renderer_(renderer) {
DCHECK((type_ == DemuxerStream::AUDIO) || (type_ == DemuxerStream::VIDEO));
}
void OnError(PipelineStatus error) override { renderer_->OnError(error); }
void OnEnded() override { renderer_->OnRendererEnded(type_); }
void OnStatisticsUpdate(const PipelineStatistics& stats) override {
renderer_->OnStatisticsUpdate(stats);
}
void OnBufferingStateChange(BufferingState state) override {
renderer_->OnBufferingStateChange(type_, state);
}
void OnWaitingForDecryptionKey() override {
renderer_->OnWaitingForDecryptionKey();
}
void OnAudioConfigChange(const AudioDecoderConfig& config) override {
renderer_->OnAudioConfigChange(config);
}
void OnVideoConfigChange(const VideoDecoderConfig& config) override {
renderer_->OnVideoConfigChange(config);
}
void OnVideoNaturalSizeChange(const gfx::Size& size) override {
DCHECK(type_ == DemuxerStream::VIDEO);
renderer_->OnVideoNaturalSizeChange(size);
}
void OnVideoOpacityChange(bool opaque) override {
DCHECK(type_ == DemuxerStream::VIDEO);
renderer_->OnVideoOpacityChange(opaque);
}
void OnDurationChange(base::TimeDelta duration) override {
// RendererClients should only be notified of duration changes in certain
// scenarios, none of which should arise for RendererClientInternal.
// Duration changes should be sent to the pipeline by the DemuxerStream, via
// the DemuxerHost interface.
NOTREACHED();
}
private:
DemuxerStream::Type type_;
RendererImpl* renderer_;
};
RendererImpl::RendererImpl(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
std::unique_ptr<AudioRenderer> audio_renderer,
std::unique_ptr<VideoRenderer> video_renderer)
: state_(STATE_UNINITIALIZED),
task_runner_(task_runner),
audio_renderer_(std::move(audio_renderer)),
video_renderer_(std::move(video_renderer)),
current_audio_stream_(nullptr),
current_video_stream_(nullptr),
time_source_(NULL),
time_ticking_(false),
playback_rate_(0.0),
audio_buffering_state_(BUFFERING_HAVE_NOTHING),
video_buffering_state_(BUFFERING_HAVE_NOTHING),
audio_ended_(false),
video_ended_(false),
audio_playing_(false),
video_playing_(false),
cdm_context_(nullptr),
underflow_disabled_for_testing_(false),
clockless_video_playback_enabled_for_testing_(false),
video_underflow_threshold_(
base::TimeDelta::FromMilliseconds(kDefaultVideoUnderflowThresholdMs)),
pending_audio_track_change_(false),
pending_video_track_change_(false),
weak_factory_(this) {
weak_this_ = weak_factory_.GetWeakPtr();
DVLOG(1) << __func__;
// TODO(dalecurtis): Remove once experiments for http://crbug.com/470940 are
// complete.
int threshold_ms = 0;
std::string threshold_ms_str(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kVideoUnderflowThresholdMs));
if (base::StringToInt(threshold_ms_str, &threshold_ms) && threshold_ms > 0) {
video_underflow_threshold_ =
base::TimeDelta::FromMilliseconds(threshold_ms);
}
}
RendererImpl::~RendererImpl() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
// RendererImpl is being destroyed, so invalidate weak pointers right away to
// avoid getting callbacks which might try to access fields that has been
// destroyed, e.g. audio_renderer_/video_renderer_ below (crbug.com/668963).
weak_factory_.InvalidateWeakPtrs();
// Tear down in opposite order of construction as |video_renderer_| can still
// need |time_source_| (which can be |audio_renderer_|) to be alive.
video_renderer_.reset();
audio_renderer_.reset();
if (!init_cb_.is_null()) {
FinishInitialization(PIPELINE_ERROR_ABORT);
} else if (!flush_cb_.is_null()) {
base::ResetAndReturn(&flush_cb_).Run();
}
}
void RendererImpl::Initialize(MediaResource* media_resource,
RendererClient* client,
const PipelineStatusCB& init_cb) {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_UNINITIALIZED);
DCHECK(!init_cb.is_null());
DCHECK(client);
client_ = client;
media_resource_ = media_resource;
init_cb_ = init_cb;
if (HasEncryptedStream() && !cdm_context_) {
DVLOG(1) << __func__ << ": Has encrypted stream but CDM is not set.";
state_ = STATE_INIT_PENDING_CDM;
return;
}
state_ = STATE_INITIALIZING;
InitializeAudioRenderer();
}
void RendererImpl::SetCdm(CdmContext* cdm_context,
const CdmAttachedCB& cdm_attached_cb) {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(cdm_context);
if (cdm_context_) {
DVLOG(1) << "Switching CDM not supported.";
cdm_attached_cb.Run(false);
return;
}
cdm_context_ = cdm_context;
cdm_attached_cb.Run(true);
if (state_ != STATE_INIT_PENDING_CDM)
return;
DCHECK(!init_cb_.is_null());
state_ = STATE_INITIALIZING;
InitializeAudioRenderer();
}
void RendererImpl::Flush(const base::Closure& flush_cb) {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(flush_cb_.is_null());
DCHECK(!(pending_audio_track_change_ || pending_video_track_change_));
if (state_ == STATE_FLUSHED) {
task_runner_->PostTask(FROM_HERE, flush_cb);
return;
}
if (state_ != STATE_PLAYING) {
DCHECK_EQ(state_, STATE_ERROR);
return;
}
flush_cb_ = flush_cb;
state_ = STATE_FLUSHING;
// If a stream restart is pending, this Flush() will complete it. Upon flush
// completion any pending actions will be executed as well.
FlushInternal();
}
void RendererImpl::StartPlayingFrom(base::TimeDelta time) {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ != STATE_FLUSHED) {
DCHECK_EQ(state_, STATE_ERROR);
return;
}
time_source_->SetMediaTime(time);
state_ = STATE_PLAYING;
if (audio_renderer_) {
audio_playing_ = true;
audio_renderer_->StartPlaying();
}
if (video_renderer_) {
video_playing_ = true;
video_renderer_->StartPlayingFrom(time);
}
}
void RendererImpl::SetPlaybackRate(double playback_rate) {
DVLOG(1) << __func__ << "(" << playback_rate << ")";
DCHECK(task_runner_->BelongsToCurrentThread());
// Playback rate changes are only carried out while playing.
if (state_ != STATE_PLAYING && state_ != STATE_FLUSHED)
return;
time_source_->SetPlaybackRate(playback_rate);
const double old_rate = playback_rate_;
playback_rate_ = playback_rate;
if (!time_ticking_ || !video_renderer_)
return;
if (old_rate == 0 && playback_rate > 0)
video_renderer_->OnTimeProgressing();
else if (old_rate > 0 && playback_rate == 0)
video_renderer_->OnTimeStopped();
}
void RendererImpl::SetVolume(float volume) {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
if (audio_renderer_)
audio_renderer_->SetVolume(volume);
}
base::TimeDelta RendererImpl::GetMediaTime() {
// No BelongsToCurrentThread() checking because this can be called from other
// threads.
{
base::AutoLock lock(restarting_audio_lock_);
if (pending_audio_track_change_) {
DCHECK_NE(kNoTimestamp, restarting_audio_time_);
return restarting_audio_time_;
}
}
return time_source_->CurrentMediaTime();
}
void RendererImpl::DisableUnderflowForTesting() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_UNINITIALIZED);
underflow_disabled_for_testing_ = true;
}
void RendererImpl::EnableClocklessVideoPlaybackForTesting() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_UNINITIALIZED);
DCHECK(underflow_disabled_for_testing_)
<< "Underflow must be disabled for clockless video playback";
clockless_video_playback_enabled_for_testing_ = true;
}
bool RendererImpl::GetWallClockTimes(
const std::vector<base::TimeDelta>& media_timestamps,
std::vector<base::TimeTicks>* wall_clock_times) {
// No BelongsToCurrentThread() checking because this can be called from other
// threads.
//
// TODO(scherkus): Currently called from VideoRendererImpl's internal thread,
// which should go away at some point http://crbug.com/110814
if (clockless_video_playback_enabled_for_testing_) {
if (media_timestamps.empty()) {
*wall_clock_times = std::vector<base::TimeTicks>(1,
base::TimeTicks::Now());
} else {
*wall_clock_times = std::vector<base::TimeTicks>();
for (auto const &media_time : media_timestamps) {
wall_clock_times->push_back(base::TimeTicks() + media_time);
}
}
return true;
}
return time_source_->GetWallClockTimes(media_timestamps, wall_clock_times);
}
bool RendererImpl::HasEncryptedStream() {
std::vector<DemuxerStream*> demuxer_streams =
media_resource_->GetAllStreams();
for (auto* stream : demuxer_streams) {
if (stream->type() == DemuxerStream::AUDIO &&
stream->audio_decoder_config().is_encrypted())
return true;
if (stream->type() == DemuxerStream::VIDEO &&
stream->video_decoder_config().is_encrypted())
return true;
}
return false;
}
void RendererImpl::FinishInitialization(PipelineStatus status) {
DCHECK(!init_cb_.is_null());
base::ResetAndReturn(&init_cb_).Run(status);
}
void RendererImpl::InitializeAudioRenderer() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_INITIALIZING);
DCHECK(!init_cb_.is_null());
PipelineStatusCB done_cb =
base::Bind(&RendererImpl::OnAudioRendererInitializeDone, weak_this_);
// TODO(servolk): Implement proper support for multiple streams. But for now
// pick the first enabled stream to preserve the existing behavior.
DemuxerStream* audio_stream =
media_resource_->GetFirstStream(DemuxerStream::AUDIO);
if (!audio_stream) {
audio_renderer_.reset();
task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK));
return;
}
current_audio_stream_ = audio_stream;
audio_renderer_client_.reset(
new RendererClientInternal(DemuxerStream::AUDIO, this));
// Note: After the initialization of a renderer, error events from it may
// happen at any time and all future calls must guard against STATE_ERROR.
audio_renderer_->Initialize(audio_stream, cdm_context_,
audio_renderer_client_.get(), done_cb);
}
void RendererImpl::OnAudioRendererInitializeDone(PipelineStatus status) {
DVLOG(1) << __func__ << ": " << status;
DCHECK(task_runner_->BelongsToCurrentThread());
// OnError() may be fired at any time by the renderers, even if they thought
// they initialized successfully (due to delayed output device setup).
if (state_ != STATE_INITIALIZING) {
DCHECK(init_cb_.is_null());
audio_renderer_.reset();
return;
}
if (status != PIPELINE_OK) {
FinishInitialization(status);
return;
}
DCHECK(!init_cb_.is_null());
InitializeVideoRenderer();
}
void RendererImpl::InitializeVideoRenderer() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_INITIALIZING);
DCHECK(!init_cb_.is_null());
PipelineStatusCB done_cb =
base::Bind(&RendererImpl::OnVideoRendererInitializeDone, weak_this_);
// TODO(servolk): Implement proper support for multiple streams. But for now
// pick the first enabled stream to preserve the existing behavior.
DemuxerStream* video_stream =
media_resource_->GetFirstStream(DemuxerStream::VIDEO);
if (!video_stream) {
video_renderer_.reset();
task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK));
return;
}
current_video_stream_ = video_stream;
video_renderer_client_.reset(
new RendererClientInternal(DemuxerStream::VIDEO, this));
video_renderer_->Initialize(
video_stream, cdm_context_, video_renderer_client_.get(),
base::Bind(&RendererImpl::GetWallClockTimes, base::Unretained(this)),
done_cb);
}
void RendererImpl::OnVideoRendererInitializeDone(PipelineStatus status) {
DVLOG(1) << __func__ << ": " << status;
DCHECK(task_runner_->BelongsToCurrentThread());
// OnError() may be fired at any time by the renderers, even if they thought
// they initialized successfully (due to delayed output device setup).
if (state_ != STATE_INITIALIZING) {
DCHECK(init_cb_.is_null());
audio_renderer_.reset();
video_renderer_.reset();
return;
}
DCHECK(!init_cb_.is_null());
if (status != PIPELINE_OK) {
FinishInitialization(status);
return;
}
if (audio_renderer_) {
time_source_ = audio_renderer_->GetTimeSource();
} else if (!time_source_) {
wall_clock_time_source_.reset(new WallClockTimeSource());
time_source_ = wall_clock_time_source_.get();
}
state_ = STATE_FLUSHED;
DCHECK(time_source_);
DCHECK(audio_renderer_ || video_renderer_);
FinishInitialization(PIPELINE_OK);
}
void RendererImpl::FlushInternal() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_FLUSHING);
DCHECK(!flush_cb_.is_null());
if (time_ticking_)
PausePlayback();
FlushAudioRenderer();
}
// TODO(tmathmeyer) Combine this functionality with track switching flushing.
void RendererImpl::FlushAudioRenderer() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_FLUSHING);
DCHECK(!flush_cb_.is_null());
if (!audio_renderer_ || !audio_playing_) {
OnAudioRendererFlushDone();
} else {
audio_renderer_->Flush(base::BindRepeating(
&RendererImpl::OnAudioRendererFlushDone, weak_this_));
}
}
void RendererImpl::OnAudioRendererFlushDone() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ == STATE_ERROR) {
DCHECK(flush_cb_.is_null());
return;
}
DCHECK_EQ(state_, STATE_FLUSHING);
DCHECK(!flush_cb_.is_null());
// If we had a deferred video renderer underflow prior to the flush, it should
// have been cleared by the audio renderer changing to BUFFERING_HAVE_NOTHING.
DCHECK(deferred_video_underflow_cb_.IsCancelled());
DCHECK_EQ(audio_buffering_state_, BUFFERING_HAVE_NOTHING);
audio_ended_ = false;
audio_playing_ = false;
FlushVideoRenderer();
}
void RendererImpl::FlushVideoRenderer() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_FLUSHING);
DCHECK(!flush_cb_.is_null());
if (!video_renderer_ || !video_playing_) {
OnVideoRendererFlushDone();
} else {
video_renderer_->Flush(base::BindRepeating(
&RendererImpl::OnVideoRendererFlushDone, weak_this_));
}
}
void RendererImpl::OnVideoRendererFlushDone() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ == STATE_ERROR) {
DCHECK(flush_cb_.is_null());
return;
}
DCHECK_EQ(state_, STATE_FLUSHING);
DCHECK(!flush_cb_.is_null());
DCHECK_EQ(video_buffering_state_, BUFFERING_HAVE_NOTHING);
video_ended_ = false;
video_playing_ = false;
state_ = STATE_FLUSHED;
base::ResetAndReturn(&flush_cb_).Run();
}
void RendererImpl::ReinitializeAudioRenderer(
DemuxerStream* stream,
base::TimeDelta time,
base::OnceClosure reinitialize_completed_cb) {
DVLOG(2) << __func__ << " stream=" << stream << " time=" << time.InSecondsF();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_NE(stream, current_audio_stream_);
current_audio_stream_ = stream;
audio_renderer_->Initialize(
stream, cdm_context_, audio_renderer_client_.get(),
base::BindRepeating(&RendererImpl::OnAudioRendererReinitialized,
weak_this_, stream, time,
base::Passed(&reinitialize_completed_cb)));
}
void RendererImpl::OnAudioRendererReinitialized(
DemuxerStream* stream,
base::TimeDelta time,
base::OnceClosure reinitialize_completed_cb,
PipelineStatus status) {
DVLOG(2) << __func__ << ": status=" << status;
DCHECK_EQ(stream, current_audio_stream_);
if (status != PIPELINE_OK) {
std::move(reinitialize_completed_cb).Run();
OnError(status);
return;
}
RestartAudioRenderer(stream, time, std::move(reinitialize_completed_cb));
}
void RendererImpl::ReinitializeVideoRenderer(
DemuxerStream* stream,
base::TimeDelta time,
base::OnceClosure reinitialize_completed_cb) {
DVLOG(2) << __func__ << " stream=" << stream << " time=" << time.InSecondsF();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_NE(stream, current_video_stream_);
current_video_stream_ = stream;
video_renderer_->OnTimeStopped();
video_renderer_->Initialize(
stream, cdm_context_, video_renderer_client_.get(),
base::BindRepeating(&RendererImpl::GetWallClockTimes,
base::Unretained(this)),
base::BindRepeating(&RendererImpl::OnVideoRendererReinitialized,
weak_this_, stream, time,
base::Passed(&reinitialize_completed_cb)));
}
void RendererImpl::OnVideoRendererReinitialized(
DemuxerStream* stream,
base::TimeDelta time,
base::OnceClosure reinitialize_completed_cb,
PipelineStatus status) {
DVLOG(2) << __func__ << ": status=" << status;
DCHECK_EQ(stream, current_video_stream_);
if (status != PIPELINE_OK) {
std::move(reinitialize_completed_cb).Run();
OnError(status);
return;
}
RestartVideoRenderer(stream, time, std::move(reinitialize_completed_cb));
}
void RendererImpl::RestartAudioRenderer(
DemuxerStream* stream,
base::TimeDelta time,
base::OnceClosure restart_completed_cb) {
DVLOG(2) << __func__ << " stream=" << stream << " time=" << time.InSecondsF();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(audio_renderer_);
DCHECK_EQ(stream, current_audio_stream_);
DCHECK(state_ == STATE_PLAYING || state_ == STATE_FLUSHED ||
state_ == STATE_FLUSHING);
if (state_ == STATE_FLUSHED) {
// If we are in the FLUSHED state, then we are done. The audio renderer will
// be restarted by a subsequent RendererImpl::StartPlayingFrom call.
std::move(restart_completed_cb).Run();
return;
}
audio_renderer_->StartPlaying();
{
base::AutoLock lock(restarting_audio_lock_);
audio_playing_ = true;
pending_audio_track_change_ = false;
}
std::move(restart_completed_cb).Run();
}
void RendererImpl::RestartVideoRenderer(
DemuxerStream* stream,
base::TimeDelta time,
base::OnceClosure restart_completed_cb) {
DVLOG(2) << __func__ << " stream=" << stream << " time=" << time.InSecondsF();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(video_renderer_);
DCHECK_EQ(stream, current_video_stream_);
DCHECK(state_ == STATE_PLAYING || state_ == STATE_FLUSHED ||
state_ == STATE_FLUSHING);
if (state_ == STATE_FLUSHED) {
// If we are in the FLUSHED state, then we are done. The video renderer will
// be restarted by a subsequent RendererImpl::StartPlayingFrom call.
std::move(restart_completed_cb).Run();
return;
}
video_renderer_->StartPlayingFrom(time);
video_playing_ = true;
pending_video_track_change_ = false;
std::move(restart_completed_cb).Run();
}
void RendererImpl::OnStatisticsUpdate(const PipelineStatistics& stats) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_->OnStatisticsUpdate(stats);
}
void RendererImpl::OnBufferingStateChange(DemuxerStream::Type type,
BufferingState new_buffering_state) {
DCHECK((type == DemuxerStream::AUDIO) || (type == DemuxerStream::VIDEO));
BufferingState* buffering_state = type == DemuxerStream::AUDIO
? &audio_buffering_state_
: &video_buffering_state_;
DVLOG(1) << __func__ << (type == DemuxerStream::AUDIO ? " audio " : " video ")
<< MediaLog::BufferingStateToString(*buffering_state) << " -> "
<< MediaLog::BufferingStateToString(new_buffering_state);
DCHECK(task_runner_->BelongsToCurrentThread());
bool was_waiting_for_enough_data = WaitingForEnoughData();
if (new_buffering_state == BUFFERING_HAVE_NOTHING) {
if ((pending_audio_track_change_ && type == DemuxerStream::AUDIO) ||
(pending_video_track_change_ && type == DemuxerStream::VIDEO)) {
// Don't pass up a nothing event if it was triggered by a track change.
// This would cause the renderer to effectively lie about underflow state.
// Even though this might cause an immediate video underflow due to
// changing an audio track, all playing is paused when audio is disabled.
*buffering_state = new_buffering_state;
return;
}
}
// When audio is present and has enough data, defer video underflow callbacks
// for some time to avoid unnecessary glitches in audio; see
// http://crbug.com/144683#c53.
if (audio_renderer_ && type == DemuxerStream::VIDEO &&
state_ == STATE_PLAYING) {
if (video_buffering_state_ == BUFFERING_HAVE_ENOUGH &&
audio_buffering_state_ == BUFFERING_HAVE_ENOUGH &&
new_buffering_state == BUFFERING_HAVE_NOTHING &&
deferred_video_underflow_cb_.IsCancelled()) {
DVLOG(4) << __func__ << " Deferring HAVE_NOTHING for video stream.";
deferred_video_underflow_cb_.Reset(
base::Bind(&RendererImpl::OnBufferingStateChange, weak_this_, type,
new_buffering_state));
task_runner_->PostDelayedTask(FROM_HERE,
deferred_video_underflow_cb_.callback(),
video_underflow_threshold_);
return;
}
DVLOG(4) << "deferred_video_underflow_cb_.Cancel()";
deferred_video_underflow_cb_.Cancel();
} else if (!deferred_video_underflow_cb_.IsCancelled() &&
type == DemuxerStream::AUDIO &&
new_buffering_state == BUFFERING_HAVE_NOTHING) {
// If audio underflows while we have a deferred video underflow in progress
// we want to mark video as underflowed immediately and cancel the deferral.
deferred_video_underflow_cb_.Cancel();
video_buffering_state_ = BUFFERING_HAVE_NOTHING;
}
*buffering_state = new_buffering_state;
// Disable underflow by ignoring updates that renderers have ran out of data.
if (state_ == STATE_PLAYING && underflow_disabled_for_testing_ &&
time_ticking_) {
DVLOG(1) << "Update ignored because underflow is disabled for testing.";
return;
}
// Renderer underflowed.
if (!was_waiting_for_enough_data && WaitingForEnoughData()) {
PausePlayback();
client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
return;
}
// Renderer prerolled.
if (was_waiting_for_enough_data && !WaitingForEnoughData()) {
// Prevent condition where audio or video is sputtering and flipping back
// and forth between NOTHING and ENOUGH mixing with a track change, causing
// a StartPlayback to be called while the audio renderer is being flushed.
if (!pending_audio_track_change_ && !pending_video_track_change_) {
StartPlayback();
client_->OnBufferingStateChange(BUFFERING_HAVE_ENOUGH);
return;
}
}
}
bool RendererImpl::WaitingForEnoughData() const {
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ != STATE_PLAYING)
return false;
if (audio_renderer_ && audio_buffering_state_ != BUFFERING_HAVE_ENOUGH)
return true;
if (video_renderer_ && video_buffering_state_ != BUFFERING_HAVE_ENOUGH)
return true;
return false;
}
void RendererImpl::PausePlayback() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
switch (state_) {
case STATE_PLAYING:
DCHECK(PlaybackHasEnded() || WaitingForEnoughData() ||
pending_audio_track_change_)
<< "Playback should only pause due to ending or underflowing or"
" when restarting audio stream";
break;
case STATE_FLUSHING:
case STATE_FLUSHED:
// It's OK to pause playback when flushing.
break;
case STATE_UNINITIALIZED:
case STATE_INIT_PENDING_CDM:
case STATE_INITIALIZING:
NOTREACHED() << "Invalid state: " << state_;
break;
case STATE_ERROR:
// An error state may occur at any time.
break;
}
if (time_ticking_) {
time_ticking_ = false;
time_source_->StopTicking();
}
if (playback_rate_ > 0 && video_renderer_)
video_renderer_->OnTimeStopped();
}
void RendererImpl::StartPlayback() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, STATE_PLAYING);
DCHECK(!WaitingForEnoughData());
if (!time_ticking_) {
time_ticking_ = true;
audio_playing_ = true;
time_source_->StartTicking();
}
if (playback_rate_ > 0 && video_renderer_) {
video_playing_ = true;
video_renderer_->OnTimeProgressing();
}
}
void RendererImpl::OnRendererEnded(DemuxerStream::Type type) {
DVLOG(1) << __func__ << (type == DemuxerStream::AUDIO ? " audio" : " video");
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK((type == DemuxerStream::AUDIO) || (type == DemuxerStream::VIDEO));
if (state_ != STATE_PLAYING)
return;
if (type == DemuxerStream::AUDIO) {
DCHECK(!audio_ended_);
audio_ended_ = true;
} else {
DCHECK(!video_ended_);
DCHECK(video_renderer_);
video_ended_ = true;
video_renderer_->OnTimeStopped();
}
RunEndedCallbackIfNeeded();
}
bool RendererImpl::PlaybackHasEnded() const {
DCHECK(task_runner_->BelongsToCurrentThread());
if (audio_renderer_ && !audio_ended_)
return false;
if (video_renderer_ && !video_ended_)
return false;
return true;
}
void RendererImpl::RunEndedCallbackIfNeeded() {
DVLOG(1) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
if (!PlaybackHasEnded())
return;
if (time_ticking_)
PausePlayback();
client_->OnEnded();
}
void RendererImpl::OnError(PipelineStatus error) {
DVLOG(1) << __func__ << "(" << error << ")";
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
// An error has already been delivered.
if (state_ == STATE_ERROR)
return;
const State old_state = state_;
state_ = STATE_ERROR;
if (!init_cb_.is_null()) {
DCHECK(old_state == STATE_INITIALIZING ||
old_state == STATE_INIT_PENDING_CDM);
FinishInitialization(error);
return;
}
// After OnError() returns, the pipeline may destroy |this|.
client_->OnError(error);
if (!flush_cb_.is_null())
base::ResetAndReturn(&flush_cb_).Run();
}
void RendererImpl::OnWaitingForDecryptionKey() {
DCHECK(task_runner_->BelongsToCurrentThread());
client_->OnWaitingForDecryptionKey();
}
void RendererImpl::OnAudioConfigChange(const AudioDecoderConfig& config) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_->OnAudioConfigChange(config);
}
void RendererImpl::OnVideoConfigChange(const VideoDecoderConfig& config) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_->OnVideoConfigChange(config);
}
void RendererImpl::OnVideoNaturalSizeChange(const gfx::Size& size) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_->OnVideoNaturalSizeChange(size);
}
void RendererImpl::OnVideoOpacityChange(bool opaque) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_->OnVideoOpacityChange(opaque);
}
void RendererImpl::CleanUpTrackChange(base::RepeatingClosure on_finished,
bool* pending_change,
bool* ended,
bool* playing) {
{
// This lock is required for setting pending_audio_track_change_, and has
// no effect when setting pending_video_track_change_.
base::AutoLock lock(restarting_audio_lock_);
*pending_change = *ended = *playing = false;
}
std::move(on_finished).Run();
}
void RendererImpl::OnSelectedVideoTracksChanged(
const std::vector<DemuxerStream*>& enabled_tracks,
base::OnceClosure change_completed_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_LT(enabled_tracks.size(), 2u);
DemuxerStream* stream = enabled_tracks.empty() ? nullptr : enabled_tracks[0];
if (!stream && !video_playing_) {
std::move(change_completed_cb).Run();
return;
}
// 'fixing' the stream -> restarting if its the same stream,
// reinitializing if it is different.
base::RepeatingClosure fix_stream_cb;
if (stream && stream != current_video_stream_) {
fix_stream_cb = base::BindRepeating(
&RendererImpl::ReinitializeVideoRenderer, weak_this_, stream,
GetMediaTime(), base::Passed(&change_completed_cb));
} else {
fix_stream_cb = base::BindRepeating(
&RendererImpl::RestartVideoRenderer, weak_this_, current_video_stream_,
GetMediaTime(), base::Passed(&change_completed_cb));
}
pending_video_track_change_ = true;
video_renderer_->Flush(base::BindRepeating(
&RendererImpl::CleanUpTrackChange, weak_this_,
base::Passed(&fix_stream_cb), &pending_video_track_change_, &video_ended_,
&video_playing_));
}
void RendererImpl::OnEnabledAudioTracksChanged(
const std::vector<DemuxerStream*>& enabled_tracks,
base::OnceClosure change_completed_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_LT(enabled_tracks.size(), 2u);
DemuxerStream* stream = enabled_tracks.empty() ? nullptr : enabled_tracks[0];
if (!stream && !audio_playing_) {
std::move(change_completed_cb).Run();
return;
}
// 'fixing' the stream -> restarting if its the same stream,
// reinitializing if it is different.
base::RepeatingClosure fix_stream_cb;
if (stream && stream != current_audio_stream_) {
fix_stream_cb = base::BindRepeating(
&RendererImpl::ReinitializeAudioRenderer, weak_this_, stream,
GetMediaTime(), base::Passed(&change_completed_cb));
} else {
fix_stream_cb = base::BindRepeating(
&RendererImpl::RestartAudioRenderer, weak_this_, current_audio_stream_,
GetMediaTime(), base::Passed(&change_completed_cb));
}
{
base::AutoLock lock(restarting_audio_lock_);
pending_audio_track_change_ = true;
restarting_audio_time_ = time_source_->CurrentMediaTime();
}
if (audio_playing_)
PausePlayback();
audio_renderer_->Flush(base::BindRepeating(
&RendererImpl::CleanUpTrackChange, weak_this_,
base::Passed(&fix_stream_cb), &pending_audio_track_change_, &audio_ended_,
&audio_playing_));
}
} // namespace media