blob: 1defe0f2c3f80813a5035c1b6626d9eb4ecd5bc2 [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/player/web_media_player_impl.h"
#include <math.h>
#include <limits>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/float_util.h"
#include "base/message_loop_proxy.h"
#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "media/audio/shell_audio_sink.h"
#include "media/base/bind_to_loop.h"
#include "media/base/filter_collection.h"
#include "media/base/limits.h"
#include "media/base/media_log.h"
#include "media/base/video_frame.h"
#include "media/filters/chunk_demuxer.h"
#include "media/filters/shell_audio_renderer.h"
#include "media/filters/shell_demuxer.h"
#include "media/filters/video_renderer_base.h"
#include "media/player/web_media_player_proxy.h"
namespace {
// Limits the range of playback rate.
//
// TODO(kylep): Revisit these.
//
// Vista has substantially lower performance than XP or Windows7. If you speed
// up a video too much, it can't keep up, and rendering stops updating except on
// the time bar. For really high speeds, audio becomes a bottleneck and we just
// use up the data we have, which may not achieve the speed requested, but will
// not crash the tab.
//
// A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems
// like a busy loop). It gets unresponsive, although its not completely dead.
//
// Also our timers are not very accurate (especially for ogg), which becomes
// evident at low speeds and on Vista. Since other speeds are risky and outside
// the norms, we think 1/16x to 16x is a safe and useful range for now.
const float kMinRate = 0.0625f;
const float kMaxRate = 16.0f;
// Prefix for histograms related to Encrypted Media Extensions.
const char* kMediaEme = "Media.EME.";
#if defined(COBALT_SKIP_SEEK_REQUEST_NEAR_END)
// On some platforms, the underlying media player can hang if we keep seeking to
// a position that is near the end of the video. So we ignore any seeks near the
// end of stream position when the current playback position is also near the
// end of the stream. In this case, "near the end of stream" means "position
// greater than or equal to duration() - kEndOfStreamEpsilonInSeconds".
const double kEndOfStreamEpsilonInSeconds = 2.;
bool IsNearTheEndOfStream(const media::WebMediaPlayerImpl* wmpi,
double position) {
float duration = wmpi->GetDuration();
if (base::IsFinite(duration)) {
// If video is very short, we always treat a position as near the end.
if (duration <= kEndOfStreamEpsilonInSeconds)
return true;
if (position >= duration - kEndOfStreamEpsilonInSeconds)
return true;
}
return false;
}
#endif // defined(COBALT_SKIP_SEEK_REQUEST_NEAR_END)
base::TimeDelta ConvertSecondsToTimestamp(float seconds) {
float microseconds = seconds * base::Time::kMicrosecondsPerSecond;
float integer = ceilf(microseconds);
float difference = integer - microseconds;
// Round down if difference is large enough.
if ((microseconds > 0 && difference > 0.5f) ||
(microseconds <= 0 && difference >= 0.5f)) {
integer -= 1.0f;
}
// Now we can safely cast to int64 microseconds.
return base::TimeDelta::FromMicroseconds(static_cast<int64>(integer));
}
} // namespace
namespace media {
#define BIND_TO_RENDER_LOOP(function) \
BindToLoop(main_loop_->message_loop_proxy(), \
base::Bind(function, AsWeakPtr()))
#define BIND_TO_RENDER_LOOP_2(function, arg1, arg2) \
BindToLoop(main_loop_->message_loop_proxy(), \
base::Bind(function, AsWeakPtr(), arg1, arg2))
// TODO(acolwell): Investigate whether the key_system & session_id parameters
// are really necessary.
typedef base::Callback<
void(const std::string&, const std::string&, scoped_array<uint8>, int)>
OnNeedKeyCB;
static void LogMediaSourceError(const scoped_refptr<MediaLog>& media_log,
const std::string& error) {
media_log->AddEvent(media_log->CreateMediaSourceErrorEvent(error));
}
WebMediaPlayerImpl::WebMediaPlayerImpl(
PipelineWindow window,
WebMediaPlayerClient* client,
WebMediaPlayerDelegate* delegate,
const scoped_refptr<ShellVideoFrameProvider>& video_frame_provider,
scoped_ptr<FilterCollection> collection,
const scoped_refptr<AudioRendererSink>& audio_renderer_sink,
scoped_ptr<MessageLoopFactory> message_loop_factory,
const scoped_refptr<MediaLog>& media_log)
: network_state_(WebMediaPlayer::kNetworkStateEmpty),
ready_state_(WebMediaPlayer::kReadyStateHaveNothing),
main_loop_(MessageLoop::current()),
filter_collection_(collection.Pass()),
message_loop_factory_(message_loop_factory.Pass()),
client_(client),
delegate_(delegate),
video_frame_provider_(video_frame_provider),
proxy_(new WebMediaPlayerProxy(main_loop_->message_loop_proxy(), this)),
media_log_(media_log),
incremented_externally_allocated_memory_(false),
audio_renderer_sink_(audio_renderer_sink),
is_local_source_(false),
supports_save_(true),
suppress_destruction_errors_(false) {
media_log_->AddEvent(
media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_CREATED));
scoped_refptr<base::MessageLoopProxy> pipeline_message_loop =
message_loop_factory_->GetMessageLoop(MessageLoopFactory::kPipeline);
pipeline_ = Pipeline::Create(window, pipeline_message_loop, media_log_);
// Also we want to be notified of |main_loop_| destruction.
main_loop_->AddDestructionObserver(this);
SetDecryptorReadyCB set_decryptor_ready_cb;
decryptor_.reset(new ProxyDecryptor(proxy_.get()));
set_decryptor_ready_cb = base::Bind(&ProxyDecryptor::SetDecryptorReadyCB,
base::Unretained(decryptor_.get()));
// Create default video renderer.
scoped_refptr<VideoRendererBase> video_renderer = new VideoRendererBase(
pipeline_message_loop, set_decryptor_ready_cb,
base::Bind(base::DoNothing),
BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::SetOpaque), true);
filter_collection_->AddVideoRenderer(video_renderer);
proxy_->set_frame_provider(video_renderer);
#if defined(COBALT_USE_PUNCHOUT)
// The size passed to CreatePunchOutFrame is used to pass the natural size of
// the video to the pipeline. The pipeline already gets the natural size of
// the video from the underlying player so we set this to gfx::Size() to avoid
// propagating the frame size through the media stack.
punch_out_video_frame_ = VideoFrame::CreatePunchOutFrame(gfx::Size());
#else // defined(COBALT_USE_PUNCHOUT)
DCHECK(audio_renderer_sink);
filter_collection_->AddAudioRenderer(ShellAudioRenderer::Create(
audio_renderer_sink, set_decryptor_ready_cb, pipeline_message_loop));
#endif // defined(COBALT_USE_PUNCHOUT)
if (video_frame_provider_) {
media_time_and_seeking_state_cb_ =
base::Bind(&WebMediaPlayerImpl::GetMediaTimeAndSeekingState,
base::Unretained(this));
video_frame_provider_->RegisterMediaTimeAndSeekingStateCB(
media_time_and_seeking_state_cb_);
}
if (delegate_) {
delegate_->RegisterPlayer(this);
}
}
WebMediaPlayerImpl::~WebMediaPlayerImpl() {
DCHECK(!main_loop_ || main_loop_ == MessageLoop::current());
if (delegate_) {
delegate_->UnregisterPlayer(this);
}
if (video_frame_provider_) {
DCHECK(!media_time_and_seeking_state_cb_.is_null());
video_frame_provider_->UnregisterMediaTimeAndSeekingStateCB(
media_time_and_seeking_state_cb_);
media_time_and_seeking_state_cb_.Reset();
}
#if defined(__LB_ANDROID__)
audio_focus_bridge_.AbandonAudioFocus();
#endif // defined(__LB_ANDROID__)
decryptor_->DestroySoon();
Destroy();
media_log_->AddEvent(
media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
// Finally tell the |main_loop_| we don't want to be notified of destruction
// event.
if (main_loop_) {
main_loop_->RemoveDestructionObserver(this);
}
}
namespace {
// Helper enum for reporting scheme histograms.
enum URLSchemeForHistogram {
kUnknownURLScheme,
kMissingURLScheme,
kHttpURLScheme,
kHttpsURLScheme,
kFtpURLScheme,
kChromeExtensionURLScheme,
kJavascriptURLScheme,
kFileURLScheme,
kBlobURLScheme,
kDataURLScheme,
kFileSystemScheme,
kMaxURLScheme = kFileSystemScheme // Must be equal to highest enum value.
};
URLSchemeForHistogram URLScheme(const GURL& url) {
if (!url.has_scheme())
return kMissingURLScheme;
if (url.SchemeIs("http"))
return kHttpURLScheme;
if (url.SchemeIs("https"))
return kHttpsURLScheme;
if (url.SchemeIs("ftp"))
return kFtpURLScheme;
if (url.SchemeIs("chrome-extension"))
return kChromeExtensionURLScheme;
if (url.SchemeIs("javascript"))
return kJavascriptURLScheme;
if (url.SchemeIs("file"))
return kFileURLScheme;
if (url.SchemeIs("blob"))
return kBlobURLScheme;
if (url.SchemeIs("data"))
return kDataURLScheme;
if (url.SchemeIs("filesystem"))
return kFileSystemScheme;
return kUnknownURLScheme;
}
} // anonymous namespace
void WebMediaPlayerImpl::LoadMediaSource() {
DCHECK_EQ(main_loop_, MessageLoop::current());
DCHECK(filter_collection_);
// Handle any volume changes that occured before load().
SetVolume(GetClient()->Volume());
SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
SetReadyState(WebMediaPlayer::kReadyStateHaveNothing);
scoped_refptr<base::MessageLoopProxy> message_loop =
message_loop_factory_->GetMessageLoop(MessageLoopFactory::kPipeline);
// Media source pipelines can start immediately.
chunk_demuxer_ = new ChunkDemuxer(
BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDemuxerOpened),
BIND_TO_RENDER_LOOP_2(&WebMediaPlayerImpl::OnNeedKey, "", ""),
base::Bind(&LogMediaSourceError, media_log_));
filter_collection_->SetDemuxer(chunk_demuxer_);
supports_save_ = false;
state_.is_media_source = true;
StartPipeline();
}
void WebMediaPlayerImpl::LoadProgressive(
const GURL& url,
const scoped_refptr<BufferedDataSource>& data_source,
CORSMode cors_mode) {
DCHECK_EQ(main_loop_, MessageLoop::current());
DCHECK(filter_collection_);
UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), kMaxURLScheme);
// Handle any volume changes that occured before load().
SetVolume(GetClient()->Volume());
SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
SetReadyState(WebMediaPlayer::kReadyStateHaveNothing);
media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec()));
scoped_refptr<base::MessageLoopProxy> message_loop =
message_loop_factory_->GetMessageLoop(MessageLoopFactory::kPipeline);
proxy_->set_data_source(data_source);
is_local_source_ = !url.SchemeIs("http") && !url.SchemeIs("https");
filter_collection_->SetDemuxer(
new ShellDemuxer(message_loop, proxy_->data_source()));
state_.is_progressive = true;
StartPipeline();
}
void WebMediaPlayerImpl::CancelLoad() {
DCHECK_EQ(main_loop_, MessageLoop::current());
}
void WebMediaPlayerImpl::Play() {
DCHECK_EQ(main_loop_, MessageLoop::current());
#if defined(__LB_ANDROID__)
audio_focus_bridge_.RequestAudioFocus();
#endif // defined(__LB_ANDROID__)
state_.paused = false;
pipeline_->SetPlaybackRate(state_.playback_rate);
media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PLAY));
}
void WebMediaPlayerImpl::Pause() {
DCHECK_EQ(main_loop_, MessageLoop::current());
#if defined(__LB_ANDROID__)
audio_focus_bridge_.AbandonAudioFocus();
#endif // defined(__LB_ANDROID__)
state_.paused = true;
pipeline_->SetPlaybackRate(0.0f);
state_.paused_time = pipeline_->GetMediaTime();
media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PAUSE));
}
bool WebMediaPlayerImpl::SupportsFullscreen() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return true;
}
bool WebMediaPlayerImpl::SupportsSave() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return supports_save_;
}
void WebMediaPlayerImpl::Seek(float seconds) {
DCHECK_EQ(main_loop_, MessageLoop::current());
#if defined(COBALT_SKIP_SEEK_REQUEST_NEAR_END)
// Ignore any seek request that is near the end of the stream when the
// current playback position is also near the end of the stream to avoid
// a hang in the MediaEngine.
if (IsNearTheEndOfStream(this, GetCurrentTime()) &&
IsNearTheEndOfStream(this, seconds)) {
return;
}
#endif // defined(COBALT_SKIP_SEEK_REQUEST_NEAR_END)
if (state_.starting || state_.seeking) {
state_.pending_seek = true;
state_.pending_seek_seconds = seconds;
if (chunk_demuxer_) {
chunk_demuxer_->CancelPendingSeek();
decryptor_->CancelDecrypt(Decryptor::kAudio);
decryptor_->CancelDecrypt(Decryptor::kVideo);
}
return;
}
media_log_->AddEvent(media_log_->CreateSeekEvent(seconds));
base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds);
// Update our paused time.
if (state_.paused)
state_.paused_time = seek_time;
state_.seeking = true;
if (chunk_demuxer_) {
chunk_demuxer_->StartWaitingForSeek();
decryptor_->CancelDecrypt(Decryptor::kAudio);
decryptor_->CancelDecrypt(Decryptor::kVideo);
}
// Kick off the asynchronous seek!
pipeline_->Seek(seek_time,
BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek));
}
void WebMediaPlayerImpl::SetEndTime(float seconds) {
DCHECK_EQ(main_loop_, MessageLoop::current());
// TODO(hclam): add method call when it has been implemented.
return;
}
void WebMediaPlayerImpl::SetRate(float rate) {
DCHECK_EQ(main_loop_, MessageLoop::current());
// TODO(kylep): Remove when support for negatives is added. Also, modify the
// following checks so rewind uses reasonable values also.
if (rate < 0.0f)
return;
// Limit rates to reasonable values by clamping.
if (rate != 0.0f) {
if (rate < kMinRate)
rate = kMinRate;
else if (rate > kMaxRate)
rate = kMaxRate;
}
state_.playback_rate = rate;
if (!state_.paused) {
pipeline_->SetPlaybackRate(rate);
}
}
void WebMediaPlayerImpl::SetVolume(float volume) {
DCHECK_EQ(main_loop_, MessageLoop::current());
pipeline_->SetVolume(volume);
}
void WebMediaPlayerImpl::SetVisible(bool visible) {
DCHECK_EQ(main_loop_, MessageLoop::current());
// TODO(hclam): add appropriate method call when pipeline has it implemented.
return;
}
bool WebMediaPlayerImpl::GetTotalBytesKnown() {
DCHECK_EQ(main_loop_, MessageLoop::current());
return pipeline_->GetTotalBytes() != 0;
}
bool WebMediaPlayerImpl::HasVideo() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return pipeline_->HasVideo();
}
bool WebMediaPlayerImpl::HasAudio() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return pipeline_->HasAudio();
}
gfx::Size WebMediaPlayerImpl::GetNaturalSize() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
gfx::Size size;
pipeline_->GetNaturalVideoSize(&size);
return size;
}
bool WebMediaPlayerImpl::IsPaused() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return pipeline_->GetPlaybackRate() == 0.0f;
}
bool WebMediaPlayerImpl::IsSeeking() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing)
return false;
return state_.seeking;
}
float WebMediaPlayerImpl::GetDuration() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing)
return std::numeric_limits<float>::quiet_NaN();
base::TimeDelta duration = pipeline_->GetMediaDuration();
// Return positive infinity if the resource is unbounded.
// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration
if (duration == kInfiniteDuration())
return std::numeric_limits<float>::infinity();
return static_cast<float>(duration.InSecondsF());
}
float WebMediaPlayerImpl::GetCurrentTime() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (state_.paused)
return static_cast<float>(state_.paused_time.InSecondsF());
return static_cast<float>(pipeline_->GetMediaTime().InSecondsF());
}
int WebMediaPlayerImpl::GetDataRate() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
// TODO(hclam): Add this method call if pipeline has it in the interface.
return 0;
}
WebMediaPlayer::NetworkState WebMediaPlayerImpl::GetNetworkState() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return network_state_;
}
WebMediaPlayer::ReadyState WebMediaPlayerImpl::GetReadyState() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return ready_state_;
}
const Ranges<base::TimeDelta>& WebMediaPlayerImpl::GetBufferedTimeRanges() {
DCHECK_EQ(main_loop_, MessageLoop::current());
buffered_ = pipeline_->GetBufferedTimeRanges();
return buffered_;
}
float WebMediaPlayerImpl::GetMaxTimeSeekable() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
// We don't support seeking in streaming media.
if (proxy_ && proxy_->data_source() && proxy_->data_source()->IsStreaming())
return 0.0f;
return static_cast<float>(pipeline_->GetMediaDuration().InSecondsF());
}
bool WebMediaPlayerImpl::DidLoadingProgress() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return pipeline_->DidLoadingProgress();
}
unsigned long long WebMediaPlayerImpl::GetTotalBytes() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return pipeline_->GetTotalBytes();
}
bool WebMediaPlayerImpl::HasSingleSecurityOrigin() const {
if (proxy_)
return proxy_->HasSingleOrigin();
return true;
}
bool WebMediaPlayerImpl::DidPassCORSAccessCheck() const {
return proxy_ && proxy_->DidPassCORSAccessCheck();
}
float WebMediaPlayerImpl::MediaTimeForTimeValue(float timeValue) const {
return ConvertSecondsToTimestamp(timeValue).InSecondsF();
}
unsigned WebMediaPlayerImpl::GetDecodedFrameCount() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
PipelineStatistics stats = pipeline_->GetStatistics();
return stats.video_frames_decoded;
}
unsigned WebMediaPlayerImpl::GetDroppedFrameCount() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
PipelineStatistics stats = pipeline_->GetStatistics();
return stats.video_frames_dropped;
}
unsigned WebMediaPlayerImpl::GetAudioDecodedByteCount() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
PipelineStatistics stats = pipeline_->GetStatistics();
return stats.audio_bytes_decoded;
}
unsigned WebMediaPlayerImpl::GetVideoDecodedByteCount() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
PipelineStatistics stats = pipeline_->GetStatistics();
return stats.video_bytes_decoded;
}
scoped_refptr<ShellVideoFrameProvider>
WebMediaPlayerImpl::GetVideoFrameProvider() {
return video_frame_provider_;
}
scoped_refptr<VideoFrame> WebMediaPlayerImpl::GetCurrentFrame() {
if (video_frame_provider_) {
return video_frame_provider_->GetCurrentFrame();
}
#if defined(COBALT_USE_PUNCHOUT)
// We want to give proxy_ a chance to produce a valid frame before returning
// the punch out frame
return punch_out_video_frame_;
#else // defined(COBALT_USE_PUNCHOUT)
return NULL;
#endif // defined(COBALT_USE_PUNCHOUT)
}
void WebMediaPlayerImpl::PutCurrentFrame(
const scoped_refptr<VideoFrame>& video_frame) {
#if defined(COBALT_USE_PUNCHOUT)
if (video_frame == punch_out_video_frame_) {
// This happens when proxy_ returns NULL in getCurrentFrame() so we make
// sure that it gets a NULL back as well
proxy_->PutCurrentFrame(NULL);
return;
}
#endif // defined(COBALT_USE_PUNCHOUT)
if (video_frame) {
proxy_->PutCurrentFrame(video_frame);
} else {
proxy_->PutCurrentFrame(NULL);
}
}
// TODO: Eliminate the duplicated enums.
#define COMPILE_ASSERT_MATCHING_STATUS_ENUM(player_name, chromium_name) \
COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::player_name) == \
static_cast<int>(ChunkDemuxer::chromium_name), \
mismatching_status_enums)
COMPILE_ASSERT_MATCHING_STATUS_ENUM(kAddIdStatusOk, kOk);
COMPILE_ASSERT_MATCHING_STATUS_ENUM(kAddIdStatusNotSupported, kNotSupported);
COMPILE_ASSERT_MATCHING_STATUS_ENUM(kAddIdStatusReachedIdLimit,
kReachedIdLimit);
#undef COMPILE_ASSERT_MATCHING_ENUM
WebMediaPlayer::AddIdStatus WebMediaPlayerImpl::SourceAddId(
const std::string& id,
const std::string& type,
const std::vector<std::string>& codecs) {
DCHECK_EQ(main_loop_, MessageLoop::current());
std::vector<std::string> new_codecs(codecs.size());
for (size_t i = 0; i < codecs.size(); ++i)
new_codecs[i] = codecs[i];
return static_cast<WebMediaPlayer::AddIdStatus>(
chunk_demuxer_->AddId(id, type, new_codecs));
}
bool WebMediaPlayerImpl::SourceRemoveId(const std::string& id) {
DCHECK(!id.empty());
chunk_demuxer_->RemoveId(id);
return true;
}
Ranges<base::TimeDelta> WebMediaPlayerImpl::SourceBuffered(
const std::string& id) {
return chunk_demuxer_->GetBufferedRanges(id);
}
bool WebMediaPlayerImpl::SourceAppend(const std::string& id,
const unsigned char* data,
unsigned length) {
DCHECK_EQ(main_loop_, MessageLoop::current());
float old_duration = GetDuration();
if (!chunk_demuxer_->AppendData(id, data, length))
return false;
if (old_duration != GetDuration())
GetClient()->DurationChanged();
return true;
}
bool WebMediaPlayerImpl::SourceAbort(const std::string& id) {
chunk_demuxer_->Abort(id);
return true;
}
double WebMediaPlayerImpl::SourceGetDuration() const {
DCHECK(chunk_demuxer_);
return chunk_demuxer_->GetDuration();
}
void WebMediaPlayerImpl::SourceSetDuration(double new_duration) {
DCHECK_GE(new_duration, 0);
DCHECK(chunk_demuxer_);
chunk_demuxer_->SetDuration(new_duration);
}
void WebMediaPlayerImpl::SourceEndOfStream(
WebMediaPlayer::EndOfStreamStatus status) {
DCHECK_EQ(main_loop_, MessageLoop::current());
PipelineStatus pipeline_status = PIPELINE_OK;
switch (status) {
case WebMediaPlayer::kEndOfStreamStatusNoError:
break;
case WebMediaPlayer::kEndOfStreamStatusNetworkError:
pipeline_status = PIPELINE_ERROR_NETWORK;
break;
case WebMediaPlayer::kEndOfStreamStatusDecodeError:
pipeline_status = PIPELINE_ERROR_DECODE;
break;
default:
NOTIMPLEMENTED();
}
float old_duration = GetDuration();
if (!chunk_demuxer_->EndOfStream(pipeline_status))
DVLOG(1) << "EndOfStream call failed.";
if (old_duration != GetDuration())
GetClient()->DurationChanged();
}
bool WebMediaPlayerImpl::SourceSetTimestampOffset(const std::string& id,
double offset) {
base::TimeDelta time_offset = base::TimeDelta::FromMicroseconds(
offset * base::Time::kMicrosecondsPerSecond);
return chunk_demuxer_->SetTimestampOffset(id, time_offset);
}
// Helper enum for reporting generateKeyRequest/addKey histograms.
enum MediaKeyException {
kUnknownResultId,
kSuccess,
kKeySystemNotSupported,
kInvalidPlayerState,
kMaxMediaKeyException
};
static MediaKeyException MediaKeyExceptionForUMA(
WebMediaPlayer::MediaKeyException e) {
switch (e) {
case WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported:
return kKeySystemNotSupported;
case WebMediaPlayer::kMediaKeyExceptionInvalidPlayerState:
return kInvalidPlayerState;
case WebMediaPlayer::kMediaKeyExceptionNoError:
return kSuccess;
default:
return kUnknownResultId;
}
}
// Helper for converting |key_system| name and exception |e| to a pair of enum
// values from above, for reporting to UMA.
static void ReportMediaKeyExceptionToUMA(const std::string& method,
const std::string& key_system,
WebMediaPlayer::MediaKeyException e) {
MediaKeyException result_id = MediaKeyExceptionForUMA(e);
DCHECK_NE(result_id, kUnknownResultId) << e;
base::LinearHistogram::FactoryGet(
kMediaEme + KeySystemNameForUMA(key_system) + "." + method, 1,
kMaxMediaKeyException, kMaxMediaKeyException + 1,
base::Histogram::kUmaTargetedHistogramFlag)
->Add(result_id);
}
WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::GenerateKeyRequest(
const std::string& key_system,
const unsigned char* init_data,
unsigned init_data_length) {
WebMediaPlayer::MediaKeyException e =
GenerateKeyRequestInternal(key_system, init_data, init_data_length);
ReportMediaKeyExceptionToUMA("generateKeyRequest", key_system, e);
return e;
}
WebMediaPlayer::MediaKeyException
WebMediaPlayerImpl::GenerateKeyRequestInternal(const std::string& key_system,
const unsigned char* init_data,
unsigned init_data_length) {
if (!IsSupportedKeySystem(key_system))
return WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported;
// We do not support run-time switching between key systems for now.
if (current_key_system_.empty())
current_key_system_ = key_system;
else if (key_system != current_key_system_)
return WebMediaPlayer::kMediaKeyExceptionInvalidPlayerState;
DVLOG(1) << "generateKeyRequest: " << key_system << ": "
<< std::string(reinterpret_cast<const char*>(init_data),
static_cast<size_t>(init_data_length));
// TODO(xhwang): We assume all streams are from the same container (thus have
// the same "type") for now. In the future, the "type" should be passed down
// from the application.
if (!decryptor_->GenerateKeyRequest(key_system, init_data_type_, init_data,
init_data_length)) {
current_key_system_.clear();
return WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported;
}
return WebMediaPlayer::kMediaKeyExceptionNoError;
}
WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::AddKey(
const std::string& key_system,
const unsigned char* key,
unsigned key_length,
const unsigned char* init_data,
unsigned init_data_length,
const std::string& session_id) {
WebMediaPlayer::MediaKeyException e = AddKeyInternal(
key_system, key, key_length, init_data, init_data_length, session_id);
ReportMediaKeyExceptionToUMA("addKey", key_system, e);
return e;
}
WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::AddKeyInternal(
const std::string& key_system,
const unsigned char* key,
unsigned key_length,
const unsigned char* init_data,
unsigned init_data_length,
const std::string& session_id) {
DCHECK(key);
DCHECK_GT(key_length, 0u);
if (!IsSupportedKeySystem(key_system))
return WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported;
if (current_key_system_.empty() || key_system != current_key_system_)
return WebMediaPlayer::kMediaKeyExceptionInvalidPlayerState;
DVLOG(1) << "addKey: " << key_system << ": "
<< base::HexEncode(key, static_cast<size_t>(key_length)) << ", "
<< base::HexEncode(init_data, static_cast<size_t>(init_data_length))
<< " [" << session_id << "]";
decryptor_->AddKey(key_system, key, key_length, init_data, init_data_length,
session_id);
return WebMediaPlayer::kMediaKeyExceptionNoError;
}
WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::CancelKeyRequest(
const std::string& key_system,
const std::string& session_id) {
WebMediaPlayer::MediaKeyException e =
CancelKeyRequestInternal(key_system, session_id);
ReportMediaKeyExceptionToUMA("cancelKeyRequest", key_system, e);
return e;
}
WebMediaPlayerImpl::SetBoundsCB WebMediaPlayerImpl::GetSetBoundsCB() {
// |pipeline_| is always valid during WebMediaPlayerImpl's life time. It is
// also reference counted so it lives after WebMediaPlayerImpl is destroyed.
return pipeline_->GetSetBoundsCB();
}
WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::CancelKeyRequestInternal(
const std::string& key_system,
const std::string& session_id) {
if (!IsSupportedKeySystem(key_system))
return WebMediaPlayer::kMediaKeyExceptionKeySystemNotSupported;
if (current_key_system_.empty() || key_system != current_key_system_)
return WebMediaPlayer::kMediaKeyExceptionInvalidPlayerState;
decryptor_->CancelKeyRequest(key_system, session_id);
return WebMediaPlayer::kMediaKeyExceptionNoError;
}
void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() {
Destroy();
main_loop_ = NULL;
}
bool WebMediaPlayerImpl::GetDebugReportDataAddress(void** out_address,
size_t* out_size) {
*out_address = &state_;
*out_size = sizeof(state_);
return true;
}
void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) {
DCHECK_EQ(main_loop_, MessageLoop::current());
state_.starting = false;
state_.seeking = false;
if (state_.pending_seek) {
state_.pending_seek = false;
Seek(state_.pending_seek_seconds);
return;
}
if (status != PIPELINE_OK) {
OnPipelineError(status);
return;
}
// Update our paused time.
if (state_.paused)
state_.paused_time = pipeline_->GetMediaTime();
GetClient()->TimeChanged();
}
void WebMediaPlayerImpl::OnPipelineEnded(PipelineStatus status) {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (status != PIPELINE_OK) {
OnPipelineError(status);
return;
}
GetClient()->TimeChanged();
}
void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (suppress_destruction_errors_)
return;
media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error));
if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing) {
// Any error that occurs before reaching ReadyStateHaveMetadata should
// be considered a format error.
SetNetworkState(WebMediaPlayer::kNetworkStateFormatError);
return;
}
switch (error) {
case PIPELINE_OK:
NOTREACHED() << "PIPELINE_OK isn't an error!";
break;
case PIPELINE_ERROR_NETWORK:
case PIPELINE_ERROR_READ:
SetNetworkState(WebMediaPlayer::kNetworkStateNetworkError);
break;
// TODO(vrk): Because OnPipelineInitialize() directly reports the
// NetworkStateFormatError instead of calling OnPipelineError(), I believe
// this block can be deleted. Should look into it! (crbug.com/126070)
case PIPELINE_ERROR_INITIALIZATION_FAILED:
case PIPELINE_ERROR_COULD_NOT_RENDER:
case PIPELINE_ERROR_URL_NOT_FOUND:
case DEMUXER_ERROR_COULD_NOT_OPEN:
case DEMUXER_ERROR_COULD_NOT_PARSE:
case DEMUXER_ERROR_NO_SUPPORTED_STREAMS:
case DECODER_ERROR_NOT_SUPPORTED:
SetNetworkState(WebMediaPlayer::kNetworkStateFormatError);
break;
case PIPELINE_ERROR_DECODE:
case PIPELINE_ERROR_ABORT:
case PIPELINE_ERROR_OPERATION_PENDING:
case PIPELINE_ERROR_INVALID_STATE:
SetNetworkState(WebMediaPlayer::kNetworkStateDecodeError);
break;
case PIPELINE_ERROR_DECRYPT:
// Decrypt error.
base::Histogram::FactoryGet(
(kMediaEme + KeySystemNameForUMA(current_key_system_) +
".DecryptError"),
1, 1000000, 50, base::Histogram::kUmaTargetedHistogramFlag)
->Add(1);
// TODO(xhwang): Change to use NetworkStateDecryptError once it's added in
// Webkit (see http://crbug.com/124486).
SetNetworkState(WebMediaPlayer::kNetworkStateDecodeError);
break;
case PIPELINE_STATUS_MAX:
NOTREACHED() << "PIPELINE_STATUS_MAX isn't a real error!";
break;
}
}
void WebMediaPlayerImpl::OnPipelineBufferingState(
Pipeline::BufferingState buffering_state) {
DVLOG(1) << "OnPipelineBufferingState(" << buffering_state << ")";
switch (buffering_state) {
case Pipeline::kHaveMetadata:
SetReadyState(WebMediaPlayer::kReadyStateHaveMetadata);
break;
case Pipeline::kPrerollCompleted:
SetReadyState(WebMediaPlayer::kReadyStateHaveEnoughData);
break;
}
}
void WebMediaPlayerImpl::OnDemuxerOpened() {
DCHECK_EQ(main_loop_, MessageLoop::current());
GetClient()->SourceOpened();
}
void WebMediaPlayerImpl::OnKeyAdded(const std::string& key_system,
const std::string& session_id) {
DCHECK_EQ(main_loop_, MessageLoop::current());
base::Histogram::FactoryGet(
kMediaEme + KeySystemNameForUMA(key_system) + ".KeyAdded", 1, 1000000, 50,
base::Histogram::kUmaTargetedHistogramFlag)
->Add(1);
GetClient()->KeyAdded(key_system, session_id);
}
void WebMediaPlayerImpl::OnNeedKey(const std::string& key_system,
const std::string& session_id,
const std::string& type,
scoped_array<uint8> init_data,
int init_data_size) {
DCHECK_EQ(main_loop_, MessageLoop::current());
// Do not fire NeedKey event if encrypted media is not enabled.
if (!decryptor_)
return;
UMA_HISTOGRAM_COUNTS(kMediaEme + std::string("NeedKey"), 1);
#if !defined(__LB_SHELL__) && !defined(COBALT)
DCHECK(init_data_type_.empty() || type.empty() || type == init_data_type_);
#endif // !defined(__LB_SHELL__) && !defined(COBALT)
if (init_data_type_.empty())
init_data_type_ = type;
GetClient()->KeyNeeded(key_system, session_id, init_data.get(),
init_data_size);
}
// TODO: Eliminate the duplicated enums.
#define COMPILE_ASSERT_MATCHING_ENUM(name) \
COMPILE_ASSERT(static_cast<int>(WebMediaPlayerClient::name) == \
static_cast<int>(Decryptor::name), \
mismatching_enums)
COMPILE_ASSERT_MATCHING_ENUM(kUnknownError);
COMPILE_ASSERT_MATCHING_ENUM(kClientError);
COMPILE_ASSERT_MATCHING_ENUM(kServiceError);
COMPILE_ASSERT_MATCHING_ENUM(kOutputError);
COMPILE_ASSERT_MATCHING_ENUM(kHardwareChangeError);
COMPILE_ASSERT_MATCHING_ENUM(kDomainError);
#undef COMPILE_ASSERT_MATCHING_ENUM
void WebMediaPlayerImpl::OnKeyError(const std::string& key_system,
const std::string& session_id,
Decryptor::KeyError error_code,
int system_code) {
DCHECK_EQ(main_loop_, MessageLoop::current());
base::LinearHistogram::FactoryGet(
kMediaEme + KeySystemNameForUMA(key_system) + ".KeyError", 1,
Decryptor::kMaxKeyError, Decryptor::kMaxKeyError + 1,
base::Histogram::kUmaTargetedHistogramFlag)
->Add(error_code);
GetClient()->KeyError(
key_system, session_id,
static_cast<WebMediaPlayerClient::MediaKeyErrorCode>(error_code),
system_code);
}
void WebMediaPlayerImpl::OnKeyMessage(const std::string& key_system,
const std::string& session_id,
const std::string& message,
const GURL& default_url) {
DCHECK_EQ(main_loop_, MessageLoop::current());
GetClient()->KeyMessage(key_system, session_id,
reinterpret_cast<const uint8*>(message.data()),
message.size(), default_url.spec());
}
void WebMediaPlayerImpl::SetOpaque(bool opaque) {
DCHECK_EQ(main_loop_, MessageLoop::current());
GetClient()->SetOpaque(opaque);
}
void WebMediaPlayerImpl::NotifyDownloading(bool is_downloading) {
if (!is_downloading && network_state_ == WebMediaPlayer::kNetworkStateLoading)
SetNetworkState(WebMediaPlayer::kNetworkStateIdle);
else if (is_downloading &&
network_state_ == WebMediaPlayer::kNetworkStateIdle)
SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
media_log_->AddEvent(
media_log_->CreateBooleanEvent(MediaLogEvent::NETWORK_ACTIVITY_SET,
"is_downloading_data", is_downloading));
}
void WebMediaPlayerImpl::StartPipeline() {
state_.starting = true;
SetDecryptorReadyCB set_decryptor_ready_cb;
if (decryptor_) {
set_decryptor_ready_cb = base::Bind(&ProxyDecryptor::SetDecryptorReadyCB,
base::Unretained(decryptor_.get()));
}
pipeline_->Start(
filter_collection_.Pass(),
set_decryptor_ready_cb,
BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineEnded),
BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError),
BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek),
BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineBufferingState),
BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged));
}
void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) {
DCHECK_EQ(main_loop_, MessageLoop::current());
DVLOG(1) << "SetNetworkState: " << state;
network_state_ = state;
// Always notify to ensure client has the latest value.
GetClient()->NetworkStateChanged();
}
void WebMediaPlayerImpl::SetReadyState(WebMediaPlayer::ReadyState state) {
DCHECK_EQ(main_loop_, MessageLoop::current());
DVLOG(1) << "SetReadyState: " << state;
if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing &&
state >= WebMediaPlayer::kReadyStateHaveMetadata) {
if (!HasVideo())
GetClient()->DisableAcceleratedCompositing();
} else if (state == WebMediaPlayer::kReadyStateHaveEnoughData) {
if (is_local_source_ &&
network_state_ == WebMediaPlayer::kNetworkStateLoading) {
SetNetworkState(WebMediaPlayer::kNetworkStateLoaded);
}
}
ready_state_ = state;
// Always notify to ensure client has the latest value.
GetClient()->ReadyStateChanged();
}
void WebMediaPlayerImpl::Destroy() {
DCHECK(!main_loop_ || main_loop_ == MessageLoop::current());
// If |main_loop_| has already stopped, do nothing here.
if (!main_loop_) {
// This may happen if this function was already called by the
// DestructionObserver override when the thread running this player was
// stopped. The pipeline should have been shut down.
DCHECK(!chunk_demuxer_);
DCHECK(!message_loop_factory_);
DCHECK(!proxy_);
return;
}
// Tell the data source to abort any pending reads so that the pipeline is
// not blocked when issuing stop commands to the other filters.
suppress_destruction_errors_ = true;
if (proxy_) {
proxy_->AbortDataSource();
if (chunk_demuxer_) {
chunk_demuxer_->Shutdown();
chunk_demuxer_ = NULL;
}
}
// Make sure to kill the pipeline so there's no more media threads running.
// Note: stopping the pipeline might block for a long time.
base::WaitableEvent waiter(false, false);
DLOG(INFO) << "Trying to stop media pipeline.";
pipeline_->Stop(
base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter)));
DLOG(INFO) << "Media pipeline stopped.";
waiter.Wait();
message_loop_factory_.reset();
// And then detach the proxy, it may live on the render thread for a little
// longer until all the tasks are finished.
if (proxy_) {
proxy_->Detach();
proxy_ = NULL;
}
}
void WebMediaPlayerImpl::GetMediaTimeAndSeekingState(
base::TimeDelta* media_time,
bool* is_seeking) const {
DCHECK(media_time);
DCHECK(is_seeking);
*media_time = pipeline_->GetMediaTime();
*is_seeking = state_.seeking;
}
WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
DCHECK_EQ(main_loop_, MessageLoop::current());
DCHECK(client_);
return client_;
}
void WebMediaPlayerImpl::OnDurationChanged() {
if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing)
return;
GetClient()->DurationChanged();
}
} // namespace media