| // 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 |