blob: a8b4ab9743bec0b2913cb96cf4ac26825514b07b [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 "webkit/media/webmediaplayer_impl.h"
#include <limits>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.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/null_audio_sink.h"
#include "media/base/filter_collection.h"
#include "media/base/limits.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/pipeline.h"
#include "media/base/video_frame.h"
#include "media/filters/audio_renderer_impl.h"
#include "media/filters/video_renderer_base.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebVideoFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebRect.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebSize.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURL.h"
#include "v8/include/v8.h"
#include "webkit/media/buffered_data_source.h"
#include "webkit/media/filter_helpers.h"
#include "webkit/media/webmediaplayer_delegate.h"
#include "webkit/media/webmediaplayer_proxy.h"
#include "webkit/media/webmediaplayer_util.h"
#include "webkit/media/webvideoframe_impl.h"
#include "webkit/plugins/ppapi/ppapi_webplugin_impl.h"
using WebKit::WebCanvas;
using WebKit::WebMediaPlayer;
using WebKit::WebRect;
using WebKit::WebSize;
using WebKit::WebString;
using media::PipelineStatus;
namespace {
// Amount of extra memory used by each player instance reported to V8.
// It is not exact number -- first, it differs on different platforms,
// and second, it is very hard to calculate. Instead, use some arbitrary
// value that will cause garbage collection from time to time. We don't want
// it to happen on every allocation, but don't want 5k players to sit in memory
// either. Looks that chosen constant achieves both goals, at least for audio
// objects. (Do not worry about video objects yet, JS programs do not create
// thousands of them...)
const int kPlayerExtraMemory = 1024 * 1024;
// 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;
} // namespace
namespace webkit_media {
#define COMPILE_ASSERT_MATCHING_ENUM(name) \
COMPILE_ASSERT(static_cast<int>(WebKit::WebMediaPlayer::CORSMode ## name) == \
static_cast<int>(BufferedResourceLoader::k ## name), \
mismatching_enums)
COMPILE_ASSERT_MATCHING_ENUM(Unspecified);
COMPILE_ASSERT_MATCHING_ENUM(Anonymous);
COMPILE_ASSERT_MATCHING_ENUM(UseCredentials);
#undef COMPILE_ASSERT_MATCHING_ENUM
static WebKit::WebTimeRanges ConvertToWebTimeRanges(
const media::Ranges<base::TimeDelta>& ranges) {
WebKit::WebTimeRanges result(ranges.size());
for (size_t i = 0; i < ranges.size(); i++) {
result[i].start = ranges.start(i).InSecondsF();
result[i].end = ranges.end(i).InSecondsF();
}
return result;
}
WebMediaPlayerImpl::WebMediaPlayerImpl(
WebKit::WebFrame* frame,
WebKit::WebMediaPlayerClient* client,
base::WeakPtr<WebMediaPlayerDelegate> delegate,
media::FilterCollection* collection,
WebKit::WebAudioSourceProvider* audio_source_provider,
media::AudioRendererSink* audio_renderer_sink,
media::MessageLoopFactory* message_loop_factory,
MediaStreamClient* media_stream_client,
media::MediaLog* media_log)
: frame_(frame),
network_state_(WebMediaPlayer::NetworkStateEmpty),
ready_state_(WebMediaPlayer::ReadyStateHaveNothing),
main_loop_(MessageLoop::current()),
filter_collection_(collection),
started_(false),
message_loop_factory_(message_loop_factory),
paused_(true),
seeking_(false),
playback_rate_(0.0f),
pending_seek_(false),
pending_seek_seconds_(0.0f),
client_(client),
proxy_(new WebMediaPlayerProxy(main_loop_->message_loop_proxy(), this)),
delegate_(delegate),
media_stream_client_(media_stream_client),
media_log_(media_log),
accelerated_compositing_reported_(false),
incremented_externally_allocated_memory_(false),
audio_source_provider_(audio_source_provider),
audio_renderer_sink_(audio_renderer_sink),
is_local_source_(false),
decryptor_(proxy_.get(), client, frame) {
media_log_->AddEvent(
media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_CREATED));
MessageLoop* pipeline_message_loop =
message_loop_factory_->GetMessageLoop("PipelineThread");
pipeline_ = new media::Pipeline(pipeline_message_loop, media_log_);
// Let V8 know we started new thread if we did not did it yet.
// Made separate task to avoid deletion of player currently being created.
// Also, delaying GC until after player starts gets rid of starting lag --
// collection happens in parallel with playing.
//
// TODO(enal): remove when we get rid of per-audio-stream thread.
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&WebMediaPlayerImpl::IncrementExternallyAllocatedMemory,
AsWeakPtr()));
// Also we want to be notified of |main_loop_| destruction.
main_loop_->AddDestructionObserver(this);
// Create default video renderer.
scoped_refptr<media::VideoRendererBase> video_renderer =
new media::VideoRendererBase(
base::Bind(&WebMediaPlayerProxy::Repaint, proxy_),
base::Bind(&WebMediaPlayerProxy::SetOpaque, proxy_.get()),
true);
filter_collection_->AddVideoRenderer(video_renderer);
proxy_->set_frame_provider(video_renderer);
// Create default audio renderer.
filter_collection_->AddAudioRenderer(
new media::AudioRendererImpl(new media::NullAudioSink()));
}
WebMediaPlayerImpl::~WebMediaPlayerImpl() {
DCHECK_EQ(main_loop_, MessageLoop::current());
Destroy();
media_log_->AddEvent(
media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
if (delegate_)
delegate_->PlayerGone(this);
// 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::load(const WebKit::WebURL& url, CORSMode cors_mode) {
DCHECK_EQ(main_loop_, MessageLoop::current());
GURL gurl(url);
UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(gurl), kMaxURLScheme);
// Handle any volume/preload changes that occured before load().
setVolume(GetClient()->volume());
setPreload(GetClient()->preload());
SetNetworkState(WebMediaPlayer::NetworkStateLoading);
SetReadyState(WebMediaPlayer::ReadyStateHaveNothing);
media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec()));
// Media streams pipelines can start immediately.
if (BuildMediaStreamCollection(url, media_stream_client_,
message_loop_factory_.get(),
filter_collection_.get())) {
StartPipeline();
return;
}
// Media source pipelines can start immediately.
scoped_refptr<media::FFmpegVideoDecoder> video_decoder;
if (BuildMediaSourceCollection(url, GetClient()->sourceURL(), proxy_,
message_loop_factory_.get(),
filter_collection_.get(),
&decryptor_,
&video_decoder)) {
proxy_->set_video_decoder(video_decoder);
StartPipeline();
return;
}
// Otherwise it's a regular request which requires resolving the URL first.
proxy_->set_data_source(
new BufferedDataSource(main_loop_, frame_, media_log_,
base::Bind(&WebMediaPlayerImpl::NotifyDownloading,
base::Unretained(this))));
proxy_->data_source()->Initialize(
url, static_cast<BufferedResourceLoader::CORSMode>(cors_mode),
base::Bind(
&WebMediaPlayerImpl::DataSourceInitialized,
base::Unretained(this), gurl));
is_local_source_ = !gurl.SchemeIs("http") && !gurl.SchemeIs("https");
BuildDefaultCollection(proxy_->data_source(),
message_loop_factory_.get(),
filter_collection_.get(),
&decryptor_,
&video_decoder);
proxy_->set_video_decoder(video_decoder);
}
void WebMediaPlayerImpl::cancelLoad() {
DCHECK_EQ(main_loop_, MessageLoop::current());
}
void WebMediaPlayerImpl::play() {
DCHECK_EQ(main_loop_, MessageLoop::current());
paused_ = false;
pipeline_->SetPlaybackRate(playback_rate_);
media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY));
if (delegate_)
delegate_->DidPlay(this);
}
void WebMediaPlayerImpl::pause() {
DCHECK_EQ(main_loop_, MessageLoop::current());
paused_ = true;
pipeline_->SetPlaybackRate(0.0f);
paused_time_ = pipeline_->GetMediaTime();
media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE));
if (delegate_)
delegate_->DidPause(this);
}
bool WebMediaPlayerImpl::supportsFullscreen() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return true;
}
bool WebMediaPlayerImpl::supportsSave() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return true;
}
void WebMediaPlayerImpl::seek(float seconds) {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (seeking_) {
pending_seek_ = true;
pending_seek_seconds_ = seconds;
return;
}
media_log_->AddEvent(media_log_->CreateSeekEvent(seconds));
base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds);
// Update our paused time.
if (paused_)
paused_time_ = seek_time;
seeking_ = true;
proxy_->DemuxerStartWaitingForSeek();
// Kick off the asynchronous seek!
pipeline_->Seek(
seek_time,
base::Bind(&WebMediaPlayerProxy::PipelineSeekCallback,
proxy_.get()));
}
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;
}
playback_rate_ = rate;
if (!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;
}
#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \
COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::webkit_name) == \
static_cast<int>(webkit_media::chromium_name), \
mismatching_enums)
COMPILE_ASSERT_MATCHING_ENUM(PreloadNone, NONE);
COMPILE_ASSERT_MATCHING_ENUM(PreloadMetaData, METADATA);
COMPILE_ASSERT_MATCHING_ENUM(PreloadAuto, AUTO);
#undef COMPILE_ASSERT_MATCHING_ENUM
void WebMediaPlayerImpl::setPreload(WebMediaPlayer::Preload preload) {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (proxy_ && proxy_->data_source()) {
// XXX: Why do I need to use webkit_media:: prefix? clang complains!
proxy_->data_source()->SetPreload(
static_cast<webkit_media::Preload>(preload));
}
}
bool WebMediaPlayerImpl::totalBytesKnown() {
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();
}
WebKit::WebSize WebMediaPlayerImpl::naturalSize() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
gfx::Size size;
pipeline_->GetNaturalVideoSize(&size);
return WebKit::WebSize(size);
}
bool WebMediaPlayerImpl::paused() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return pipeline_->GetPlaybackRate() == 0.0f;
}
bool WebMediaPlayerImpl::seeking() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing)
return false;
return seeking_;
}
float WebMediaPlayerImpl::duration() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
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 == media::kInfiniteDuration())
return std::numeric_limits<float>::infinity();
return static_cast<float>(duration.InSecondsF());
}
float WebMediaPlayerImpl::currentTime() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (paused_)
return static_cast<float>(paused_time_.InSecondsF());
return static_cast<float>(pipeline_->GetMediaTime().InSecondsF());
}
int WebMediaPlayerImpl::dataRate() 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::networkState() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return network_state_;
}
WebMediaPlayer::ReadyState WebMediaPlayerImpl::readyState() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return ready_state_;
}
const WebKit::WebTimeRanges& WebMediaPlayerImpl::buffered() {
DCHECK_EQ(main_loop_, MessageLoop::current());
WebKit::WebTimeRanges web_ranges(
ConvertToWebTimeRanges(pipeline_->GetBufferedTimeRanges()));
buffered_.swap(web_ranges);
return buffered_;
}
float WebMediaPlayerImpl::maxTimeSeekable() 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::totalBytes() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
return pipeline_->GetTotalBytes();
}
void WebMediaPlayerImpl::setSize(const WebSize& size) {
DCHECK_EQ(main_loop_, MessageLoop::current());
// Don't need to do anything as we use the dimensions passed in via paint().
}
// This variant (without alpha) is just present during staging of this API
// change. Later we will again only have one virtual paint().
void WebMediaPlayerImpl::paint(WebKit::WebCanvas* canvas,
const WebKit::WebRect& rect) {
paint(canvas, rect, 0xFF);
}
void WebMediaPlayerImpl::paint(WebCanvas* canvas,
const WebRect& rect,
uint8_t alpha) {
DCHECK_EQ(main_loop_, MessageLoop::current());
DCHECK(proxy_);
if (!accelerated_compositing_reported_) {
accelerated_compositing_reported_ = true;
// Normally paint() is only called in non-accelerated rendering, but there
// are exceptions such as webgl where compositing is used in the WebView but
// video frames are still rendered to a canvas.
UMA_HISTOGRAM_BOOLEAN(
"Media.AcceleratedCompositingActive",
frame_->view()->isAcceleratedCompositingActive());
}
proxy_->Paint(canvas, rect, alpha);
}
bool WebMediaPlayerImpl::hasSingleSecurityOrigin() const {
if (proxy_)
return proxy_->HasSingleOrigin();
return true;
}
bool WebMediaPlayerImpl::didPassCORSAccessCheck() const {
return proxy_ && proxy_->DidPassCORSAccessCheck();
}
WebMediaPlayer::MovieLoadType WebMediaPlayerImpl::movieLoadType() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
// Disable seeking while streaming.
if (proxy_ && proxy_->data_source() && proxy_->data_source()->IsStreaming())
return WebMediaPlayer::MovieLoadTypeLiveStream;
return WebMediaPlayer::MovieLoadTypeUnknown;
}
float WebMediaPlayerImpl::mediaTimeForTimeValue(float timeValue) const {
return ConvertSecondsToTimestamp(timeValue).InSecondsF();
}
unsigned WebMediaPlayerImpl::decodedFrameCount() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
media::PipelineStatistics stats = pipeline_->GetStatistics();
return stats.video_frames_decoded;
}
unsigned WebMediaPlayerImpl::droppedFrameCount() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
media::PipelineStatistics stats = pipeline_->GetStatistics();
return stats.video_frames_dropped;
}
unsigned WebMediaPlayerImpl::audioDecodedByteCount() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
media::PipelineStatistics stats = pipeline_->GetStatistics();
return stats.audio_bytes_decoded;
}
unsigned WebMediaPlayerImpl::videoDecodedByteCount() const {
DCHECK_EQ(main_loop_, MessageLoop::current());
media::PipelineStatistics stats = pipeline_->GetStatistics();
return stats.video_bytes_decoded;
}
WebKit::WebVideoFrame* WebMediaPlayerImpl::getCurrentFrame() {
scoped_refptr<media::VideoFrame> video_frame;
proxy_->GetCurrentFrame(&video_frame);
if (video_frame.get())
return new WebVideoFrameImpl(video_frame);
return NULL;
}
void WebMediaPlayerImpl::putCurrentFrame(
WebKit::WebVideoFrame* web_video_frame) {
if (!accelerated_compositing_reported_) {
accelerated_compositing_reported_ = true;
DCHECK(frame_->view()->isAcceleratedCompositingActive());
UMA_HISTOGRAM_BOOLEAN("Media.AcceleratedCompositingActive", true);
}
if (web_video_frame) {
scoped_refptr<media::VideoFrame> video_frame(
WebVideoFrameImpl::toVideoFrame(web_video_frame));
proxy_->PutCurrentFrame(video_frame);
delete web_video_frame;
} else {
proxy_->PutCurrentFrame(NULL);
}
}
#define COMPILE_ASSERT_MATCHING_STATUS_ENUM(webkit_name, chromium_name) \
COMPILE_ASSERT(static_cast<int>(WebKit::WebMediaPlayer::webkit_name) == \
static_cast<int>(media::ChunkDemuxer::chromium_name), \
mismatching_status_enums)
COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddIdStatusOk, kOk);
COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddIdStatusNotSupported, kNotSupported);
COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddIdStatusReachedIdLimit, kReachedIdLimit);
WebKit::WebMediaPlayer::AddIdStatus WebMediaPlayerImpl::sourceAddId(
const WebKit::WebString& id,
const WebKit::WebString& type) {
DCHECK_EQ(main_loop_, MessageLoop::current());
WebKit::WebString kDefaultSourceType("video/webm; codecs=\"vp8, vorbis\"");
if (type != kDefaultSourceType)
return WebKit::WebMediaPlayer::AddIdStatusNotSupported;
WebKit::WebVector<WebKit::WebString> codecs(static_cast<size_t>(2));
codecs[0] = "vp8";
codecs[1] = "vorbis";
return sourceAddId(id, "video/webm", codecs);
}
WebKit::WebMediaPlayer::AddIdStatus WebMediaPlayerImpl::sourceAddId(
const WebKit::WebString& id,
const WebKit::WebString& type,
const WebKit::WebVector<WebKit::WebString>& 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].utf8().data();
return static_cast<WebKit::WebMediaPlayer::AddIdStatus>(
proxy_->DemuxerAddId(id.utf8().data(), type.utf8().data(),
new_codecs));
}
bool WebMediaPlayerImpl::sourceRemoveId(const WebKit::WebString& id) {
DCHECK(!id.isEmpty());
proxy_->DemuxerRemoveId(id.utf8().data());
return true;
}
WebKit::WebTimeRanges WebMediaPlayerImpl::sourceBuffered(
const WebKit::WebString& id) {
return ConvertToWebTimeRanges(proxy_->DemuxerBufferedRange(id.utf8().data()));
}
bool WebMediaPlayerImpl::sourceAppend(const unsigned char* data,
unsigned length) {
return sourceAppend(WebKit::WebString::fromUTF8("DefaultSourceId"),
data, length);
}
bool WebMediaPlayerImpl::sourceAppend(const WebKit::WebString& id,
const unsigned char* data,
unsigned length) {
DCHECK_EQ(main_loop_, MessageLoop::current());
return proxy_->DemuxerAppend(id.utf8().data(), data, length);
}
bool WebMediaPlayerImpl::sourceAbort(const WebKit::WebString& id) {
proxy_->DemuxerAbort(id.utf8().data());
return true;
}
void WebMediaPlayerImpl::sourceEndOfStream(
WebMediaPlayer::EndOfStreamStatus status) {
DCHECK_EQ(main_loop_, MessageLoop::current());
media::PipelineStatus pipeline_status = media::PIPELINE_OK;
switch (status) {
case WebMediaPlayer::EndOfStreamStatusNoError:
break;
case WebMediaPlayer::EndOfStreamStatusNetworkError:
pipeline_status = media::PIPELINE_ERROR_NETWORK;
break;
case WebMediaPlayer::EndOfStreamStatusDecodeError:
pipeline_status = media::PIPELINE_ERROR_DECODE;
break;
default:
NOTIMPLEMENTED();
}
proxy_->DemuxerEndOfStream(pipeline_status);
}
WebKit::WebMediaPlayer::MediaKeyException
WebMediaPlayerImpl::generateKeyRequest(const WebString& key_system,
const unsigned char* init_data,
unsigned init_data_length) {
if (!IsSupportedKeySystem(key_system))
return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
// We do not support run-time switching between key systems for now.
if (current_key_system_.isEmpty())
current_key_system_ = key_system;
else if (key_system != current_key_system_)
return WebKit::WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
DVLOG(1) << "generateKeyRequest: " << key_system.utf8().data() << ": "
<< std::string(reinterpret_cast<const char*>(init_data),
static_cast<size_t>(init_data_length));
decryptor_.GenerateKeyRequest(key_system.utf8(),
init_data, init_data_length);
return WebKit::WebMediaPlayer::MediaKeyExceptionNoError;
}
WebKit::WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::addKey(
const WebString& key_system,
const unsigned char* key,
unsigned key_length,
const unsigned char* init_data,
unsigned init_data_length,
const WebString& session_id) {
DCHECK(key);
DCHECK_GT(key_length, 0u);
if (!IsSupportedKeySystem(key_system))
return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
if (current_key_system_.isEmpty() || key_system != current_key_system_)
return WebKit::WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
DVLOG(1) << "addKey: " << key_system.utf8().data() << ": "
<< std::string(reinterpret_cast<const char*>(key),
static_cast<size_t>(key_length)) << ", "
<< std::string(reinterpret_cast<const char*>(init_data),
static_cast<size_t>(init_data_length))
<< " [" << session_id.utf8().data() << "]";
decryptor_.AddKey(key_system.utf8(), key, key_length,
init_data, init_data_length, session_id.utf8());
return WebKit::WebMediaPlayer::MediaKeyExceptionNoError;
}
WebKit::WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::cancelKeyRequest(
const WebString& key_system,
const WebString& session_id) {
if (!IsSupportedKeySystem(key_system))
return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
if (current_key_system_.isEmpty() || key_system != current_key_system_)
return WebKit::WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
decryptor_.CancelKeyRequest(key_system.utf8(), session_id.utf8());
return WebKit::WebMediaPlayer::MediaKeyExceptionNoError;
}
void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() {
Destroy();
main_loop_ = NULL;
}
void WebMediaPlayerImpl::Repaint() {
DCHECK_EQ(main_loop_, MessageLoop::current());
GetClient()->repaint();
}
void WebMediaPlayerImpl::OnPipelineInitialize(PipelineStatus status) {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (status != media::PIPELINE_OK) {
// Any error that occurs before the pipeline can initialize should be
// considered a format error.
SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
// Repaint to trigger UI update.
Repaint();
return;
}
if (!hasVideo())
GetClient()->disableAcceleratedCompositing();
if (is_local_source_)
SetNetworkState(WebMediaPlayer::NetworkStateLoaded);
SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
// Fire canplaythrough immediately after playback begins because of
// crbug.com/106480.
// TODO(vrk): set ready state to HaveFutureData when bug above is fixed.
SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
// Repaint to trigger UI update.
Repaint();
}
void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) {
DCHECK_EQ(main_loop_, MessageLoop::current());
seeking_ = false;
if (pending_seek_) {
pending_seek_ = false;
seek(pending_seek_seconds_);
return;
}
if (status != media::PIPELINE_OK) {
OnPipelineError(status);
return;
}
// Update our paused time.
if (paused_)
paused_time_ = pipeline_->GetMediaTime();
GetClient()->timeChanged();
}
void WebMediaPlayerImpl::OnPipelineEnded(PipelineStatus status) {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (status != media::PIPELINE_OK) {
OnPipelineError(status);
return;
}
GetClient()->timeChanged();
}
void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
DCHECK_EQ(main_loop_, MessageLoop::current());
switch (error) {
case media::PIPELINE_OK:
NOTREACHED() << "PIPELINE_OK isn't an error!";
break;
case media::PIPELINE_ERROR_NETWORK:
case media::PIPELINE_ERROR_READ:
SetNetworkState(WebMediaPlayer::NetworkStateNetworkError);
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 media::PIPELINE_ERROR_INITIALIZATION_FAILED:
case media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING:
case media::PIPELINE_ERROR_COULD_NOT_RENDER:
case media::PIPELINE_ERROR_URL_NOT_FOUND:
case media::DEMUXER_ERROR_COULD_NOT_OPEN:
case media::DEMUXER_ERROR_COULD_NOT_PARSE:
case media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS:
case media::DECODER_ERROR_NOT_SUPPORTED:
SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
break;
case media::PIPELINE_ERROR_DECODE:
case media::PIPELINE_ERROR_ABORT:
case media::PIPELINE_ERROR_OPERATION_PENDING:
case media::PIPELINE_ERROR_INVALID_STATE:
SetNetworkState(WebMediaPlayer::NetworkStateDecodeError);
break;
case media::PIPELINE_ERROR_DECRYPT:
// Decrypt error.
// TODO(xhwang): Change to use NetworkStateDecryptError once it's added in
// Webkit (see http://crbug.com/124486).
SetNetworkState(WebMediaPlayer::NetworkStateDecodeError);
break;
case media::PIPELINE_STATUS_MAX:
NOTREACHED() << "PIPELINE_STATUS_MAX isn't a real error!";
break;
}
// Repaint to trigger UI update.
Repaint();
}
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());
GetClient()->keyAdded(WebString::fromUTF8(key_system),
WebString::fromUTF8(session_id));
}
void WebMediaPlayerImpl::OnNeedKey(const std::string& key_system,
const std::string& session_id,
scoped_array<uint8> init_data,
int init_data_size) {
DCHECK_EQ(main_loop_, MessageLoop::current());
GetClient()->keyNeeded(WebString::fromUTF8(key_system),
WebString::fromUTF8(session_id),
init_data.get(),
init_data_size);
}
#define COMPILE_ASSERT_MATCHING_ENUM(name) \
COMPILE_ASSERT(static_cast<int>(WebKit::WebMediaPlayerClient::name) == \
static_cast<int>(media::Decryptor::k ## name), \
mismatching_enums)
COMPILE_ASSERT_MATCHING_ENUM(UnknownError);
COMPILE_ASSERT_MATCHING_ENUM(ClientError);
COMPILE_ASSERT_MATCHING_ENUM(ServiceError);
COMPILE_ASSERT_MATCHING_ENUM(OutputError);
COMPILE_ASSERT_MATCHING_ENUM(HardwareChangeError);
COMPILE_ASSERT_MATCHING_ENUM(DomainError);
#undef COMPILE_ASSERT_MATCHING_ENUM
void WebMediaPlayerImpl::OnKeyError(const std::string& key_system,
const std::string& session_id,
media::Decryptor::KeyError error_code,
int system_code) {
DCHECK_EQ(main_loop_, MessageLoop::current());
GetClient()->keyError(
WebString::fromUTF8(key_system),
WebString::fromUTF8(session_id),
static_cast<WebKit::WebMediaPlayerClient::MediaKeyErrorCode>(error_code),
system_code);
}
void WebMediaPlayerImpl::OnKeyMessage(const std::string& key_system,
const std::string& session_id,
scoped_array<uint8> message,
int message_length,
const std::string& /* default_url */) {
DCHECK_EQ(main_loop_, MessageLoop::current());
GetClient()->keyMessage(WebString::fromUTF8(key_system),
WebString::fromUTF8(session_id),
message.get(),
message_length);
}
void WebMediaPlayerImpl::SetOpaque(bool opaque) {
DCHECK_EQ(main_loop_, MessageLoop::current());
GetClient()->setOpaque(opaque);
}
void WebMediaPlayerImpl::DataSourceInitialized(const GURL& gurl, bool success) {
DCHECK_EQ(main_loop_, MessageLoop::current());
if (!success) {
SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
Repaint();
return;
}
StartPipeline();
}
void WebMediaPlayerImpl::NotifyDownloading(bool is_downloading) {
if (!is_downloading && network_state_ == WebMediaPlayer::NetworkStateLoading)
SetNetworkState(WebMediaPlayer::NetworkStateIdle);
else if (is_downloading && network_state_ == WebMediaPlayer::NetworkStateIdle)
SetNetworkState(WebMediaPlayer::NetworkStateLoading);
media_log_->AddEvent(
media_log_->CreateBooleanEvent(
media::MediaLogEvent::NETWORK_ACTIVITY_SET,
"is_downloading_data", is_downloading));
}
void WebMediaPlayerImpl::StartPipeline() {
started_ = true;
pipeline_->Start(
filter_collection_.Pass(),
base::Bind(&WebMediaPlayerProxy::PipelineEndedCallback, proxy_.get()),
base::Bind(&WebMediaPlayerProxy::PipelineErrorCallback, proxy_.get()),
base::Bind(&WebMediaPlayerProxy::PipelineInitializationCallback,
proxy_.get()));
}
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;
ready_state_ = state;
// Always notify to ensure client has the latest value.
GetClient()->readyStateChanged();
}
void WebMediaPlayerImpl::Destroy() {
DCHECK_EQ(main_loop_, MessageLoop::current());
// Tell the data source to abort any pending reads so that the pipeline is
// not blocked when issuing stop commands to the other filters.
if (proxy_) {
proxy_->AbortDataSource();
proxy_->DemuxerShutdown();
}
// Make sure to kill the pipeline so there's no more media threads running.
// Note: stopping the pipeline might block for a long time.
if (started_) {
base::WaitableEvent waiter(false, false);
pipeline_->Stop(base::Bind(
&base::WaitableEvent::Signal, base::Unretained(&waiter)));
waiter.Wait();
started_ = false;
}
// Let V8 know we are not using extra resources anymore.
if (incremented_externally_allocated_memory_) {
v8::V8::AdjustAmountOfExternalAllocatedMemory(-kPlayerExtraMemory);
incremented_externally_allocated_memory_ = false;
}
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;
}
}
WebKit::WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
DCHECK_EQ(main_loop_, MessageLoop::current());
DCHECK(client_);
return client_;
}
WebKit::WebAudioSourceProvider* WebMediaPlayerImpl::audioSourceProvider() {
return audio_source_provider_;
}
void WebMediaPlayerImpl::IncrementExternallyAllocatedMemory() {
DCHECK_EQ(main_loop_, MessageLoop::current());
incremented_externally_allocated_memory_ = true;
v8::V8::AdjustAmountOfExternalAllocatedMemory(kPlayerExtraMemory);
}
} // namespace webkit_media