blob: 3b10cc2117ebf6e20f84e01c4cb951b238484df5 [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/base/pipeline_impl.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/cdm_context.h"
#include "media/base/decoder.h"
#include "media/base/demuxer.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/renderer.h"
#include "media/base/renderer_client.h"
#include "media/base/serial_runner.h"
#include "media/base/text_renderer.h"
#include "media/base/text_track_config.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_decoder_config.h"
static const double kDefaultPlaybackRate = 0.0;
static const float kDefaultVolume = 1.0f;
namespace media {
namespace {
gfx::Size GetRotatedVideoSize(VideoRotation rotation, gfx::Size natural_size) {
if (rotation == VIDEO_ROTATION_90 || rotation == VIDEO_ROTATION_270)
return gfx::Size(natural_size.height(), natural_size.width());
return natural_size;
}
} // namespace
// A wrapper of Renderer that runs on the |media_task_runner|.
// |default_renderer| in Start() and Resume() helps avoid a round trip to the
// render main task runner for Renderer creation in most cases which could add
// latency to start-to-play time.
class PipelineImpl::RendererWrapper final : public DemuxerHost,
public RendererClient {
public:
RendererWrapper(scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
MediaLog* media_log);
RendererWrapper(const RendererWrapper&) = delete;
RendererWrapper& operator=(const RendererWrapper&) = delete;
~RendererWrapper() final;
void Start(StartType start_type,
Demuxer* demuxer,
std::unique_ptr<Renderer> default_renderer,
base::WeakPtr<PipelineImpl> weak_pipeline);
void Stop();
void Seek(base::TimeDelta time);
void Suspend();
void Resume(std::unique_ptr<Renderer> default_renderer, base::TimeDelta time);
void SetPlaybackRate(double playback_rate);
void SetVolume(float volume);
void SetLatencyHint(absl::optional<base::TimeDelta> latency_hint);
void SetPreservesPitch(bool preserves_pitch);
void SetAutoplayInitiated(bool autoplay_initiated);
base::TimeDelta GetMediaTime() const;
Ranges<base::TimeDelta> GetBufferedTimeRanges() const;
bool DidLoadingProgress();
PipelineStatistics GetStatistics() const;
void SetCdm(CdmContext* cdm_context, CdmAttachedCB cdm_attached_cb);
// |enabled_track_ids| contains track ids of enabled audio tracks.
void OnEnabledAudioTracksChanged(
const std::vector<MediaTrack::Id>& enabled_track_ids,
base::OnceClosure change_completed_cb);
// |selected_track_id| is either empty, which means no video track is
// selected, or contains the selected video track id.
void OnSelectedVideoTrackChanged(
absl::optional<MediaTrack::Id> selected_track_id,
base::OnceClosure change_completed_cb);
private:
// Contains state shared between main and media thread. On the media thread
// each member can be read without locking, but writing requires locking. On
// the main thread reading requires a lock and writing is prohibited.
//
// This struct should only contain state that is not immediately needed by
// PipelineClient and can be cached on the media thread until queried.
// Alternatively we could cache it on the main thread by posting the
// notification to the main thread. But some of the state change notifications
// (OnStatisticsUpdate and OnBufferedTimeRangesChanged) arrive much more
// frequently than needed. Posting all those notifications to the main thread
// causes performance issues: crbug.com/619975.
struct SharedState {
// TODO(scherkus): Enforce that Renderer is only called on a single thread,
// even for accessing media time http://crbug.com/370634
//
// Note: Renderer implementations must support GetMediaTime() being called
// on both the main and media threads. RendererWrapper::GetMediaTime() calls
// it from the main thread (locked).
std::unique_ptr<Renderer> renderer;
// True when OnBufferedTimeRangesChanged() has been called more recently
// than DidLoadingProgress().
bool did_loading_progress = false;
// Amount of available buffered data as reported by Demuxer.
Ranges<base::TimeDelta> buffered_time_ranges;
// Accumulated statistics reported by the renderer.
PipelineStatistics statistics;
// The media timestamp to return while the pipeline is suspended.
// Otherwise set to kNoTimestamp.
base::TimeDelta suspend_timestamp = kNoTimestamp;
};
base::TimeDelta GetCurrentTimestamp();
void OnDemuxerCompletedTrackChange(
base::OnceClosure change_completed_cb,
DemuxerStream::Type stream_type,
const std::vector<DemuxerStream*>& streams);
// DemuxerHost implementaion.
void OnBufferedTimeRangesChanged(const Ranges<base::TimeDelta>& ranges) final;
void SetDuration(base::TimeDelta duration) final;
void OnDemuxerError(PipelineStatus error) final;
// RendererClient implementation.
void OnError(PipelineStatus error) final;
void OnEnded() final;
void OnStatisticsUpdate(const PipelineStatistics& stats) final;
void OnBufferingStateChange(BufferingState state,
BufferingStateChangeReason reason) final;
void OnWaiting(WaitingReason reason) final;
void OnAudioConfigChange(const AudioDecoderConfig& config) final;
void OnVideoConfigChange(const VideoDecoderConfig& config) final;
void OnVideoNaturalSizeChange(const gfx::Size& size) final;
void OnVideoOpacityChange(bool opaque) final;
void OnVideoFrameRateChange(absl::optional<int> fps) final;
// Common handlers for notifications from renderers and demuxer.
void OnPipelineError(PipelineStatus error);
void OnCdmAttached(CdmAttachedCB cdm_attached_cb,
CdmContext* cdm_context,
bool success);
void CheckPlaybackEnded();
// State transition tasks.
void SetState(State next_state);
void CompleteSeek(base::TimeDelta seek_time, PipelineStatus status);
void CompleteSuspend(PipelineStatus status);
void InitializeDemuxer(PipelineStatusCallback done_cb);
void CreateRenderer(PipelineStatusCallback done_cb);
void OnRendererCreated(PipelineStatusCallback done_cb,
std::unique_ptr<Renderer> renderer);
void InitializeRenderer(PipelineStatusCallback done_cb);
void DestroyRenderer();
void ReportMetadata(StartType start_type);
// Returns whether there's any encrypted stream in the demuxer.
bool HasEncryptedStream();
// Uses |default_renderer_| as the Renderer or asynchronously creates a new
// one by calling back to PipelineImpl. Fires |done_cb| with the result.
void CreateRendererInternal(PipelineStatusCallback done_cb);
const scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
MediaLog* const media_log_;
// A weak pointer to PipelineImpl. Must only use on the main task runner.
base::WeakPtr<PipelineImpl> weak_pipeline_;
Demuxer* demuxer_;
// Optional default renderer to be used during Start() and Resume(). If not
// available, or if a different Renderer is needed,
// PipelineImpl::AsyncCreateRenderer() will be called to create a new one.
std::unique_ptr<Renderer> default_renderer_;
double playback_rate_;
float volume_;
absl::optional<base::TimeDelta> latency_hint_;
CdmContext* cdm_context_;
// By default, apply pitch adjustments.
bool preserves_pitch_ = true;
bool autoplay_initiated_ = false;
// Lock used to serialize |shared_state_|.
// TODO(crbug.com/893739): Add GUARDED_BY annotations.
mutable base::Lock shared_state_lock_;
// State shared between main and media thread.
SharedState shared_state_;
// Current state of the pipeline.
State state_;
// Status of the pipeline. Initialized to PIPELINE_OK which indicates that
// the pipeline is operating correctly. Any other value indicates that the
// pipeline is stopped or is stopping. Clients can call the Stop() method to
// reset the pipeline state, and restore this to PIPELINE_OK.
PipelineStatus status_;
// Whether we've received the audio/video/text ended events.
bool renderer_ended_;
bool text_renderer_ended_;
// Series of tasks to Start(), Seek(), and Resume().
std::unique_ptr<SerialRunner> pending_callbacks_;
// Callback to store the |done_cb| when CreateRenderer() needs to wait for a
// CDM to be set. Should only be set in kStarting or kResuming states.
PipelineStatusCallback create_renderer_done_cb_;
// Called from non-media threads when an error occurs.
PipelineStatusCB error_cb_;
base::WeakPtrFactory<RendererWrapper> weak_factory_{this};
};
PipelineImpl::RendererWrapper::RendererWrapper(
scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
MediaLog* media_log)
: media_task_runner_(std::move(media_task_runner)),
main_task_runner_(std::move(main_task_runner)),
media_log_(media_log),
demuxer_(nullptr),
playback_rate_(kDefaultPlaybackRate),
volume_(kDefaultVolume),
cdm_context_(nullptr),
state_(kCreated),
status_(PIPELINE_OK),
renderer_ended_(false),
text_renderer_ended_(false) {}
PipelineImpl::RendererWrapper::~RendererWrapper() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(state_ == kCreated || state_ == kStopped);
}
// Note that the usage of base::Unretained() with the renderers is considered
// safe as they are owned by |pending_callbacks_| and share the same lifetime.
//
// That being said, deleting the renderers while keeping |pending_callbacks_|
// running on the media thread would result in crashes.
void PipelineImpl::RendererWrapper::Start(
StartType start_type,
Demuxer* demuxer,
std::unique_ptr<Renderer> default_renderer,
base::WeakPtr<PipelineImpl> weak_pipeline) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(state_ == kCreated || state_ == kStopped)
<< "Received start in unexpected state: " << state_;
DCHECK(!demuxer_);
DCHECK(!renderer_ended_);
DCHECK(!text_renderer_ended_);
SetState(kStarting);
demuxer_ = demuxer;
default_renderer_ = std::move(default_renderer);
weak_pipeline_ = weak_pipeline;
// Setup |error_cb_| on the media thread.
error_cb_ = base::BindRepeating(&RendererWrapper::OnPipelineError,
weak_factory_.GetWeakPtr());
// Queue asynchronous actions required to start.
DCHECK(!pending_callbacks_);
SerialRunner::Queue fns;
// Initialize demuxer.
fns.Push(base::BindOnce(&RendererWrapper::InitializeDemuxer,
weak_factory_.GetWeakPtr()));
// Once the demuxer is initialized successfully, media metadata must be
// available - report the metadata to client. If starting without a renderer
// we'll complete initialization at this point.
fns.Push(base::BindOnce(&RendererWrapper::ReportMetadata,
weak_factory_.GetWeakPtr(), start_type));
// Create renderer.
fns.Push(base::BindOnce(&RendererWrapper::CreateRenderer,
weak_factory_.GetWeakPtr()));
// Initialize renderer.
fns.Push(base::BindOnce(&RendererWrapper::InitializeRenderer,
weak_factory_.GetWeakPtr()));
// Run tasks.
pending_callbacks_ = SerialRunner::Run(
std::move(fns),
base::BindOnce(&RendererWrapper::CompleteSeek, weak_factory_.GetWeakPtr(),
base::TimeDelta()));
}
void PipelineImpl::RendererWrapper::Stop() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(state_ != kStopping && state_ != kStopped);
SetState(kStopping);
if (shared_state_.statistics.video_frames_decoded > 0) {
UMA_HISTOGRAM_COUNTS_1M("Media.DroppedFrameCount",
shared_state_.statistics.video_frames_dropped);
}
// If we stop during starting/seeking/suspending/resuming we don't want to
// leave outstanding callbacks around. The callbacks also do not get run if
// the pipeline is stopped before it had a chance to complete outstanding
// tasks.
pending_callbacks_.reset();
weak_factory_.InvalidateWeakPtrs();
DestroyRenderer();
if (demuxer_) {
demuxer_->Stop();
demuxer_ = nullptr;
}
SetState(kStopped);
// Reset the status. Otherwise, if we encountered an error, new errors will
// never be propagated. See https://crbug.com/812465.
status_ = PIPELINE_OK;
}
void PipelineImpl::RendererWrapper::Seek(base::TimeDelta time) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
// Suppress seeking if we're not fully started.
if (state_ != kPlaying) {
DCHECK(state_ == kStopping || state_ == kStopped)
<< "Receive seek in unexpected state: " << state_;
OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
return;
}
base::TimeDelta seek_timestamp = std::max(time, demuxer_->GetStartTime());
SetState(kSeeking);
renderer_ended_ = false;
text_renderer_ended_ = false;
// Queue asynchronous actions required to start.
DCHECK(!pending_callbacks_);
SerialRunner::Queue bound_fns;
// Abort any reads the renderer may be blocked on.
demuxer_->AbortPendingReads();
// Flush.
DCHECK(shared_state_.renderer);
bound_fns.Push(base::BindOnce(
&Renderer::Flush, base::Unretained(shared_state_.renderer.get())));
// Seek demuxer.
bound_fns.Push(base::BindOnce(&Demuxer::Seek, base::Unretained(demuxer_),
seek_timestamp));
// Run tasks.
pending_callbacks_ = SerialRunner::Run(
std::move(bound_fns),
base::BindOnce(&RendererWrapper::CompleteSeek, weak_factory_.GetWeakPtr(),
seek_timestamp));
}
void PipelineImpl::RendererWrapper::Suspend() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
// Suppress suspending if we're not playing.
if (state_ != kPlaying) {
DCHECK(state_ == kStopping || state_ == kStopped)
<< "Receive suspend in unexpected state: " << state_;
OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
return;
}
DCHECK(!pending_callbacks_.get());
SetState(kSuspending);
// Freeze playback and record the media time before destroying the renderer.
shared_state_.renderer->SetPlaybackRate(0.0);
{
base::AutoLock auto_lock(shared_state_lock_);
DCHECK(shared_state_.renderer);
shared_state_.suspend_timestamp = shared_state_.renderer->GetMediaTime();
DCHECK(shared_state_.suspend_timestamp != kNoTimestamp);
}
// Queue the asynchronous actions required to stop playback.
SerialRunner::Queue fns;
// No need to flush the renderer since it's going to be destroyed.
pending_callbacks_ = SerialRunner::Run(
std::move(fns), base::BindOnce(&RendererWrapper::CompleteSuspend,
weak_factory_.GetWeakPtr()));
}
void PipelineImpl::RendererWrapper::Resume(
std::unique_ptr<Renderer> default_renderer,
base::TimeDelta timestamp) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
// Suppress resuming if we're not suspended.
if (state_ != kSuspended) {
DCHECK(state_ == kStopping || state_ == kStopped)
<< "Receive resume in unexpected state: " << state_;
OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
return;
}
DCHECK(!pending_callbacks_.get());
SetState(kResuming);
{
base::AutoLock auto_lock(shared_state_lock_);
DCHECK(!shared_state_.renderer);
}
default_renderer_ = std::move(default_renderer);
renderer_ended_ = false;
text_renderer_ended_ = false;
base::TimeDelta start_timestamp =
std::max(timestamp, demuxer_->GetStartTime());
// Queue the asynchronous actions required to start playback.
SerialRunner::Queue fns;
fns.Push(base::BindOnce(&Demuxer::Seek, base::Unretained(demuxer_),
start_timestamp));
fns.Push(base::BindOnce(&RendererWrapper::CreateRenderer,
weak_factory_.GetWeakPtr()));
fns.Push(base::BindOnce(&RendererWrapper::InitializeRenderer,
weak_factory_.GetWeakPtr()));
pending_callbacks_ = SerialRunner::Run(
std::move(fns),
base::BindOnce(&RendererWrapper::CompleteSeek, weak_factory_.GetWeakPtr(),
start_timestamp));
}
void PipelineImpl::RendererWrapper::SetPlaybackRate(double playback_rate) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
playback_rate_ = playback_rate;
if (state_ == kPlaying)
shared_state_.renderer->SetPlaybackRate(playback_rate_);
}
void PipelineImpl::RendererWrapper::SetVolume(float volume) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
volume_ = volume;
if (shared_state_.renderer)
shared_state_.renderer->SetVolume(volume_);
}
void PipelineImpl::RendererWrapper::SetLatencyHint(
absl::optional<base::TimeDelta> latency_hint) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (latency_hint_ == latency_hint)
return;
latency_hint_ = latency_hint;
if (shared_state_.renderer)
shared_state_.renderer->SetLatencyHint(latency_hint_);
}
void PipelineImpl::RendererWrapper::SetPreservesPitch(bool preserves_pitch) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (preserves_pitch_ == preserves_pitch)
return;
preserves_pitch_ = preserves_pitch;
if (shared_state_.renderer)
shared_state_.renderer->SetPreservesPitch(preserves_pitch_);
}
void PipelineImpl::RendererWrapper::SetAutoplayInitiated(
bool autoplay_initiated) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (autoplay_initiated_ == autoplay_initiated)
return;
autoplay_initiated_ = autoplay_initiated;
if (shared_state_.renderer)
shared_state_.renderer->SetAutoplayInitiated(autoplay_initiated_);
}
base::TimeDelta PipelineImpl::RendererWrapper::GetMediaTime() const {
DCHECK(main_task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(shared_state_lock_);
if (shared_state_.suspend_timestamp != kNoTimestamp)
return shared_state_.suspend_timestamp;
return shared_state_.renderer ? shared_state_.renderer->GetMediaTime()
: base::TimeDelta();
}
Ranges<base::TimeDelta> PipelineImpl::RendererWrapper::GetBufferedTimeRanges()
const {
DCHECK(main_task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(shared_state_lock_);
return shared_state_.buffered_time_ranges;
}
bool PipelineImpl::RendererWrapper::DidLoadingProgress() {
DCHECK(main_task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(shared_state_lock_);
bool did_progress = shared_state_.did_loading_progress;
shared_state_.did_loading_progress = false;
return did_progress;
}
PipelineStatistics PipelineImpl::RendererWrapper::GetStatistics() const {
DCHECK(main_task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(shared_state_lock_);
return shared_state_.statistics;
}
void PipelineImpl::RendererWrapper::SetCdm(CdmContext* cdm_context,
CdmAttachedCB cdm_attached_cb) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(cdm_context);
// If there's already a renderer, set the CDM on the renderer directly.
if (shared_state_.renderer) {
shared_state_.renderer->SetCdm(
cdm_context, base::BindOnce(&RendererWrapper::OnCdmAttached,
weak_factory_.GetWeakPtr(),
std::move(cdm_attached_cb), cdm_context));
return;
}
// Otherwise, wait for the Renderer to be created and the CDM will be set
// in InitializeRenderer().
cdm_context_ = cdm_context;
std::move(cdm_attached_cb).Run(true);
// Continue Renderer creation if it's waiting for the CDM to be set.
if (create_renderer_done_cb_)
CreateRendererInternal(std::move(create_renderer_done_cb_));
}
void PipelineImpl::RendererWrapper::CreateRendererInternal(
PipelineStatusCallback done_cb) {
DVLOG(1) << __func__;
DCHECK(state_ == kStarting || state_ == kResuming);
DCHECK(cdm_context_ || !HasEncryptedStream())
<< "CDM should be available now if has encrypted stream";
absl::optional<RendererType> renderer_type;
#if defined(OS_WIN)
if (cdm_context_ && cdm_context_->RequiresMediaFoundationRenderer())
renderer_type = RendererType::kMediaFoundation;
#endif // defined(OS_WIN)
// TODO(xhwang): During Resume(), the |default_renderer_| might already match
// the |renderer_type|, in which case we shouldn't need to create a new one.
if (!default_renderer_ || renderer_type) {
// Create the Renderer asynchronously on the main task runner. Use
// BindToCurrentLoop to call OnRendererCreated() on the media task runner.
auto renderer_created_cb = BindToCurrentLoop(
base::BindOnce(&RendererWrapper::OnRendererCreated,
weak_factory_.GetWeakPtr(), std::move(done_cb)));
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PipelineImpl::AsyncCreateRenderer, weak_pipeline_,
renderer_type, std::move(renderer_created_cb)));
return;
}
// Just use the default one.
OnRendererCreated(std::move(done_cb), std::move(default_renderer_));
}
void PipelineImpl::RendererWrapper::OnBufferedTimeRangesChanged(
const Ranges<base::TimeDelta>& ranges) {
// TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
// implementations call DemuxerHost on the media thread.
base::AutoLock auto_lock(shared_state_lock_);
shared_state_.did_loading_progress = true;
shared_state_.buffered_time_ranges = ranges;
}
void PipelineImpl::RendererWrapper::SetDuration(base::TimeDelta duration) {
// TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
// implementations call DemuxerHost on the media thread.
media_log_->AddEvent<MediaLogEvent::kDurationChanged>(duration);
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnDurationChange, weak_pipeline_,
duration));
}
void PipelineImpl::RendererWrapper::OnDemuxerError(PipelineStatus error) {
// TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
// implementations call DemuxerHost on the media thread.
DCHECK(error_cb_);
media_task_runner_->PostTask(FROM_HERE, base::BindOnce(error_cb_, error));
}
void PipelineImpl::RendererWrapper::OnError(PipelineStatus error) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(error_cb_);
media_task_runner_->PostTask(FROM_HERE, base::BindOnce(error_cb_, error));
}
void PipelineImpl::RendererWrapper::OnEnded() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
media_log_->AddEvent<MediaLogEvent::kEnded>();
if (state_ != kPlaying)
return;
DCHECK(!renderer_ended_);
renderer_ended_ = true;
CheckPlaybackEnded();
}
// TODO(crbug/817089): Combine this functionality into renderer->GetMediaTime().
base::TimeDelta PipelineImpl::RendererWrapper::GetCurrentTimestamp() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(demuxer_);
DCHECK(shared_state_.renderer || state_ != kPlaying);
return state_ == kPlaying ? shared_state_.renderer->GetMediaTime()
: demuxer_->GetStartTime();
}
void PipelineImpl::OnEnabledAudioTracksChanged(
const std::vector<MediaTrack::Id>& enabled_track_ids,
base::OnceClosure change_completed_cb) {
DCHECK(thread_checker_.CalledOnValidThread());
media_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RendererWrapper::OnEnabledAudioTracksChanged,
base::Unretained(renderer_wrapper_.get()),
enabled_track_ids,
BindToCurrentLoop(std::move(change_completed_cb))));
}
void PipelineImpl::RendererWrapper::OnEnabledAudioTracksChanged(
const std::vector<MediaTrack::Id>& enabled_track_ids,
base::OnceClosure change_completed_cb) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
// If the pipeline has been created, but not started yet, we may still receive
// track notifications from blink level (e.g. when video track gets deselected
// due to player/pipeline belonging to a background tab). We can safely ignore
// these, since WebMediaPlayerImpl will ensure that demuxer stream / track
// status is in sync with blink after pipeline is started.
if (state_ == kCreated) {
DCHECK(!demuxer_);
std::move(change_completed_cb).Run();
return;
}
// Track status notifications might be delivered asynchronously. If we receive
// a notification when pipeline is stopped/shut down, it's safe to ignore it.
if (state_ == kStopping || state_ == kStopped) {
std::move(change_completed_cb).Run();
return;
}
demuxer_->OnEnabledAudioTracksChanged(
enabled_track_ids, GetCurrentTimestamp(),
base::BindOnce(&RendererWrapper::OnDemuxerCompletedTrackChange,
weak_factory_.GetWeakPtr(),
std::move(change_completed_cb)));
}
void PipelineImpl::OnSelectedVideoTrackChanged(
absl::optional<MediaTrack::Id> selected_track_id,
base::OnceClosure change_completed_cb) {
DCHECK(thread_checker_.CalledOnValidThread());
media_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RendererWrapper::OnSelectedVideoTrackChanged,
base::Unretained(renderer_wrapper_.get()),
selected_track_id,
BindToCurrentLoop(std::move(change_completed_cb))));
}
void PipelineImpl::RendererWrapper::OnSelectedVideoTrackChanged(
absl::optional<MediaTrack::Id> selected_track_id,
base::OnceClosure change_completed_cb) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
// See RenderWrapper::OnEnabledAudioTracksChanged.
if (state_ == kCreated) {
DCHECK(!demuxer_);
std::move(change_completed_cb).Run();
return;
}
if (state_ == kStopping || state_ == kStopped) {
std::move(change_completed_cb).Run();
return;
}
std::vector<MediaTrack::Id> tracks;
if (selected_track_id)
tracks.push_back(*selected_track_id);
demuxer_->OnSelectedVideoTrackChanged(
tracks, GetCurrentTimestamp(),
base::BindOnce(&RendererWrapper::OnDemuxerCompletedTrackChange,
weak_factory_.GetWeakPtr(),
std::move(change_completed_cb)));
}
void PipelineImpl::RendererWrapper::OnDemuxerCompletedTrackChange(
base::OnceClosure change_completed_cb,
DemuxerStream::Type stream_type,
const std::vector<DemuxerStream*>& streams) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (!shared_state_.renderer) {
// This can happen if the pipeline has been suspended.
std::move(change_completed_cb).Run();
return;
}
switch (stream_type) {
case DemuxerStream::AUDIO:
shared_state_.renderer->OnEnabledAudioTracksChanged(
streams, std::move(change_completed_cb));
break;
case DemuxerStream::VIDEO:
shared_state_.renderer->OnSelectedVideoTracksChanged(
streams, std::move(change_completed_cb));
break;
// TODO(tmathmeyer): Look into text track switching.
case DemuxerStream::TEXT:
case DemuxerStream::UNKNOWN: // Fail on unknown type.
NOTREACHED();
}
}
void PipelineImpl::RendererWrapper::OnStatisticsUpdate(
const PipelineStatistics& stats) {
DVLOG(3) << __func__;
DCHECK(media_task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(shared_state_lock_);
shared_state_.statistics.audio_bytes_decoded += stats.audio_bytes_decoded;
shared_state_.statistics.video_bytes_decoded += stats.video_bytes_decoded;
shared_state_.statistics.video_frames_decoded += stats.video_frames_decoded;
shared_state_.statistics.video_frames_decoded_power_efficient +=
stats.video_frames_decoded_power_efficient;
shared_state_.statistics.video_frames_dropped += stats.video_frames_dropped;
shared_state_.statistics.audio_memory_usage += stats.audio_memory_usage;
shared_state_.statistics.video_memory_usage += stats.video_memory_usage;
if (stats.audio_pipeline_info.decoder_type != AudioDecoderType::kUnknown &&
stats.audio_pipeline_info !=
shared_state_.statistics.audio_pipeline_info) {
shared_state_.statistics.audio_pipeline_info = stats.audio_pipeline_info;
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnAudioPipelineInfoChange,
weak_pipeline_, stats.audio_pipeline_info));
}
if (stats.video_pipeline_info.decoder_type != VideoDecoderType::kUnknown &&
stats.video_pipeline_info !=
shared_state_.statistics.video_pipeline_info) {
shared_state_.statistics.video_pipeline_info = stats.video_pipeline_info;
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnVideoPipelineInfoChange,
weak_pipeline_, stats.video_pipeline_info));
}
if (stats.video_frame_duration_average != kNoTimestamp) {
shared_state_.statistics.video_frame_duration_average =
stats.video_frame_duration_average;
}
base::TimeDelta old_key_frame_distance_average =
shared_state_.statistics.video_keyframe_distance_average;
if (stats.video_keyframe_distance_average != kNoTimestamp) {
shared_state_.statistics.video_keyframe_distance_average =
stats.video_keyframe_distance_average;
}
if (shared_state_.statistics.video_keyframe_distance_average !=
old_key_frame_distance_average) {
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PipelineImpl::OnVideoAverageKeyframeDistanceUpdate,
weak_pipeline_));
}
}
void PipelineImpl::RendererWrapper::OnBufferingStateChange(
BufferingState state,
BufferingStateChangeReason reason) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DVLOG(2) << __func__ << "(" << state << ", " << reason << ") ";
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnBufferingStateChange,
weak_pipeline_, state, reason));
}
void PipelineImpl::RendererWrapper::OnWaiting(WaitingReason reason) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PipelineImpl::OnWaiting, weak_pipeline_, reason));
}
void PipelineImpl::RendererWrapper::OnVideoNaturalSizeChange(
const gfx::Size& size) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnVideoNaturalSizeChange,
weak_pipeline_, size));
}
void PipelineImpl::RendererWrapper::OnVideoOpacityChange(bool opaque) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnVideoOpacityChange,
weak_pipeline_, opaque));
}
void PipelineImpl::RendererWrapper::OnVideoFrameRateChange(
absl::optional<int> fps) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnVideoFrameRateChange,
weak_pipeline_, fps));
}
void PipelineImpl::RendererWrapper::OnAudioConfigChange(
const AudioDecoderConfig& config) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
main_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&PipelineImpl::OnAudioConfigChange,
weak_pipeline_, config));
}
void PipelineImpl::RendererWrapper::OnVideoConfigChange(
const VideoDecoderConfig& config) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
main_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&PipelineImpl::OnVideoConfigChange,
weak_pipeline_, config));
}
void PipelineImpl::RendererWrapper::OnPipelineError(PipelineStatus error) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
// Preserve existing abnormal status.
if (status_ != PIPELINE_OK)
return;
// If the pipeline is already stopping or stopped we don't need to report an
// error. Similarly if the pipeline is suspending or suspended, the error may
// be recoverable, so don't propagate it now, instead let the subsequent seek
// during resume propagate it if it's unrecoverable.
if (state_ == kStopping || state_ == kStopped || state_ == kSuspending ||
state_ == kSuspended) {
return;
}
status_ = error;
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnError, weak_pipeline_, error));
}
void PipelineImpl::RendererWrapper::OnCdmAttached(CdmAttachedCB cdm_attached_cb,
CdmContext* cdm_context,
bool success) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (success)
cdm_context_ = cdm_context;
std::move(cdm_attached_cb).Run(success);
}
void PipelineImpl::RendererWrapper::CheckPlaybackEnded() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (shared_state_.renderer && !renderer_ended_)
return;
// Don't fire an ended event if we're already in an error state.
if (status_ != PIPELINE_OK)
return;
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnEnded, weak_pipeline_));
}
void PipelineImpl::RendererWrapper::SetState(State next_state) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
DVLOG(1) << PipelineImpl::GetStateString(state_) << " -> "
<< PipelineImpl::GetStateString(next_state);
state_ = next_state;
// TODO(tmathmeyer) Make State serializable so GetStateString won't need
// to be called here.
media_log_->AddEvent<MediaLogEvent::kPipelineStateChange>(
std::string(PipelineImpl::GetStateString(next_state)));
}
void PipelineImpl::RendererWrapper::CompleteSeek(base::TimeDelta seek_time,
PipelineStatus status) {
DVLOG(1) << __func__ << ": seek_time=" << seek_time << ", status=" << status;
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(state_ == kStarting || state_ == kSeeking || state_ == kResuming);
if (state_ == kStarting) {
UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.Start", status,
PIPELINE_STATUS_MAX + 1);
}
DCHECK(pending_callbacks_);
pending_callbacks_.reset();
if (status != PIPELINE_OK) {
OnPipelineError(status);
return;
}
shared_state_.renderer->StartPlayingFrom(
std::max(seek_time, demuxer_->GetStartTime()));
{
base::AutoLock auto_lock(shared_state_lock_);
shared_state_.suspend_timestamp = kNoTimestamp;
}
shared_state_.renderer->SetPlaybackRate(playback_rate_);
SetState(kPlaying);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PipelineImpl::OnSeekDone, weak_pipeline_, false));
}
void PipelineImpl::RendererWrapper::CompleteSuspend(PipelineStatus status) {
DVLOG(1) << __func__ << ": status=" << status;
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK_EQ(kSuspending, state_);
DCHECK(pending_callbacks_);
pending_callbacks_.reset();
// In case we are suspending or suspended, the error may be recoverable,
// so don't propagate it now, instead let the subsequent seek during resume
// propagate it if it's unrecoverable.
LOG_IF(WARNING, status != PIPELINE_OK)
<< "Encountered pipeline error while suspending: " << status;
DestroyRenderer();
{
base::AutoLock auto_lock(shared_state_lock_);
shared_state_.statistics.audio_memory_usage = 0;
shared_state_.statistics.video_memory_usage = 0;
}
// Abort any reads the renderer may have kicked off.
demuxer_->AbortPendingReads();
SetState(kSuspended);
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PipelineImpl::OnSuspendDone, weak_pipeline_));
}
void PipelineImpl::RendererWrapper::InitializeDemuxer(
PipelineStatusCallback done_cb) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
demuxer_->Initialize(this, std::move(done_cb));
}
void PipelineImpl::RendererWrapper::CreateRenderer(
PipelineStatusCallback done_cb) {
DVLOG(1) << __func__;
DCHECK(media_task_runner_->BelongsToCurrentThread());
DCHECK(state_ == kStarting || state_ == kResuming);
if (HasEncryptedStream() && !cdm_context_) {
DVLOG(1) << __func__ << ": Has encrypted stream but CDM is not set.";
create_renderer_done_cb_ = std::move(done_cb);
OnWaiting(WaitingReason::kNoCdm);
return;
}
CreateRendererInternal(std::move(done_cb));
}
void PipelineImpl::RendererWrapper::OnRendererCreated(
PipelineStatusCallback done_cb,
std::unique_ptr<Renderer> renderer) {
DVLOG(1) << __func__ << ": renderer=" << renderer.get();
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (!renderer) {
std::move(done_cb).Run(PIPELINE_ERROR_INITIALIZATION_FAILED);
return;
}
{
base::AutoLock auto_lock(shared_state_lock_);
DCHECK(!shared_state_.renderer);
shared_state_.renderer = std::move(renderer);
}
std::move(done_cb).Run(PIPELINE_OK);
}
void PipelineImpl::RendererWrapper::InitializeRenderer(
PipelineStatusCallback done_cb) {
DVLOG(1) << __func__;
DCHECK(media_task_runner_->BelongsToCurrentThread());
switch (demuxer_->GetType()) {
case MediaResource::Type::STREAM:
if (demuxer_->GetAllStreams().empty()) {
DVLOG(1) << "Error: demuxer does not have an audio or a video stream.";
std::move(done_cb).Run(PIPELINE_ERROR_COULD_NOT_RENDER);
return;
}
break;
case MediaResource::Type::URL:
// NOTE: Empty GURL are not valid.
if (!demuxer_->GetMediaUrlParams().media_url.is_valid()) {
DVLOG(1) << "Error: demuxer does not have a valid URL.";
std::move(done_cb).Run(PIPELINE_ERROR_COULD_NOT_RENDER);
return;
}
break;
}
if (cdm_context_)
shared_state_.renderer->SetCdm(cdm_context_, base::DoNothing());
if (latency_hint_)
shared_state_.renderer->SetLatencyHint(latency_hint_);
shared_state_.renderer->SetPreservesPitch(preserves_pitch_);
// Calling SetVolume() before Initialize() allows renderers to optimize for
// power by avoiding initialization of audio output until necessary.
shared_state_.renderer->SetVolume(volume_);
shared_state_.renderer->Initialize(demuxer_, this, std::move(done_cb));
}
void PipelineImpl::RendererWrapper::DestroyRenderer() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
// Destroy the renderer outside the lock scope to avoid holding the lock
// while renderer is being destroyed (in case Renderer destructor is costly).
std::unique_ptr<Renderer> renderer;
{
base::AutoLock auto_lock(shared_state_lock_);
renderer.swap(shared_state_.renderer);
}
}
void PipelineImpl::RendererWrapper::ReportMetadata(StartType start_type) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
PipelineMetadata metadata;
std::vector<DemuxerStream*> streams;
switch (demuxer_->GetType()) {
case MediaResource::Type::STREAM:
metadata.timeline_offset = demuxer_->GetTimelineOffset();
// TODO(servolk): What should we do about metadata for multiple streams?
streams = demuxer_->GetAllStreams();
for (auto* stream : streams) {
if (stream->type() == DemuxerStream::VIDEO && !metadata.has_video) {
metadata.has_video = true;
metadata.natural_size = GetRotatedVideoSize(
stream->video_decoder_config().video_transformation().rotation,
stream->video_decoder_config().natural_size());
metadata.video_decoder_config = stream->video_decoder_config();
}
if (stream->type() == DemuxerStream::AUDIO && !metadata.has_audio) {
metadata.has_audio = true;
metadata.audio_decoder_config = stream->audio_decoder_config();
}
}
break;
case MediaResource::Type::URL:
// We don't know if the MediaPlayerRender has Audio/Video until we start
// playing. Conservatively assume that they do.
metadata.has_video = true;
metadata.has_audio = true;
break;
}
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PipelineImpl::OnMetadata, weak_pipeline_, metadata));
// If suspended start has not been requested, or is not allowed given the
// metadata, continue the normal renderer initialization path.
if (start_type == StartType::kNormal ||
(start_type == StartType::kSuspendAfterMetadataForAudioOnly &&
metadata.has_video)) {
return;
}
// Abort pending render initialization tasks and suspend the pipeline.
pending_callbacks_.reset();
DestroyRenderer();
shared_state_.suspend_timestamp =
std::max(base::TimeDelta(), demuxer_->GetStartTime());
SetState(kSuspended);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PipelineImpl::OnSeekDone, weak_pipeline_, true));
}
bool PipelineImpl::RendererWrapper::HasEncryptedStream() {
// Encrypted streams are only handled explicitly for STREAM type.
if (demuxer_->GetType() != MediaResource::Type::STREAM)
return false;
auto streams = demuxer_->GetAllStreams();
for (auto* stream : 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;
}
PipelineImpl::PipelineImpl(
scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
CreateRendererCB create_renderer_cb,
MediaLog* media_log)
: media_task_runner_(media_task_runner),
create_renderer_cb_(create_renderer_cb),
media_log_(media_log),
client_(nullptr),
playback_rate_(kDefaultPlaybackRate),
volume_(kDefaultVolume),
is_suspended_(false) {
DVLOG(2) << __func__;
DCHECK(create_renderer_cb_);
renderer_wrapper_ = std::make_unique<RendererWrapper>(
media_task_runner_, std::move(main_task_runner), media_log_);
}
PipelineImpl::~PipelineImpl() {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!client_) << "Stop() must complete before destroying object";
DCHECK(!seek_cb_);
DCHECK(!suspend_cb_);
DCHECK(!weak_factory_.HasWeakPtrs())
<< "Stop() should have invalidated all weak pointers";
// RendererWrapper is deleted on the media thread.
media_task_runner_->DeleteSoon(FROM_HERE, renderer_wrapper_.release());
}
void PipelineImpl::Start(StartType start_type,
Demuxer* demuxer,
Client* client,
PipelineStatusCallback seek_cb) {
DVLOG(2) << __func__ << ": start_type=" << static_cast<int>(start_type);
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(demuxer);
DCHECK(client);
DCHECK(seek_cb);
DCHECK(!client_);
DCHECK(!seek_cb_);
client_ = client;
seek_cb_ = std::move(seek_cb);
last_media_time_ = base::TimeDelta();
seek_time_ = kNoTimestamp;
// By default, create a default renderer to avoid additional start-to-play
// latency caused by asynchronous Renderer creation. When |start_type| is
// kSuspendAfterMetadata, latency is not important and the video may never
// play. In this case, not creating a default renderer to reduce memory usage.
std::unique_ptr<Renderer> default_renderer;
if (start_type != StartType::kSuspendAfterMetadata)
default_renderer = create_renderer_cb_.Run(absl::nullopt);
media_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RendererWrapper::Start,
base::Unretained(renderer_wrapper_.get()), start_type,
demuxer, std::move(default_renderer),
weak_factory_.GetWeakPtr()));
}
void PipelineImpl::Stop() {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
if (!IsRunning()) {
DVLOG(2) << "Media pipeline isn't running. Ignoring Stop()";
return;
}
if (media_task_runner_->BelongsToCurrentThread()) {
// This path is executed by unittests that share media and main threads.
renderer_wrapper_->Stop();
} else {
// This path is executed by production code where the two task runners -
// main and media - live on different threads.
media_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RendererWrapper::Stop,
base::Unretained(renderer_wrapper_.get())));
}
// Once the pipeline is stopped, nothing is reported back to the client.
// Reset all callbacks and client handle.
seek_cb_.Reset();
suspend_cb_.Reset();
client_ = nullptr;
// Invalidate self weak pointers effectively canceling all pending
// notifications in the message queue.
weak_factory_.InvalidateWeakPtrs();
}
void PipelineImpl::Seek(base::TimeDelta time, PipelineStatusCallback seek_cb) {
DVLOG(2) << __func__ << " to " << time;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(seek_cb);
if (!IsRunning()) {
DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek().";
std::move(seek_cb).Run(PIPELINE_ERROR_INVALID_STATE);
return;
}
DCHECK(!seek_cb_);
seek_cb_ = std::move(seek_cb);
seek_time_ = time;
last_media_time_ = base::TimeDelta();
media_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RendererWrapper::Seek,
base::Unretained(renderer_wrapper_.get()), time));
}
void PipelineImpl::Suspend(PipelineStatusCallback suspend_cb) {
DVLOG(2) << __func__;
DCHECK(suspend_cb);
DCHECK(IsRunning());
DCHECK(!suspend_cb_);
suspend_cb_ = std::move(suspend_cb);
media_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RendererWrapper::Suspend,
base::Unretained(renderer_wrapper_.get())));
}
void PipelineImpl::Resume(base::TimeDelta time,
PipelineStatusCallback seek_cb) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(seek_cb);
DCHECK(IsRunning());
DCHECK(!seek_cb_);
seek_cb_ = std::move(seek_cb);
seek_time_ = time;
last_media_time_ = base::TimeDelta();
// Always create a default renderer for Resume().
auto default_renderer = create_renderer_cb_.Run(absl::nullopt);
media_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RendererWrapper::Resume,
base::Unretained(renderer_wrapper_.get()),
std::move(default_renderer), time));
}
bool PipelineImpl::IsRunning() const {
DCHECK(thread_checker_.CalledOnValidThread());
return !!client_;
}
bool PipelineImpl::IsSuspended() const {
DVLOG(2) << __func__ << "(" << is_suspended_ << ")";
DCHECK(thread_checker_.CalledOnValidThread());
return is_suspended_;
}
double PipelineImpl::GetPlaybackRate() const {
DCHECK(thread_checker_.CalledOnValidThread());
return playback_rate_;
}
void PipelineImpl::SetPlaybackRate(double playback_rate) {
DVLOG(2) << __func__ << "(" << playback_rate << ")";
DCHECK(thread_checker_.CalledOnValidThread());
// Not checking IsRunning() so we can set the playback rate before Start().
if (playback_rate < 0.0) {
DVLOG(1) << __func__ << ": Invalid playback rate " << playback_rate;
return;
}
playback_rate_ = playback_rate;
media_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RendererWrapper::SetPlaybackRate,
base::Unretained(renderer_wrapper_.get()),
playback_rate_));
}
float PipelineImpl::GetVolume() const {
DCHECK(thread_checker_.CalledOnValidThread());
return volume_;
}
void PipelineImpl::SetVolume(float volume) {
DVLOG(2) << __func__ << "(" << volume << ")";
DCHECK(thread_checker_.CalledOnValidThread());
// Not checking IsRunning() so we can set the volume before Start().
if (volume < 0.0f) {
DVLOG(1) << __func__ << ": Invalid volume " << volume;
return;
}
volume_ = volume;
media_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RendererWrapper::SetVolume,
base::Unretained(renderer_wrapper_.get()), volume_));
}
void PipelineImpl::SetLatencyHint(
absl::optional<base::TimeDelta> latency_hint) {
DVLOG(1) << __func__ << "("
<< (latency_hint
? base::NumberToString(latency_hint->InMilliseconds()) + "ms"
: "null_opt")
<< ")";
DCHECK(!latency_hint || (*latency_hint >= base::TimeDelta()));
DCHECK(thread_checker_.CalledOnValidThread());
// Not checking IsRunning() so we can set the latency hint before Start().
media_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RendererWrapper::SetLatencyHint,
base::Unretained(renderer_wrapper_.get()), latency_hint));
}
void PipelineImpl::SetPreservesPitch(bool preserves_pitch) {
DCHECK(thread_checker_.CalledOnValidThread());
media_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RendererWrapper::SetPreservesPitch,
base::Unretained(renderer_wrapper_.get()),
preserves_pitch));
}
void PipelineImpl::SetAutoplayInitiated(bool autoplay_initiated) {
DCHECK(thread_checker_.CalledOnValidThread());
media_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RendererWrapper::SetAutoplayInitiated,
base::Unretained(renderer_wrapper_.get()),
autoplay_initiated));
}
base::TimeDelta PipelineImpl::GetMediaTime() const {
DCHECK(thread_checker_.CalledOnValidThread());
// Don't trust renderer time during a pending seek. Renderer may return
// pre-seek time which may corrupt |last_media_time_| used for clamping.
if (seek_time_ != kNoTimestamp) {
DVLOG(3) << __func__ << ": (seeking) " << seek_time_.InMilliseconds()
<< " ms";
return seek_time_;
}
base::TimeDelta media_time = renderer_wrapper_->GetMediaTime();
// Clamp current media time to the last reported value, this prevents higher
// level clients from seeing time go backwards based on inaccurate or spurious
// delay values reported to the AudioClock.
//
// It is expected that such events are transient and will be recovered as
// rendering continues over time.
if (media_time < last_media_time_) {
DVLOG(2) << __func__ << ": actual=" << media_time
<< " clamped=" << last_media_time_;
return last_media_time_;
}
DVLOG(3) << __func__ << ": " << media_time.InMilliseconds() << " ms";
last_media_time_ = media_time;
return last_media_time_;
}
Ranges<base::TimeDelta> PipelineImpl::GetBufferedTimeRanges() const {
DCHECK(thread_checker_.CalledOnValidThread());
return renderer_wrapper_->GetBufferedTimeRanges();
}
base::TimeDelta PipelineImpl::GetMediaDuration() const {
DCHECK(thread_checker_.CalledOnValidThread());
return duration_;
}
bool PipelineImpl::DidLoadingProgress() {
DCHECK(thread_checker_.CalledOnValidThread());
return renderer_wrapper_->DidLoadingProgress();
}
PipelineStatistics PipelineImpl::GetStatistics() const {
DCHECK(thread_checker_.CalledOnValidThread());
return renderer_wrapper_->GetStatistics();
}
void PipelineImpl::SetCdm(CdmContext* cdm_context,
CdmAttachedCB cdm_attached_cb) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(cdm_context);
DCHECK(cdm_attached_cb);
// Not checking IsRunning() so we can set the CDM before Start().
media_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RendererWrapper::SetCdm,
base::Unretained(renderer_wrapper_.get()), cdm_context,
BindToCurrentLoop(std::move(cdm_attached_cb))));
}
#define RETURN_STRING(state) \
case state: \
return #state;
// static
const char* PipelineImpl::GetStateString(State state) {
switch (state) {
RETURN_STRING(kCreated);
RETURN_STRING(kStarting);
RETURN_STRING(kSeeking);
RETURN_STRING(kPlaying);
RETURN_STRING(kStopping);
RETURN_STRING(kStopped);
RETURN_STRING(kSuspending);
RETURN_STRING(kSuspended);
RETURN_STRING(kResuming);
}
NOTREACHED();
return "INVALID";
}
#undef RETURN_STRING
void PipelineImpl::AsyncCreateRenderer(
absl::optional<RendererType> renderer_type,
RendererCreatedCB renderer_created_cb) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
std::move(renderer_created_cb).Run(create_renderer_cb_.Run(renderer_type));
}
void PipelineImpl::OnError(PipelineStatus error) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
DCHECK(IsRunning());
// If the error happens during starting/seeking/suspending/resuming,
// report the error via the completion callback for those tasks.
// Else report error via the client interface.
if (seek_cb_) {
std::move(seek_cb_).Run(error);
return;
}
if (suspend_cb_) {
std::move(suspend_cb_).Run(error);
return;
}
DCHECK(client_);
client_->OnError(error);
}
void PipelineImpl::OnEnded() {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnEnded();
}
void PipelineImpl::OnMetadata(const PipelineMetadata& metadata) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnMetadata(metadata);
}
void PipelineImpl::OnBufferingStateChange(BufferingState state,
BufferingStateChangeReason reason) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnBufferingStateChange(state, reason);
}
void PipelineImpl::OnDurationChange(base::TimeDelta duration) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
duration_ = duration;
DCHECK(client_);
client_->OnDurationChange();
}
void PipelineImpl::OnWaiting(WaitingReason reason) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnWaiting(reason);
}
void PipelineImpl::OnVideoNaturalSizeChange(const gfx::Size& size) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnVideoNaturalSizeChange(size);
}
void PipelineImpl::OnVideoOpacityChange(bool opaque) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnVideoOpacityChange(opaque);
}
void PipelineImpl::OnVideoFrameRateChange(absl::optional<int> fps) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnVideoFrameRateChange(fps);
}
void PipelineImpl::OnAudioConfigChange(const AudioDecoderConfig& config) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnAudioConfigChange(config);
}
void PipelineImpl::OnVideoConfigChange(const VideoDecoderConfig& config) {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnVideoConfigChange(config);
}
void PipelineImpl::OnVideoAverageKeyframeDistanceUpdate() {
DVLOG(2) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnVideoAverageKeyframeDistanceUpdate();
}
void PipelineImpl::OnAudioPipelineInfoChange(const AudioPipelineInfo& info) {
DVLOG(2) << __func__ << ": info=" << info;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnAudioPipelineInfoChange(info);
}
void PipelineImpl::OnVideoPipelineInfoChange(const VideoPipelineInfo& info) {
DVLOG(2) << __func__ << ": info=" << info;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
DCHECK(client_);
client_->OnVideoPipelineInfoChange(info);
}
void PipelineImpl::OnSeekDone(bool is_suspended) {
DVLOG(3) << __func__ << ": is_suspended=" << is_suspended;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
seek_time_ = kNoTimestamp;
is_suspended_ = is_suspended;
// `seek_cb_` could have been reset in OnError().
if (seek_cb_)
std::move(seek_cb_).Run(PIPELINE_OK);
}
void PipelineImpl::OnSuspendDone() {
DVLOG(3) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(IsRunning());
is_suspended_ = true;
// `suspend_cb_` could have been reset in OnError().
if (suspend_cb_)
std::move(suspend_cb_).Run(PIPELINE_OK);
}
} // namespace media