blob: 452a9e602fa7cae83969133558bd79d95fe76fbf [file] [log] [blame]
// Copyright (c) 2008-2009 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/glue/webmediaplayer_impl.h"
#include "base/command_line.h"
#include "googleurl/src/gurl.h"
#include "media/filters/ffmpeg_audio_decoder.h"
#include "media/filters/ffmpeg_demuxer.h"
#include "media/filters/ffmpeg_video_decoder.h"
#include "media/filters/null_audio_renderer.h"
#include "webkit/api/public/WebRect.h"
#include "webkit/api/public/WebSize.h"
#include "webkit/api/public/WebURL.h"
#include "webkit/glue/media/video_renderer_impl.h"
using WebKit::WebCanvas;
using WebKit::WebRect;
using WebKit::WebSize;
namespace {
// Limits the maximum outstanding repaints posted on render thread.
// This number of 50 is a guess, it does not take too much memory on the task
// queue but gives up a pretty good latency on repaint.
const int kMaxOutstandingRepaints = 50;
} // namespace
namespace webkit_glue {
/////////////////////////////////////////////////////////////////////////////
// WebMediaPlayerImpl::Proxy implementation
WebMediaPlayerImpl::Proxy::Proxy(MessageLoop* render_loop,
WebMediaPlayerImpl* webmediaplayer)
: render_loop_(render_loop),
webmediaplayer_(webmediaplayer),
outstanding_repaints_(0) {
DCHECK(render_loop_);
DCHECK(webmediaplayer_);
}
WebMediaPlayerImpl::Proxy::~Proxy() {
Detach();
}
void WebMediaPlayerImpl::Proxy::Repaint() {
AutoLock auto_lock(lock_);
if (outstanding_repaints_ < kMaxOutstandingRepaints) {
++outstanding_repaints_;
render_loop_->PostTask(FROM_HERE,
NewRunnableMethod(this, &WebMediaPlayerImpl::Proxy::RepaintTask));
}
}
void WebMediaPlayerImpl::Proxy::TimeChanged() {
render_loop_->PostTask(FROM_HERE,
NewRunnableMethod(this, &WebMediaPlayerImpl::Proxy::TimeChangedTask));
}
void WebMediaPlayerImpl::Proxy::NetworkStateChanged(
WebKit::WebMediaPlayer::NetworkState state) {
render_loop_->PostTask(FROM_HERE,
NewRunnableMethod(this,
&WebMediaPlayerImpl::Proxy::NetworkStateChangedTask,
state));
}
void WebMediaPlayerImpl::Proxy::ReadyStateChanged(
WebKit::WebMediaPlayer::ReadyState state) {
render_loop_->PostTask(FROM_HERE,
NewRunnableMethod(this,
&WebMediaPlayerImpl::Proxy::ReadyStateChangedTask,
state));
}
void WebMediaPlayerImpl::Proxy::RepaintTask() {
DCHECK(MessageLoop::current() == render_loop_);
{
AutoLock auto_lock(lock_);
--outstanding_repaints_;
DCHECK_GE(outstanding_repaints_, 0);
}
if (webmediaplayer_)
webmediaplayer_->Repaint();
}
void WebMediaPlayerImpl::Proxy::TimeChangedTask() {
DCHECK(MessageLoop::current() == render_loop_);
if (webmediaplayer_)
webmediaplayer_->TimeChanged();
}
void WebMediaPlayerImpl::Proxy::NetworkStateChangedTask(
WebKit::WebMediaPlayer::NetworkState state) {
DCHECK(MessageLoop::current() == render_loop_);
if (webmediaplayer_)
webmediaplayer_->SetNetworkState(state);
}
void WebMediaPlayerImpl::Proxy::ReadyStateChangedTask(
WebKit::WebMediaPlayer::ReadyState state) {
DCHECK(MessageLoop::current() == render_loop_);
if (webmediaplayer_)
webmediaplayer_->SetReadyState(state);
}
void WebMediaPlayerImpl::Proxy::SetVideoRenderer(
VideoRendererImpl* video_renderer) {
video_renderer_ = video_renderer;
}
void WebMediaPlayerImpl::Proxy::Paint(skia::PlatformCanvas* canvas,
const gfx::Rect& dest_rect) {
DCHECK(MessageLoop::current() == render_loop_);
if (video_renderer_) {
video_renderer_->Paint(canvas, dest_rect);
}
}
void WebMediaPlayerImpl::Proxy::SetSize(const gfx::Rect& rect) {
DCHECK(MessageLoop::current() == render_loop_);
if (video_renderer_) {
video_renderer_->SetRect(rect);
}
}
void WebMediaPlayerImpl::Proxy::Detach() {
DCHECK(MessageLoop::current() == render_loop_);
webmediaplayer_ = NULL;
video_renderer_ = NULL;
}
void WebMediaPlayerImpl::Proxy::PipelineInitializationCallback(bool success) {
if (success) {
// Since we have initialized the pipeline, say we have everything.
// TODO(hclam): change this to report the correct status. Should also post
// a task to call to |webmediaplayer_|.
ReadyStateChanged(WebKit::WebMediaPlayer::HaveMetadata);
ReadyStateChanged(WebKit::WebMediaPlayer::HaveEnoughData);
NetworkStateChanged(WebKit::WebMediaPlayer::Loaded);
} else {
// TODO(hclam): should use pipeline_.GetError() to determine the state
// properly and reports error using MediaError.
// WebKit uses FormatError to indicate an error for bogus URL or bad file.
// Since we are at the initialization stage we can safely treat every error
// as format error. Should post a task to call to |webmediaplayer_|.
NetworkStateChanged(WebKit::WebMediaPlayer::FormatError);
}
}
void WebMediaPlayerImpl::Proxy::PipelineSeekCallback(bool success) {
if (success)
TimeChanged();
}
/////////////////////////////////////////////////////////////////////////////
// WebMediaPlayerImpl implementation
WebMediaPlayerImpl::WebMediaPlayerImpl(WebKit::WebMediaPlayerClient* client,
media::FilterFactoryCollection* factory)
: network_state_(WebKit::WebMediaPlayer::Empty),
ready_state_(WebKit::WebMediaPlayer::HaveNothing),
main_loop_(NULL),
filter_factory_(factory),
client_(client) {
// Saves the current message loop.
DCHECK(!main_loop_);
main_loop_ = MessageLoop::current();
// Also we want to be notified of |main_loop_| destruction.
main_loop_->AddDestructionObserver(this);
// Creates the proxy.
proxy_ = new Proxy(main_loop_, this);
// Add in the default filter factories.
filter_factory_->AddFactory(media::FFmpegDemuxer::CreateFilterFactory());
filter_factory_->AddFactory(media::FFmpegAudioDecoder::CreateFactory());
filter_factory_->AddFactory(media::FFmpegVideoDecoder::CreateFactory());
filter_factory_->AddFactory(media::NullAudioRenderer::CreateFilterFactory());
filter_factory_->AddFactory(VideoRendererImpl::CreateFactory(proxy_));
}
WebMediaPlayerImpl::~WebMediaPlayerImpl() {
Destroy();
// Finally tell the |main_loop_| we don't want to be notified of destruction
// event.
if (main_loop_) {
main_loop_->RemoveDestructionObserver(this);
}
}
void WebMediaPlayerImpl::load(const WebKit::WebURL& url) {
DCHECK(MessageLoop::current() == main_loop_);
DCHECK(proxy_);
// Initialize the pipeline.
SetNetworkState(WebKit::WebMediaPlayer::Loading);
SetReadyState(WebKit::WebMediaPlayer::HaveNothing);
pipeline_.Start(
filter_factory_.get(),
url.spec(),
NewCallback(proxy_.get(),
&WebMediaPlayerImpl::Proxy::PipelineInitializationCallback));
}
void WebMediaPlayerImpl::cancelLoad() {
DCHECK(MessageLoop::current() == main_loop_);
}
void WebMediaPlayerImpl::play() {
DCHECK(MessageLoop::current() == main_loop_);
// TODO(hclam): We should restore the previous playback rate rather than
// having it at 1.0.
pipeline_.SetPlaybackRate(1.0f);
}
void WebMediaPlayerImpl::pause() {
DCHECK(MessageLoop::current() == main_loop_);
pipeline_.SetPlaybackRate(0.0f);
}
void WebMediaPlayerImpl::seek(float seconds) {
DCHECK(MessageLoop::current() == main_loop_);
// Try to preserve as much accuracy as possible.
float microseconds = seconds * base::Time::kMicrosecondsPerSecond;
if (seconds != 0)
pipeline_.Seek(
base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds)),
NewCallback(proxy_.get(),
&WebMediaPlayerImpl::Proxy::PipelineSeekCallback));
}
void WebMediaPlayerImpl::setEndTime(float seconds) {
DCHECK(MessageLoop::current() == main_loop_);
// TODO(hclam): add method call when it has been implemented.
return;
}
void WebMediaPlayerImpl::setRate(float rate) {
DCHECK(MessageLoop::current() == main_loop_);
pipeline_.SetPlaybackRate(rate);
}
void WebMediaPlayerImpl::setVolume(float volume) {
DCHECK(MessageLoop::current() == main_loop_);
pipeline_.SetVolume(volume);
}
void WebMediaPlayerImpl::setVisible(bool visible) {
DCHECK(MessageLoop::current() == main_loop_);
// TODO(hclam): add appropriate method call when pipeline has it implemented.
return;
}
bool WebMediaPlayerImpl::setAutoBuffer(bool autoBuffer) {
DCHECK(MessageLoop::current() == main_loop_);
return false;
}
bool WebMediaPlayerImpl::totalBytesKnown() {
DCHECK(MessageLoop::current() == main_loop_);
return pipeline_.GetTotalBytes() != 0;
}
bool WebMediaPlayerImpl::hasVideo() const {
DCHECK(MessageLoop::current() == main_loop_);
size_t width, height;
pipeline_.GetVideoSize(&width, &height);
return width != 0 && height != 0;
}
WebKit::WebSize WebMediaPlayerImpl::naturalSize() const {
DCHECK(MessageLoop::current() == main_loop_);
size_t width, height;
pipeline_.GetVideoSize(&width, &height);
return WebKit::WebSize(width, height);
}
bool WebMediaPlayerImpl::paused() const {
DCHECK(MessageLoop::current() == main_loop_);
return pipeline_.GetPlaybackRate() == 0.0f;
}
bool WebMediaPlayerImpl::seeking() const {
DCHECK(MessageLoop::current() == main_loop_);
return false;
}
float WebMediaPlayerImpl::duration() const {
DCHECK(MessageLoop::current() == main_loop_);
return static_cast<float>(pipeline_.GetDuration().InSecondsF());
}
float WebMediaPlayerImpl::currentTime() const {
DCHECK(MessageLoop::current() == main_loop_);
return static_cast<float>(pipeline_.GetTime().InSecondsF());
}
int WebMediaPlayerImpl::dataRate() const {
DCHECK(MessageLoop::current() == main_loop_);
// TODO(hclam): Add this method call if pipeline has it in the interface.
return 0;
}
float WebMediaPlayerImpl::maxTimeBuffered() const {
DCHECK(MessageLoop::current() == main_loop_);
return static_cast<float>(pipeline_.GetBufferedTime().InSecondsF());
}
float WebMediaPlayerImpl::maxTimeSeekable() const {
DCHECK(MessageLoop::current() == main_loop_);
// TODO(scherkus): move this logic down into the pipeline.
if (pipeline_.GetTotalBytes() == 0) {
return 0.0f;
}
double total_bytes = static_cast<double>(pipeline_.GetTotalBytes());
double buffered_bytes = static_cast<double>(pipeline_.GetBufferedBytes());
double duration = static_cast<double>(pipeline_.GetDuration().InSecondsF());
return static_cast<float>(duration * (buffered_bytes / total_bytes));
}
unsigned long long WebMediaPlayerImpl::bytesLoaded() const {
DCHECK(MessageLoop::current() == main_loop_);
return pipeline_.GetBufferedBytes();
}
unsigned long long WebMediaPlayerImpl::totalBytes() const {
DCHECK(MessageLoop::current() == main_loop_);
return pipeline_.GetTotalBytes();
}
void WebMediaPlayerImpl::setSize(const WebSize& size) {
DCHECK(MessageLoop::current() == main_loop_);
DCHECK(proxy_);
proxy_->SetSize(gfx::Rect(0, 0, size.width, size.height));
}
void WebMediaPlayerImpl::paint(WebCanvas* canvas,
const WebRect& rect) {
DCHECK(MessageLoop::current() == main_loop_);
DCHECK(proxy_);
proxy_->Paint(canvas, rect);
}
void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() {
Destroy();
main_loop_ = NULL;
}
void WebMediaPlayerImpl::Repaint() {
DCHECK(MessageLoop::current() == main_loop_);
GetClient()->repaint();
}
void WebMediaPlayerImpl::TimeChanged() {
DCHECK(MessageLoop::current() == main_loop_);
GetClient()->timeChanged();
}
void WebMediaPlayerImpl::SetNetworkState(
WebKit::WebMediaPlayer::NetworkState state) {
DCHECK(MessageLoop::current() == main_loop_);
if (network_state_ != state) {
network_state_ = state;
GetClient()->networkStateChanged();
}
}
void WebMediaPlayerImpl::SetReadyState(
WebKit::WebMediaPlayer::ReadyState state) {
DCHECK(MessageLoop::current() == main_loop_);
if (ready_state_ != state) {
ready_state_ = state;
GetClient()->readyStateChanged();
}
}
void WebMediaPlayerImpl::Destroy() {
DCHECK(MessageLoop::current() == main_loop_);
// Make sure to kill the pipeline so there's no more media threads running.
// TODO(hclam): stopping the pipeline is synchronous so it might block
// stopping for a long time.
pipeline_.Stop();
// 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(MessageLoop::current() == main_loop_);
DCHECK(client_);
return client_;
}
} // namespace webkit_glue