blob: 143b23823e99545451cf7db02aeb5ae5059e90f4 [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include <memory>
#include <vector>
#include "base/basictypes.h" // For COMPILE_ASSERT
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/math/size.h"
#include "cobalt/media/base/audio_decoder_config.h"
#include "cobalt/media/base/bind_to_current_loop.h"
#include "cobalt/media/base/channel_layout.h"
#include "cobalt/media/base/decoder_buffer.h"
#include "cobalt/media/base/demuxer.h"
#include "cobalt/media/base/demuxer_stream.h"
#include "cobalt/media/base/media_export.h"
#include "cobalt/media/base/media_log.h"
#include "cobalt/media/base/pipeline.h"
#include "cobalt/media/base/pipeline_status.h"
#include "cobalt/media/base/playback_statistics.h"
#include "cobalt/media/base/ranges.h"
#include "cobalt/media/base/sbplayer_set_bounds_helper.h"
#include "cobalt/media/base/starboard_player.h"
#include "cobalt/media/base/video_decoder_config.h"
#include "starboard/configuration_constants.h"
namespace cobalt {
namespace media {
using base::Time;
using base::TimeDelta;
namespace {
static const int kRetryDelayAtSuspendInMilliseconds = 100;
// Used to post parameters to SbPlayerPipeline::StartTask() as the number of
// parameters exceed what base::Bind() can support.
struct StartTaskParameters {
Demuxer* demuxer;
SetDrmSystemReadyCB set_drm_system_ready_cb;
PipelineStatusCB ended_cb;
ErrorCB error_cb;
Pipeline::SeekCB seek_cb;
Pipeline::BufferingStateCB buffering_state_cb;
base::Closure duration_change_cb;
base::Closure output_mode_change_cb;
base::Closure content_size_change_cb;
std::string max_video_capabilities;
#if SB_HAS(PLAYER_WITH_URL)
std::string source_url;
bool is_url_based;
#endif // SB_HAS(PLAYER_WITH_URL)
};
// SbPlayerPipeline is a PipelineBase implementation that uses the SbPlayer
// interface internally.
class MEDIA_EXPORT SbPlayerPipeline : public Pipeline,
public DemuxerHost,
public StarboardPlayer::Host {
public:
// Constructs a media pipeline that will execute on |task_runner|.
SbPlayerPipeline(
PipelineWindow window,
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const GetDecodeTargetGraphicsContextProviderFunc&
get_decode_target_graphics_context_provider_func,
bool allow_resume_after_suspend, MediaLog* media_log,
VideoFrameProvider* video_frame_provider);
~SbPlayerPipeline() override;
void Suspend() override;
// TODO: This is temporary for supporting background media playback.
// Need to be removed with media refactor.
void Resume(PipelineWindow window) override;
void Start(Demuxer* demuxer,
const SetDrmSystemReadyCB& set_drm_system_ready_cb,
const PipelineStatusCB& ended_cb, const ErrorCB& error_cb,
const SeekCB& seek_cb, const BufferingStateCB& buffering_state_cb,
const base::Closure& duration_change_cb,
const base::Closure& output_mode_change_cb,
const base::Closure& content_size_change_cb,
const std::string& max_video_capabilities) override;
#if SB_HAS(PLAYER_WITH_URL)
void Start(const SetDrmSystemReadyCB& set_drm_system_ready_cb,
const OnEncryptedMediaInitDataEncounteredCB&
encrypted_media_init_data_encountered_cb,
const std::string& source_url, const PipelineStatusCB& ended_cb,
const ErrorCB& error_cb, const SeekCB& seek_cb,
const BufferingStateCB& buffering_state_cb,
const base::Closure& duration_change_cb,
const base::Closure& output_mode_change_cb,
const base::Closure& content_size_change_cb) override;
#endif // SB_HAS(PLAYER_WITH_URL)
void Stop(const base::Closure& stop_cb) override;
void Seek(TimeDelta time, const SeekCB& seek_cb);
bool HasAudio() const override;
bool HasVideo() const override;
float GetPlaybackRate() const override;
void SetPlaybackRate(float playback_rate) override;
float GetVolume() const override;
void SetVolume(float volume) override;
TimeDelta GetMediaTime() override;
Ranges<TimeDelta> GetBufferedTimeRanges() override;
TimeDelta GetMediaDuration() const override;
#if SB_HAS(PLAYER_WITH_URL)
TimeDelta GetMediaStartDate() const override;
#endif // SB_HAS(PLAYER_WITH_URL)
void GetNaturalVideoSize(math::Size* out_size) const override;
bool DidLoadingProgress() const override;
PipelineStatistics GetStatistics() const override;
SetBoundsCB GetSetBoundsCB() override;
void SetDecodeToTextureOutputMode(bool enabled) override;
private:
void StartTask(const StartTaskParameters& parameters);
void SetVolumeTask(float volume);
void SetPlaybackRateTask(float volume);
void SetDurationTask(TimeDelta duration);
// DemuxerHost implementaion.
void OnBufferedTimeRangesChanged(
const Ranges<base::TimeDelta>& ranges) override;
void SetDuration(TimeDelta duration) override;
void OnDemuxerError(PipelineStatus error) override;
void AddTextStream(DemuxerStream* text_stream,
const TextTrackConfig& config) override;
void RemoveTextStream(DemuxerStream* text_stream) override;
#if SB_HAS(PLAYER_WITH_URL)
void CreateUrlPlayer(const std::string& source_url);
void SetDrmSystem(SbDrmSystem drm_system);
#endif // SB_HAS(PLAYER_WITH_URL)
void CreatePlayer(SbDrmSystem drm_system);
void OnDemuxerInitialized(PipelineStatus status);
void OnDemuxerSeeked(PipelineStatus status);
void OnDemuxerStopped();
void OnDemuxerStreamRead(DemuxerStream::Type type,
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer);
// StarboardPlayer::Host implementation.
void OnNeedData(DemuxerStream::Type type) override;
void OnPlayerStatus(SbPlayerState state) override;
void OnPlayerError(SbPlayerError error, const std::string& message) override;
// Used to make a delayed call to OnNeedData() if |audio_read_delayed_| is
// true. If |audio_read_delayed_| is false, that means the delayed call has
// been cancelled due to a seek.
void DelayedNeedData();
void UpdateDecoderConfig(DemuxerStream* stream);
void CallSeekCB(PipelineStatus status);
void SuspendTask(base::WaitableEvent* done_event);
void ResumeTask(PipelineWindow window, base::WaitableEvent* done_event);
// Store the media time retrieved by GetMediaTime so we can cache it as an
// estimate and avoid calling SbPlayerGetInfo too frequently.
void StoreMediaTime(TimeDelta media_time);
// Message loop used to execute pipeline tasks. It is thread-safe.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
// Whether we should save DecoderBuffers for resume after suspend.
const bool allow_resume_after_suspend_;
// The window this player associates with. It should only be assigned in the
// dtor and accesed once by SbPlayerCreate().
PipelineWindow window_;
// Call to get the SbDecodeTargetGraphicsContextProvider for SbPlayerCreate().
const GetDecodeTargetGraphicsContextProviderFunc
get_decode_target_graphics_context_provider_func_;
// Lock used to serialize access for the following member variables.
mutable base::Lock lock_;
// Amount of available buffered data. Set by filters.
Ranges<TimeDelta> buffered_time_ranges_;
// True when AddBufferedByteRange() has been called more recently than
// DidLoadingProgress().
mutable bool did_loading_progress_;
// Video's natural width and height. Set by filters.
math::Size natural_size_;
// Current volume level (from 0.0f to 1.0f). This value is set immediately
// via SetVolume() and a task is dispatched on the message loop to notify the
// filters.
float volume_ = 1.f;
// Current playback rate (>= 0.0f). This value is set immediately via
// SetPlaybackRate() and a task is dispatched on the message loop to notify
// the filters.
float playback_rate_ = 0.f;
// The saved audio and video demuxer streams. Note that it is safe to store
// raw pointers of the demuxer streams, as the Demuxer guarantees that its
// |DemuxerStream|s live as long as the Demuxer itself.
DemuxerStream* audio_stream_ = nullptr;
DemuxerStream* video_stream_ = nullptr;
mutable PipelineStatistics statistics_;
// The following member variables are only accessed by tasks posted to
// |task_runner_|.
// Temporary callback used for Stop().
base::Closure stop_cb_;
// Permanent callbacks passed in via Start().
SetDrmSystemReadyCB set_drm_system_ready_cb_;
PipelineStatusCB ended_cb_;
ErrorCB error_cb_;
BufferingStateCB buffering_state_cb_;
base::Closure duration_change_cb_;
base::Closure output_mode_change_cb_;
base::Closure content_size_change_cb_;
base::Optional<bool> decode_to_texture_output_mode_;
#if SB_HAS(PLAYER_WITH_URL)
StarboardPlayer::OnEncryptedMediaInitDataEncounteredCB
on_encrypted_media_init_data_encountered_cb_;
#endif // SB_HAS(PLAYER_WITH_URL)
// Demuxer reference used for setting the preload value.
Demuxer* demuxer_ = nullptr;
bool audio_read_in_progress_ = false;
bool audio_read_delayed_ = false;
bool video_read_in_progress_ = false;
TimeDelta duration_;
#if SB_HAS(PLAYER_WITH_URL)
TimeDelta start_date_;
bool is_url_based_;
#endif // SB_HAS(PLAYER_WITH_URL)
scoped_refptr<SbPlayerSetBoundsHelper> set_bounds_helper_;
// The following member variables can be accessed from WMPI thread but all
// modifications to them happens on the pipeline thread. So any access of
// them from the WMPI thread and any modification to them on the pipeline
// thread has to guarded by lock. Access to them from the pipeline thread
// needn't to be guarded.
// Temporary callback used for Start() and Seek().
SeekCB seek_cb_;
base::TimeDelta seek_time_;
std::unique_ptr<StarboardPlayer> player_;
bool is_initial_preroll_ = true;
bool suspended_ = false;
bool stopped_ = false;
bool ended_ = false;
VideoFrameProvider* video_frame_provider_;
// Read audio from the stream if |timestamp_of_last_written_audio_| is less
// than |seek_time_| + |kAudioPrerollLimit|, this effectively allows 10
// seconds of audio to be written to the SbPlayer after playback startup or
// seek.
static const SbTime kAudioPrerollLimit = 10 * kSbTimeSecond;
// Don't read audio from the stream more than |kAudioLimit| ahead of the
// current media time during playing.
static const SbTime kAudioLimit = kSbTimeSecond;
// Only call GetMediaTime() from OnNeedData if it has been
// |kMediaTimeCheckInterval| since the last call to GetMediaTime().
static const SbTime kMediaTimeCheckInterval = 0.1 * kSbTimeSecond;
// Timestamp for the last written audio.
SbTime timestamp_of_last_written_audio_ = 0;
// Last media time reported by GetMediaTime().
SbTime last_media_time_;
// Time when we last checked the media time.
SbTime last_time_media_time_retrieved_ = 0;
// The maximum video playback capabilities required for the playback.
std::string max_video_capabilities_;
base::Optional<PlaybackStatistics::Record> statistics_record_;
DISALLOW_COPY_AND_ASSIGN(SbPlayerPipeline);
};
SbPlayerPipeline::SbPlayerPipeline(
PipelineWindow window,
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const GetDecodeTargetGraphicsContextProviderFunc&
get_decode_target_graphics_context_provider_func,
bool allow_resume_after_suspend, MediaLog* media_log,
VideoFrameProvider* video_frame_provider)
: task_runner_(task_runner),
allow_resume_after_suspend_(allow_resume_after_suspend),
window_(window),
get_decode_target_graphics_context_provider_func_(
get_decode_target_graphics_context_provider_func),
natural_size_(0, 0),
set_bounds_helper_(new SbPlayerSetBoundsHelper),
video_frame_provider_(video_frame_provider) {
#if SB_API_VERSION >= 11
SbMediaSetAudioWriteDuration(kAudioLimit);
#endif // SB_API_VERSION >= 11
}
SbPlayerPipeline::~SbPlayerPipeline() { DCHECK(!player_); }
void SbPlayerPipeline::Suspend() {
DCHECK(!task_runner_->BelongsToCurrentThread());
base::WaitableEvent waitable_event(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
task_runner_->PostTask(FROM_HERE,
base::Bind(&SbPlayerPipeline::SuspendTask,
base::Unretained(this), &waitable_event));
waitable_event.Wait();
}
void SbPlayerPipeline::Resume(PipelineWindow window) {
DCHECK(!task_runner_->BelongsToCurrentThread());
base::WaitableEvent waitable_event(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
task_runner_->PostTask(FROM_HERE,
base::Bind(&SbPlayerPipeline::ResumeTask,
base::Unretained(this),
window, &waitable_event));
waitable_event.Wait();
}
#if SB_HAS(PLAYER_WITH_URL)
void OnEncryptedMediaInitDataEncountered(
const Pipeline::OnEncryptedMediaInitDataEncounteredCB&
on_encrypted_media_init_data_encountered,
const char* init_data_type, const unsigned char* init_data,
unsigned int init_data_length) {
media::EmeInitDataType init_data_type_enum;
if (!SbStringCompareAll(init_data_type, "cenc")) {
init_data_type_enum = media::kEmeInitDataTypeCenc;
} else if (!SbStringCompareAll(init_data_type, "fairplay")) {
init_data_type_enum = media::kEmeInitDataTypeFairplay;
} else if (!SbStringCompareAll(init_data_type, "keyids")) {
init_data_type_enum = media::kEmeInitDataTypeKeyIds;
} else if (!SbStringCompareAll(init_data_type, "webm")) {
init_data_type_enum = media::kEmeInitDataTypeWebM;
} else {
LOG(WARNING) << "Unknown EME initialization data type.";
return;
}
std::vector<uint8_t> init_data_vec(init_data, init_data + init_data_length);
DCHECK(!on_encrypted_media_init_data_encountered.is_null());
on_encrypted_media_init_data_encountered.Run(init_data_type_enum,
init_data_vec);
}
#endif // SB_HAS(PLAYER_WITH_URL)
void SbPlayerPipeline::Start(Demuxer* demuxer,
const SetDrmSystemReadyCB& set_drm_system_ready_cb,
const PipelineStatusCB& ended_cb,
const ErrorCB& error_cb, const SeekCB& seek_cb,
const BufferingStateCB& buffering_state_cb,
const base::Closure& duration_change_cb,
const base::Closure& output_mode_change_cb,
const base::Closure& content_size_change_cb,
const std::string& max_video_capabilities) {
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::Start");
DCHECK(!ended_cb.is_null());
DCHECK(!error_cb.is_null());
DCHECK(!seek_cb.is_null());
DCHECK(!buffering_state_cb.is_null());
DCHECK(!duration_change_cb.is_null());
DCHECK(!output_mode_change_cb.is_null());
DCHECK(!content_size_change_cb.is_null());
DCHECK(demuxer);
StartTaskParameters parameters;
parameters.demuxer = demuxer;
parameters.set_drm_system_ready_cb = set_drm_system_ready_cb;
parameters.ended_cb = ended_cb;
parameters.error_cb = error_cb;
parameters.seek_cb = seek_cb;
parameters.buffering_state_cb = buffering_state_cb;
parameters.duration_change_cb = duration_change_cb;
parameters.output_mode_change_cb = output_mode_change_cb;
parameters.content_size_change_cb = content_size_change_cb;
parameters.max_video_capabilities = max_video_capabilities;
#if SB_HAS(PLAYER_WITH_URL)
parameters.is_url_based = false;
#endif // SB_HAS(PLAYER_WITH_URL)
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::StartTask, this, parameters));
}
#if SB_HAS(PLAYER_WITH_URL)
void SbPlayerPipeline::Start(const SetDrmSystemReadyCB& set_drm_system_ready_cb,
const OnEncryptedMediaInitDataEncounteredCB&
on_encrypted_media_init_data_encountered_cb,
const std::string& source_url,
const PipelineStatusCB& ended_cb,
const ErrorCB& error_cb,
const SeekCB& seek_cb,
const BufferingStateCB& buffering_state_cb,
const base::Closure& duration_change_cb,
const base::Closure& output_mode_change_cb,
const base::Closure& content_size_change_cb) {
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::Start");
DCHECK(!ended_cb.is_null());
DCHECK(!error_cb.is_null());
DCHECK(!seek_cb.is_null());
DCHECK(!buffering_state_cb.is_null());
DCHECK(!duration_change_cb.is_null());
DCHECK(!output_mode_change_cb.is_null());
DCHECK(!content_size_change_cb.is_null());
DCHECK(!on_encrypted_media_init_data_encountered_cb.is_null());
StartTaskParameters parameters;
parameters.demuxer = NULL;
parameters.set_drm_system_ready_cb = set_drm_system_ready_cb;
parameters.ended_cb = ended_cb;
parameters.error_cb = error_cb;
parameters.seek_cb = seek_cb;
parameters.buffering_state_cb = buffering_state_cb;
parameters.duration_change_cb = duration_change_cb;
parameters.output_mode_change_cb = output_mode_change_cb;
parameters.content_size_change_cb = content_size_change_cb;
parameters.source_url = source_url;
parameters.is_url_based = true;
on_encrypted_media_init_data_encountered_cb_ =
base::Bind(&OnEncryptedMediaInitDataEncountered,
on_encrypted_media_init_data_encountered_cb);
set_drm_system_ready_cb_ = parameters.set_drm_system_ready_cb;
DCHECK(!set_drm_system_ready_cb_.is_null());
set_drm_system_ready_cb_.Run(
base::Bind(&SbPlayerPipeline::SetDrmSystem, this));
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::StartTask, this, parameters));
}
#endif // SB_HAS(PLAYER_WITH_URL)
void SbPlayerPipeline::Stop(const base::Closure& stop_cb) {
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::Stop");
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(FROM_HERE,
base::Bind(&SbPlayerPipeline::Stop, this, stop_cb));
return;
}
DCHECK(stop_cb_.is_null());
DCHECK(!stop_cb.is_null());
stopped_ = true;
if (player_) {
std::unique_ptr<StarboardPlayer> player;
{
base::AutoLock auto_lock(lock_);
player = std::move(player_);
}
DLOG(INFO) << "Destroying SbPlayer.";
player.reset();
DLOG(INFO) << "SbPlayer destroyed.";
}
// When Stop() is in progress, we no longer need to call |error_cb_|.
error_cb_.Reset();
if (demuxer_) {
stop_cb_ = stop_cb;
demuxer_->Stop();
OnDemuxerStopped();
} else {
stop_cb.Run();
}
}
void SbPlayerPipeline::Seek(TimeDelta time, const SeekCB& seek_cb) {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::Seek, this, time, seek_cb));
return;
}
if (!player_) {
seek_cb.Run(PIPELINE_ERROR_INVALID_STATE, false);
return;
}
player_->PrepareForSeek();
ended_ = false;
DCHECK(seek_cb_.is_null());
DCHECK(!seek_cb.is_null());
if (audio_read_in_progress_ || video_read_in_progress_) {
const TimeDelta kDelay = TimeDelta::FromMilliseconds(50);
task_runner_->PostDelayedTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::Seek, this, time, seek_cb),
kDelay);
return;
}
{
base::AutoLock auto_lock(lock_);
seek_cb_ = seek_cb;
seek_time_ = time;
}
// Ignore pending delayed calls to OnNeedData, and update variables used to
// decide when to delay.
audio_read_delayed_ = false;
StoreMediaTime(seek_time_);
timestamp_of_last_written_audio_ = 0;
#if SB_HAS(PLAYER_WITH_URL)
if (is_url_based_) {
player_->Seek(seek_time_);
return;
}
#endif // SB_HAS(PLAYER_WITH_URL)
demuxer_->Seek(time, BindToCurrentLoop(base::Bind(
&SbPlayerPipeline::OnDemuxerSeeked, this)));
}
bool SbPlayerPipeline::HasAudio() const {
base::AutoLock auto_lock(lock_);
return audio_stream_ != NULL;
}
bool SbPlayerPipeline::HasVideo() const {
base::AutoLock auto_lock(lock_);
return video_stream_ != NULL;
}
float SbPlayerPipeline::GetPlaybackRate() const {
base::AutoLock auto_lock(lock_);
return playback_rate_;
}
void SbPlayerPipeline::SetPlaybackRate(float playback_rate) {
base::AutoLock auto_lock(lock_);
playback_rate_ = playback_rate;
task_runner_->PostTask(
FROM_HERE,
base::Bind(&SbPlayerPipeline::SetPlaybackRateTask, this, playback_rate));
}
float SbPlayerPipeline::GetVolume() const {
base::AutoLock auto_lock(lock_);
return volume_;
}
void SbPlayerPipeline::SetVolume(float volume) {
if (volume < 0.0f || volume > 1.0f) return;
base::AutoLock auto_lock(lock_);
volume_ = volume;
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::SetVolumeTask, this, volume));
}
void SbPlayerPipeline::StoreMediaTime(TimeDelta media_time) {
last_media_time_ = media_time.ToSbTime();
last_time_media_time_retrieved_ = SbTimeGetNow();
}
TimeDelta SbPlayerPipeline::GetMediaTime() {
base::AutoLock auto_lock(lock_);
if (!seek_cb_.is_null()) {
StoreMediaTime(seek_time_);
return seek_time_;
}
if (!player_) {
StoreMediaTime(TimeDelta());
return TimeDelta();
}
if (ended_) {
StoreMediaTime(duration_);
return duration_;
}
base::TimeDelta media_time;
#if SB_HAS(PLAYER_WITH_URL)
if (is_url_based_) {
int frame_width;
int frame_height;
player_->GetVideoResolution(&frame_width, &frame_height);
if (frame_width != natural_size_.width() ||
frame_height != natural_size_.height()) {
natural_size_ = math::Size(frame_width, frame_height);
content_size_change_cb_.Run();
}
}
#endif // SB_HAS(PLAYER_WITH_URL)
player_->GetInfo(&statistics_.video_frames_decoded,
&statistics_.video_frames_dropped, &media_time);
StoreMediaTime(media_time);
return media_time;
}
Ranges<TimeDelta> SbPlayerPipeline::GetBufferedTimeRanges() {
base::AutoLock auto_lock(lock_);
#if SB_HAS(PLAYER_WITH_URL)
Ranges<TimeDelta> time_ranges;
if (!player_) {
return time_ranges;
}
if (is_url_based_) {
base::TimeDelta media_time;
base::TimeDelta buffer_start_time;
base::TimeDelta buffer_length_time;
player_->GetInfo(&statistics_.video_frames_decoded,
&statistics_.video_frames_dropped, &media_time);
player_->GetUrlPlayerBufferedTimeRanges(&buffer_start_time,
&buffer_length_time);
if (buffer_length_time.InSeconds() == 0) {
buffered_time_ranges_ = time_ranges;
return time_ranges;
}
time_ranges.Add(buffer_start_time, buffer_start_time + buffer_length_time);
if (buffered_time_ranges_.size() > 0) {
base::TimeDelta old_buffer_start_time = buffered_time_ranges_.start(0);
base::TimeDelta old_buffer_length_time = buffered_time_ranges_.end(0);
int64 old_start_seconds = old_buffer_start_time.InSeconds();
int64 new_start_seconds = buffer_start_time.InSeconds();
int64 old_length_seconds = old_buffer_length_time.InSeconds();
int64 new_length_seconds = buffer_length_time.InSeconds();
if (old_start_seconds != new_start_seconds ||
old_length_seconds != new_length_seconds) {
did_loading_progress_ = true;
}
} else {
did_loading_progress_ = true;
}
buffered_time_ranges_ = time_ranges;
return time_ranges;
}
#endif // SB_HAS(PLAYER_WITH_URL)
return buffered_time_ranges_;
}
TimeDelta SbPlayerPipeline::GetMediaDuration() const {
base::AutoLock auto_lock(lock_);
return duration_;
}
#if SB_HAS(PLAYER_WITH_URL)
TimeDelta SbPlayerPipeline::GetMediaStartDate() const {
base::AutoLock auto_lock(lock_);
return start_date_;
}
#endif // SB_HAS(PLAYER_WITH_URL)
void SbPlayerPipeline::GetNaturalVideoSize(math::Size* out_size) const {
CHECK(out_size);
base::AutoLock auto_lock(lock_);
*out_size = natural_size_;
}
bool SbPlayerPipeline::DidLoadingProgress() const {
base::AutoLock auto_lock(lock_);
bool ret = did_loading_progress_;
did_loading_progress_ = false;
return ret;
}
PipelineStatistics SbPlayerPipeline::GetStatistics() const {
base::AutoLock auto_lock(lock_);
return statistics_;
}
Pipeline::SetBoundsCB SbPlayerPipeline::GetSetBoundsCB() {
return base::Bind(&SbPlayerSetBoundsHelper::SetBounds, set_bounds_helper_);
}
void SbPlayerPipeline::SetDecodeToTextureOutputMode(bool enabled) {
TRACE_EVENT1("cobalt::media",
"SbPlayerPipeline::SetDecodeToTextureOutputMode", "mode",
enabled);
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::SetDecodeToTextureOutputMode,
this, enabled));
return;
}
// The player can't be created yet, if it is, then we're updating the output
// mode too late.
DCHECK(!player_);
decode_to_texture_output_mode_ = enabled;
}
void SbPlayerPipeline::StartTask(const StartTaskParameters& parameters) {
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::StartTask");
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!demuxer_);
demuxer_ = parameters.demuxer;
set_drm_system_ready_cb_ = parameters.set_drm_system_ready_cb;
ended_cb_ = parameters.ended_cb;
error_cb_ = parameters.error_cb;
{
base::AutoLock auto_lock(lock_);
seek_cb_ = parameters.seek_cb;
}
buffering_state_cb_ = parameters.buffering_state_cb;
duration_change_cb_ = parameters.duration_change_cb;
output_mode_change_cb_ = parameters.output_mode_change_cb;
content_size_change_cb_ = parameters.content_size_change_cb;
max_video_capabilities_ = parameters.max_video_capabilities;
#if SB_HAS(PLAYER_WITH_URL)
is_url_based_ = parameters.is_url_based;
if (is_url_based_) {
CreateUrlPlayer(parameters.source_url);
return;
}
#endif // SB_HAS(PLAYER_WITH_URL)
const bool kEnableTextTracks = false;
demuxer_->Initialize(this,
BindToCurrentLoop(base::Bind(
&SbPlayerPipeline::OnDemuxerInitialized, this)),
kEnableTextTracks);
}
void SbPlayerPipeline::SetVolumeTask(float volume) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (player_) {
player_->SetVolume(volume_);
}
}
void SbPlayerPipeline::SetPlaybackRateTask(float volume) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (player_) {
player_->SetPlaybackRate(playback_rate_);
}
}
void SbPlayerPipeline::SetDurationTask(TimeDelta duration) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!duration_change_cb_.is_null()) {
duration_change_cb_.Run();
}
}
void SbPlayerPipeline::OnBufferedTimeRangesChanged(
const Ranges<base::TimeDelta>& ranges) {
base::AutoLock auto_lock(lock_);
did_loading_progress_ = true;
buffered_time_ranges_ = ranges;
}
void SbPlayerPipeline::SetDuration(TimeDelta duration) {
base::AutoLock auto_lock(lock_);
duration_ = duration;
task_runner_->PostTask(
FROM_HERE,
base::Bind(&SbPlayerPipeline::SetDurationTask, this, duration));
}
void SbPlayerPipeline::OnDemuxerError(PipelineStatus error) {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::OnDemuxerError, this, error));
return;
}
if (error != PIPELINE_OK) {
ResetAndRunIfNotNull(&error_cb_, error, "Demuxer error.");
}
}
void SbPlayerPipeline::AddTextStream(DemuxerStream* text_stream,
const TextTrackConfig& config) {
NOTREACHED();
}
void SbPlayerPipeline::RemoveTextStream(DemuxerStream* text_stream) {
NOTREACHED();
}
#if SB_HAS(PLAYER_WITH_URL)
void SbPlayerPipeline::CreateUrlPlayer(const std::string& source_url) {
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::CreateUrlPlayer");
DCHECK(task_runner_->BelongsToCurrentThread());
if (stopped_) {
return;
}
if (suspended_) {
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&SbPlayerPipeline::CreateUrlPlayer, this, source_url),
TimeDelta::FromMilliseconds(kRetryDelayAtSuspendInMilliseconds));
return;
}
// TODO: Check |suspended_| here as the pipeline can be suspended before the
// player is created. In this case we should delay creating the player as the
// creation of player may fail.
{
base::AutoLock auto_lock(lock_);
DLOG(INFO) << "StarboardPlayer created with url: " << source_url;
player_.reset(new StarboardPlayer(
task_runner_, source_url, window_, this, set_bounds_helper_.get(),
allow_resume_after_suspend_, *decode_to_texture_output_mode_,
on_encrypted_media_init_data_encountered_cb_, video_frame_provider_));
SetPlaybackRateTask(playback_rate_);
SetVolumeTask(volume_);
}
if (player_->IsValid()) {
base::Closure output_mode_change_cb;
{
base::AutoLock auto_lock(lock_);
DCHECK(!output_mode_change_cb_.is_null());
output_mode_change_cb = std::move(output_mode_change_cb_);
}
output_mode_change_cb.Run();
return;
}
player_.reset();
CallSeekCB(DECODER_ERROR_NOT_SUPPORTED);
}
void SbPlayerPipeline::SetDrmSystem(SbDrmSystem drm_system) {
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::SetDrmSystem");
base::AutoLock auto_lock(lock_);
if (!player_) {
DLOG(INFO)
<< "Player not set before calling SbPlayerPipeline::SetDrmSystem";
return;
}
if (player_->IsValid()) {
player_->SetDrmSystem(drm_system);
}
}
#endif // SB_HAS(PLAYER_WITH_URL)
void SbPlayerPipeline::CreatePlayer(SbDrmSystem drm_system) {
#if SB_HAS(PLAYER_WITH_URL)
DCHECK(!is_url_based_);
#endif // SB_HAS(PLAYER_WITH_URL
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::CreatePlayer");
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(audio_stream_ || video_stream_);
if (stopped_) {
return;
}
if (suspended_) {
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&SbPlayerPipeline::CreatePlayer, this, drm_system),
TimeDelta::FromMilliseconds(kRetryDelayAtSuspendInMilliseconds));
return;
}
// TODO: Check |suspended_| here as the pipeline can be suspended before the
// player is created. In this case we should delay creating the player as the
// creation of player may fail.
AudioDecoderConfig invalid_audio_config;
const AudioDecoderConfig& audio_config =
audio_stream_ ? audio_stream_->audio_decoder_config()
: invalid_audio_config;
VideoDecoderConfig invalid_video_config;
const VideoDecoderConfig& video_config =
video_stream_ ? video_stream_->video_decoder_config()
: invalid_video_config;
if (video_stream_) {
statistics_record_.emplace(video_stream_->video_decoder_config());
}
{
base::AutoLock auto_lock(lock_);
player_.reset(new StarboardPlayer(
task_runner_, get_decode_target_graphics_context_provider_func_,
audio_config, video_config, window_, drm_system, this,
set_bounds_helper_.get(), allow_resume_after_suspend_,
*decode_to_texture_output_mode_, video_frame_provider_,
max_video_capabilities_));
SetPlaybackRateTask(playback_rate_);
SetVolumeTask(volume_);
}
if (player_->IsValid()) {
base::Closure output_mode_change_cb;
{
base::AutoLock auto_lock(lock_);
DCHECK(!output_mode_change_cb_.is_null());
output_mode_change_cb = std::move(output_mode_change_cb_);
}
output_mode_change_cb.Run();
if (audio_stream_) {
UpdateDecoderConfig(audio_stream_);
}
if (video_stream_) {
UpdateDecoderConfig(video_stream_);
}
return;
}
player_.reset();
CallSeekCB(DECODER_ERROR_NOT_SUPPORTED);
}
void SbPlayerPipeline::OnDemuxerInitialized(PipelineStatus status) {
#if SB_HAS(PLAYER_WITH_URL)
DCHECK(!is_url_based_);
#endif // SB_HAS(PLAYER_WITH_URL)
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::OnDemuxerInitialized");
DCHECK(task_runner_->BelongsToCurrentThread());
if (stopped_) {
return;
}
if (status != PIPELINE_OK) {
ResetAndRunIfNotNull(&error_cb_, status, "Demuxer initialization error.");
return;
}
if (suspended_) {
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&SbPlayerPipeline::OnDemuxerInitialized, this, status),
TimeDelta::FromMilliseconds(kRetryDelayAtSuspendInMilliseconds));
return;
}
DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO);
DemuxerStream* video_stream = demuxer_->GetStream(DemuxerStream::VIDEO);
if (audio_stream == NULL && video_stream == NULL) {
LOG(INFO) << "The video has to contain an audio track or a video track.";
ResetAndRunIfNotNull(
&error_cb_, DEMUXER_ERROR_NO_SUPPORTED_STREAMS,
"The video has to contain an audio track or a video track.");
return;
}
{
base::AutoLock auto_lock(lock_);
audio_stream_ = audio_stream;
video_stream_ = video_stream;
bool is_encrypted =
audio_stream_ && audio_stream_->audio_decoder_config().is_encrypted();
is_encrypted |=
video_stream_ && video_stream_->video_decoder_config().is_encrypted();
bool natural_size_changed = false;
if (video_stream_) {
natural_size_changed =
(video_stream_->video_decoder_config().natural_size().width() !=
natural_size_.width() ||
video_stream_->video_decoder_config().natural_size().height() !=
natural_size_.height());
natural_size_ = video_stream_->video_decoder_config().natural_size();
}
if (natural_size_changed) {
content_size_change_cb_.Run();
}
if (is_encrypted) {
set_drm_system_ready_cb_.Run(
BindToCurrentLoop(base::Bind(&SbPlayerPipeline::CreatePlayer, this)));
return;
}
}
CreatePlayer(kSbDrmSystemInvalid);
}
void SbPlayerPipeline::OnDemuxerSeeked(PipelineStatus status) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (status == PIPELINE_OK && player_) {
player_->Seek(seek_time_);
}
}
void SbPlayerPipeline::OnDemuxerStopped() {
TRACE_EVENT0("cobalt::media", "SbPlayerPipeline::OnDemuxerStopped");
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::OnDemuxerStopped, this));
return;
}
std::move(stop_cb_).Run();
}
void SbPlayerPipeline::OnDemuxerStreamRead(
DemuxerStream::Type type, DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
#if SB_HAS(PLAYER_WITH_URL)
DCHECK(!is_url_based_);
#endif // SB_HAS(PLAYER_WITH_URL)
DCHECK(type == DemuxerStream::AUDIO || type == DemuxerStream::VIDEO)
<< "Unsupported DemuxerStream::Type " << type;
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::OnDemuxerStreamRead, this,
type, status, buffer));
return;
}
DemuxerStream* stream =
type == DemuxerStream::AUDIO ? audio_stream_ : video_stream_;
DCHECK(stream);
// In case if Stop() has been called.
if (!player_) {
return;
}
if (status == DemuxerStream::kAborted) {
if (type == DemuxerStream::AUDIO) {
DCHECK(audio_read_in_progress_);
audio_read_in_progress_ = false;
} else {
DCHECK(video_read_in_progress_);
video_read_in_progress_ = false;
}
if (!seek_cb_.is_null()) {
CallSeekCB(PIPELINE_OK);
}
return;
}
if (status == DemuxerStream::kConfigChanged) {
UpdateDecoderConfig(stream);
stream->Read(
base::Bind(&SbPlayerPipeline::OnDemuxerStreamRead, this, type));
return;
}
if (type == DemuxerStream::AUDIO) {
audio_read_in_progress_ = false;
if (!buffer->end_of_stream()) {
timestamp_of_last_written_audio_ = buffer->timestamp().ToSbTime();
}
} else {
video_read_in_progress_ = false;
}
player_->WriteBuffer(type, buffer);
}
void SbPlayerPipeline::OnNeedData(DemuxerStream::Type type) {
#if SB_HAS(PLAYER_WITH_URL)
DCHECK(!is_url_based_);
#endif // SB_HAS(PLAYER_WITH_URL)
DCHECK(task_runner_->BelongsToCurrentThread());
// In case if Stop() has been called.
if (!player_) {
return;
}
if (type == DemuxerStream::AUDIO) {
if (!audio_stream_) {
LOG(WARNING)
<< "Calling OnNeedData() for audio data during audioless playback";
return;
}
if (audio_read_in_progress_) {
return;
}
#if SB_API_VERSION >= 11
// If we haven't checked the media time recently, update it now.
if (SbTimeGetNow() - last_time_media_time_retrieved_ >
kMediaTimeCheckInterval) {
GetMediaTime();
}
// Delay reading audio more than |kAudioLimit| ahead of playback after the
// player has received enough audio for preroll, taking into account that
// our estimate of playback time might be behind by
// |kMediaTimeCheckInterval|.
if (timestamp_of_last_written_audio_ - seek_time_.ToSbTime() >
kAudioPrerollLimit) {
// The estimated time ahead of playback may be negative if no audio has
// been written.
SbTime time_ahead_of_playback =
timestamp_of_last_written_audio_ - last_media_time_;
if (time_ahead_of_playback > (kAudioLimit + kMediaTimeCheckInterval)) {
SbTime delay_time = (time_ahead_of_playback - kAudioLimit) /
std::max(playback_rate_, 1.0f);
task_runner_->PostDelayedTask(
FROM_HERE, base::Bind(&SbPlayerPipeline::DelayedNeedData, this),
base::TimeDelta::FromMicroseconds(delay_time));
audio_read_delayed_ = true;
return;
}
}
audio_read_delayed_ = false;
#endif // SB_API_VERSION >= 11
audio_read_in_progress_ = true;
} else {
DCHECK_EQ(type, DemuxerStream::VIDEO);
if (video_read_in_progress_) {
return;
}
video_read_in_progress_ = true;
}
DemuxerStream* stream =
type == DemuxerStream::AUDIO ? audio_stream_ : video_stream_;
DCHECK(stream);
stream->Read(base::Bind(&SbPlayerPipeline::OnDemuxerStreamRead, this, type));
}
void SbPlayerPipeline::OnPlayerStatus(SbPlayerState state) {
DCHECK(task_runner_->BelongsToCurrentThread());
// In case if Stop() has been called.
if (!player_) {
return;
}
switch (state) {
case kSbPlayerStateInitialized:
NOTREACHED();
break;
case kSbPlayerStatePrerolling:
#if SB_HAS(PLAYER_WITH_URL)
if (is_url_based_) {
break;
}
#endif // SB_HAS(PLAYER_WITH_URL)
buffering_state_cb_.Run(kHaveMetadata);
break;
case kSbPlayerStatePresenting: {
#if SB_HAS(PLAYER_WITH_URL)
if (is_url_based_) {
duration_ = player_->GetDuration();
start_date_ = player_->GetStartDate();
buffering_state_cb_.Run(kHaveMetadata);
int frame_width;
int frame_height;
player_->GetVideoResolution(&frame_width, &frame_height);
bool natural_size_changed = (frame_width != natural_size_.width() ||
frame_height != natural_size_.height());
natural_size_ = math::Size(frame_width, frame_height);
if (natural_size_changed) {
content_size_change_cb_.Run();
}
}
#endif // SB_HAS(PLAYER_WITH_URL)
buffering_state_cb_.Run(kPrerollCompleted);
if (!seek_cb_.is_null()) {
CallSeekCB(PIPELINE_OK);
}
if (statistics_record_) {
SB_DCHECK(video_stream_);
statistics_record_->OnPresenting(video_stream_->video_decoder_config());
}
break;
}
case kSbPlayerStateEndOfStream:
ended_cb_.Run(PIPELINE_OK);
ended_ = true;
break;
case kSbPlayerStateDestroyed:
break;
}
}
void SbPlayerPipeline::OnPlayerError(SbPlayerError error,
const std::string& message) {
DCHECK(task_runner_->BelongsToCurrentThread());
// In case if Stop() has been called.
if (!player_) {
return;
}
#if SB_HAS(PLAYER_WITH_URL)
if (error >= kSbPlayerErrorMax) {
DCHECK(is_url_based_);
switch (static_cast<SbUrlPlayerError>(error)) {
case kSbUrlPlayerErrorNetwork:
ResetAndRunIfNotNull(&error_cb_, PIPELINE_ERROR_NETWORK, message);
break;
case kSbUrlPlayerErrorSrcNotSupported:
ResetAndRunIfNotNull(&error_cb_, DEMUXER_ERROR_COULD_NOT_OPEN, message);
break;
}
return;
}
#endif // SB_HAS(PLAYER_WITH_URL)
auto statistics = video_stream_ ? PlaybackStatistics::GetStatistics(
video_stream_->video_decoder_config())
: "n/a";
switch (error) {
case kSbPlayerErrorDecode:
ResetAndRunIfNotNull(&error_cb_, PIPELINE_ERROR_DECODE,
message + ", statistics: " + statistics);
break;
case kSbPlayerErrorCapabilityChanged:
ResetAndRunIfNotNull(&error_cb_, PLAYBACK_CAPABILITY_CHANGED,
message + ", statistics: " + statistics);
break;
#if SB_API_VERSION >= 11
case kSbPlayerErrorMax:
NOTREACHED();
break;
#endif // SB_API_VERSION >= 11
}
}
void SbPlayerPipeline::DelayedNeedData() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (audio_read_delayed_) {
OnNeedData(DemuxerStream::AUDIO);
}
}
void SbPlayerPipeline::UpdateDecoderConfig(DemuxerStream* stream) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (stream->type() == DemuxerStream::AUDIO) {
const AudioDecoderConfig& decoder_config = stream->audio_decoder_config();
player_->UpdateAudioConfig(decoder_config);
} else {
DCHECK_EQ(stream->type(), DemuxerStream::VIDEO);
const VideoDecoderConfig& decoder_config = stream->video_decoder_config();
base::AutoLock auto_lock(lock_);
bool natural_size_changed =
(decoder_config.natural_size().width() != natural_size_.width() ||
decoder_config.natural_size().height() != natural_size_.height());
natural_size_ = decoder_config.natural_size();
player_->UpdateVideoConfig(decoder_config);
if (natural_size_changed) {
content_size_change_cb_.Run();
}
DCHECK(statistics_record_);
statistics_record_->OnConfigChange(stream->video_decoder_config());
}
}
void SbPlayerPipeline::CallSeekCB(PipelineStatus status) {
SeekCB seek_cb;
bool is_initial_preroll;
{
base::AutoLock auto_lock(lock_);
DCHECK(!seek_cb_.is_null());
seek_cb = std::move(seek_cb_);
is_initial_preroll = is_initial_preroll_;
is_initial_preroll_ = false;
}
seek_cb.Run(status, is_initial_preroll);
}
void SbPlayerPipeline::SuspendTask(base::WaitableEvent* done_event) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(done_event);
DCHECK(!suspended_);
if (suspended_) {
done_event->Signal();
return;
}
if (player_) {
// Cancel pending delayed calls to OnNeedData. After player_->Resume(),
// |player_| will call OnNeedData again.
audio_read_delayed_ = false;
player_->Suspend();
}
suspended_ = true;
done_event->Signal();
}
void SbPlayerPipeline::ResumeTask(PipelineWindow window,
base::WaitableEvent* done_event) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(done_event);
DCHECK(suspended_);
if (!suspended_) {
done_event->Signal();
return;
}
window_ = window;
if (player_) {
player_->Resume(window);
}
suspended_ = false;
done_event->Signal();
}
} // namespace
// static
scoped_refptr<Pipeline> Pipeline::Create(
PipelineWindow window,
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const GetDecodeTargetGraphicsContextProviderFunc&
get_decode_target_graphics_context_provider_func,
bool allow_resume_after_suspend, MediaLog* media_log,
VideoFrameProvider* video_frame_provider) {
return new SbPlayerPipeline(
window, task_runner, get_decode_target_graphics_context_provider_func,
allow_resume_after_suspend, media_log, video_frame_provider);
}
} // namespace media
} // namespace cobalt