blob: b035cdc44dce5bd7224985b2df9bfad3fc6a7675 [file] [log] [blame]
// Copyright 2013 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 "content/renderer/media/webmediaplayer_ms.h"
#include <limits>
#include <string>
#include "base/bind.h"
#include "base/callback.h"
#include "cc/blink/context_provider_web_context.h"
#include "cc/blink/web_layer_impl.h"
#include "cc/layers/video_frame_provider_client_impl.h"
#include "cc/layers/video_layer.h"
#include "content/public/renderer/media_stream_audio_renderer.h"
#include "content/public/renderer/media_stream_renderer_factory.h"
#include "content/public/renderer/video_frame_provider.h"
#include "content/renderer/media/webmediaplayer_ms_compositor.h"
#include "content/renderer/render_frame_impl.h"
#include "content/renderer/render_thread_impl.h"
#include "gpu/blink/webgraphicscontext3d_impl.h"
#include "media/base/media_log.h"
#include "media/base/video_frame.h"
#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h"
#include "third_party/WebKit/public/platform/WebRect.h"
#include "third_party/WebKit/public/platform/WebSize.h"
#include "third_party/WebKit/public/platform/WebURL.h"
namespace content {
namespace {
static const float DEFAULT_AUDIO_VOLUME = 1.0f;
} // anonymous namespace
WebMediaPlayerMS::WebMediaPlayerMS(
blink::WebFrame* frame,
blink::WebMediaPlayerClient* client,
base::WeakPtr<media::WebMediaPlayerDelegate> delegate,
media::MediaLog* media_log,
scoped_ptr<MediaStreamRendererFactory> factory,
const scoped_refptr<base::SingleThreadTaskRunner>& compositor_task_runner,
const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
const scoped_refptr<base::TaskRunner>& worker_task_runner,
media::GpuVideoAcceleratorFactories* gpu_factories,
const blink::WebString& sink_id,
const blink::WebSecurityOrigin& security_origin)
: RenderFrameObserver(RenderFrame::FromWebFrame(frame)),
frame_(frame),
network_state_(WebMediaPlayer::NetworkStateEmpty),
ready_state_(WebMediaPlayer::ReadyStateHaveNothing),
buffered_(static_cast<size_t>(0)),
client_(client),
delegate_(delegate),
paused_(true),
render_frame_suspended_(false),
received_first_frame_(false),
media_log_(media_log),
renderer_factory_(factory.Pass()),
media_task_runner_(media_task_runner),
worker_task_runner_(worker_task_runner),
gpu_factories_(gpu_factories),
compositor_task_runner_(compositor_task_runner),
initial_audio_output_device_id_(sink_id.utf8()),
initial_security_origin_(security_origin) {
DVLOG(1) << __FUNCTION__;
DCHECK(client);
media_log_->AddEvent(
media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_CREATED));
}
WebMediaPlayerMS::~WebMediaPlayerMS() {
DVLOG(1) << __FUNCTION__;
DCHECK(thread_checker_.CalledOnValidThread());
if (compositor_)
compositor_task_runner_->DeleteSoon(FROM_HERE, compositor_.release());
get_client()->setWebLayer(nullptr);
if (video_frame_provider_)
video_frame_provider_->Stop();
if (audio_renderer_)
audio_renderer_->Stop();
media_log_->AddEvent(
media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
if (delegate_.get())
delegate_->PlayerGone(this);
}
void WebMediaPlayerMS::load(LoadType load_type,
const blink::WebURL& url,
CORSMode /*cors_mode*/) {
DVLOG(1) << __FUNCTION__;
DCHECK(thread_checker_.CalledOnValidThread());
// TODO(acolwell): Change this to DCHECK_EQ(load_type, LoadTypeMediaStream)
// once Blink-side changes land.
DCHECK_NE(load_type, LoadTypeMediaSource);
compositor_.reset(new WebMediaPlayerMSCompositor(compositor_task_runner_, url,
AsWeakPtr()));
SetNetworkState(WebMediaPlayer::NetworkStateLoading);
SetReadyState(WebMediaPlayer::ReadyStateHaveNothing);
media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec()));
video_frame_provider_ = renderer_factory_->GetVideoFrameProvider(
url,
base::Bind(&WebMediaPlayerMS::OnSourceError, AsWeakPtr()),
base::Bind(&WebMediaPlayerMS::OnFrameAvailable, AsWeakPtr()),
media_task_runner_,
worker_task_runner_,
gpu_factories_);
RenderFrame* const frame = RenderFrame::FromWebFrame(frame_);
audio_renderer_ = renderer_factory_->GetAudioRenderer(
url, frame->GetRoutingID(), initial_audio_output_device_id_,
initial_security_origin_);
if (!video_frame_provider_ && !audio_renderer_) {
SetNetworkState(WebMediaPlayer::NetworkStateNetworkError);
return;
}
if (audio_renderer_) {
audio_renderer_->SetVolume(DEFAULT_AUDIO_VOLUME);
audio_renderer_->Start();
}
if (video_frame_provider_)
video_frame_provider_->Start();
if (audio_renderer_ && !video_frame_provider_) {
// This is audio-only mode.
SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
}
}
void WebMediaPlayerMS::play() {
DVLOG(1) << __FUNCTION__;
DCHECK(thread_checker_.CalledOnValidThread());
if (paused_) {
if (video_frame_provider_.get())
video_frame_provider_->Play();
compositor_->StartRendering();
if (audio_renderer_.get())
audio_renderer_->Play();
if (delegate_.get())
delegate_->DidPlay(this);
}
paused_ = false;
media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY));
}
void WebMediaPlayerMS::pause() {
DVLOG(1) << __FUNCTION__;
DCHECK(thread_checker_.CalledOnValidThread());
if (video_frame_provider_.get())
video_frame_provider_->Pause();
compositor_->StopRendering();
compositor_->ReplaceCurrentFrameWithACopy();
if (!paused_) {
if (audio_renderer_.get())
audio_renderer_->Pause();
if (delegate_.get())
delegate_->DidPause(this);
}
paused_ = true;
media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE));
}
bool WebMediaPlayerMS::supportsSave() const {
DCHECK(thread_checker_.CalledOnValidThread());
return false;
}
void WebMediaPlayerMS::seek(double seconds) {
DCHECK(thread_checker_.CalledOnValidThread());
}
void WebMediaPlayerMS::setRate(double rate) {
DCHECK(thread_checker_.CalledOnValidThread());
}
void WebMediaPlayerMS::setVolume(double volume) {
DVLOG(1) << __FUNCTION__ << "(volume=" << volume << ")";
DCHECK(thread_checker_.CalledOnValidThread());
if (audio_renderer_.get())
audio_renderer_->SetVolume(volume);
}
void WebMediaPlayerMS::setSinkId(
const blink::WebString& sink_id,
const blink::WebSecurityOrigin& security_origin,
blink::WebSetSinkIdCallbacks* web_callback) {
DVLOG(1) << __FUNCTION__;
DCHECK(thread_checker_.CalledOnValidThread());
const media::SwitchOutputDeviceCB callback =
media::ConvertToSwitchOutputDeviceCB(web_callback);
if (audio_renderer_ && audio_renderer_->GetOutputDevice()) {
audio_renderer_->GetOutputDevice()->SwitchOutputDevice(
sink_id.utf8(), security_origin, callback);
} else {
callback.Run(media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL);
}
}
void WebMediaPlayerMS::setPreload(WebMediaPlayer::Preload preload) {
DCHECK(thread_checker_.CalledOnValidThread());
}
bool WebMediaPlayerMS::hasVideo() const {
DCHECK(thread_checker_.CalledOnValidThread());
return (video_frame_provider_.get() != nullptr);
}
bool WebMediaPlayerMS::hasAudio() const {
DCHECK(thread_checker_.CalledOnValidThread());
return (audio_renderer_.get() != nullptr);
}
blink::WebSize WebMediaPlayerMS::naturalSize() const {
DCHECK(thread_checker_.CalledOnValidThread());
return blink::WebSize(compositor_->GetCurrentSize());
}
bool WebMediaPlayerMS::paused() const {
DCHECK(thread_checker_.CalledOnValidThread());
return paused_;
}
bool WebMediaPlayerMS::seeking() const {
DCHECK(thread_checker_.CalledOnValidThread());
return false;
}
double WebMediaPlayerMS::duration() const {
DCHECK(thread_checker_.CalledOnValidThread());
return std::numeric_limits<double>::infinity();
}
double WebMediaPlayerMS::currentTime() const {
DCHECK(thread_checker_.CalledOnValidThread());
const base::TimeDelta current_time = compositor_->GetCurrentTime();
if (current_time.ToInternalValue() != 0)
return current_time.InSecondsF();
else if (audio_renderer_.get())
return audio_renderer_->GetCurrentRenderTime().InSecondsF();
return 0.0;
}
blink::WebMediaPlayer::NetworkState WebMediaPlayerMS::networkState() const {
DVLOG(1) << __FUNCTION__ << ", state:" << network_state_;
DCHECK(thread_checker_.CalledOnValidThread());
return network_state_;
}
blink::WebMediaPlayer::ReadyState WebMediaPlayerMS::readyState() const {
DVLOG(1) << __FUNCTION__ << ", state:" << ready_state_;
DCHECK(thread_checker_.CalledOnValidThread());
return ready_state_;
}
blink::WebTimeRanges WebMediaPlayerMS::buffered() const {
DCHECK(thread_checker_.CalledOnValidThread());
return buffered_;
}
blink::WebTimeRanges WebMediaPlayerMS::seekable() const {
DCHECK(thread_checker_.CalledOnValidThread());
return blink::WebTimeRanges();
}
bool WebMediaPlayerMS::didLoadingProgress() {
DCHECK(thread_checker_.CalledOnValidThread());
return true;
}
void WebMediaPlayerMS::paint(blink::WebCanvas* canvas,
const blink::WebRect& rect,
unsigned char alpha,
SkXfermode::Mode mode) {
DVLOG(3) << __FUNCTION__;
DCHECK(thread_checker_.CalledOnValidThread());
const scoped_refptr<media::VideoFrame> frame = compositor_->GetCurrentFrame();
media::Context3D context_3d;
if (frame && frame->HasTextures()) {
cc::ContextProvider* provider =
RenderThreadImpl::current()->SharedMainThreadContextProvider().get();
// GPU Process crashed.
if (!provider)
return;
context_3d = media::Context3D(provider->ContextGL(), provider->GrContext());
DCHECK(context_3d.gl);
}
const gfx::RectF dest_rect(rect.x, rect.y, rect.width, rect.height);
video_renderer_.Paint(frame, canvas, dest_rect, alpha, mode,
media::VIDEO_ROTATION_0, context_3d);
}
bool WebMediaPlayerMS::hasSingleSecurityOrigin() const {
DCHECK(thread_checker_.CalledOnValidThread());
return true;
}
bool WebMediaPlayerMS::didPassCORSAccessCheck() const {
DCHECK(thread_checker_.CalledOnValidThread());
return true;
}
double WebMediaPlayerMS::mediaTimeForTimeValue(double timeValue) const {
return base::TimeDelta::FromSecondsD(timeValue).InSecondsF();
}
unsigned WebMediaPlayerMS::decodedFrameCount() const {
DCHECK(thread_checker_.CalledOnValidThread());
return compositor_->total_frame_count();
}
unsigned WebMediaPlayerMS::droppedFrameCount() const {
DCHECK(thread_checker_.CalledOnValidThread());
return compositor_->dropped_frame_count();
}
unsigned WebMediaPlayerMS::audioDecodedByteCount() const {
DCHECK(thread_checker_.CalledOnValidThread());
NOTIMPLEMENTED();
return 0;
}
unsigned WebMediaPlayerMS::videoDecodedByteCount() const {
DCHECK(thread_checker_.CalledOnValidThread());
NOTIMPLEMENTED();
return 0;
}
void WebMediaPlayerMS::WasHidden() {
#if defined(OS_ANDROID)
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!render_frame_suspended_);
// Method called when the RenderFrame is sent to background and suspended
// (android). Substitute the displayed VideoFrame with a copy to avoid
// holding on to it unnecessarily.
render_frame_suspended_=true;
if (!paused_)
compositor_->ReplaceCurrentFrameWithACopy();
#endif // defined(OS_ANDROID)
}
void WebMediaPlayerMS::WasShown() {
#if defined(OS_ANDROID)
DCHECK(thread_checker_.CalledOnValidThread());
render_frame_suspended_ = false;
#endif // defined(OS_ANDROID)
}
bool WebMediaPlayerMS::copyVideoTextureToPlatformTexture(
blink::WebGraphicsContext3D* web_graphics_context,
unsigned int texture,
unsigned int internal_format,
unsigned int type,
bool premultiply_alpha,
bool flip_y) {
TRACE_EVENT0("media", "WebMediaPlayerMS:copyVideoTextureToPlatformTexture");
DCHECK(thread_checker_.CalledOnValidThread());
scoped_refptr<media::VideoFrame> video_frame = compositor_->GetCurrentFrame();
if (!video_frame.get() || video_frame->HasTextures() ||
media::VideoFrame::NumPlanes(video_frame->format()) != 1) {
return false;
}
// TODO(dshwang): need more elegant way to convert WebGraphicsContext3D to
// GLES2Interface.
gpu::gles2::GLES2Interface* const gl =
static_cast<gpu_blink::WebGraphicsContext3DImpl*>(web_graphics_context)
->GetGLInterface();
media::SkCanvasVideoRenderer::CopyVideoFrameSingleTextureToGLTexture(
gl, video_frame.get(), texture, internal_format, type, premultiply_alpha,
flip_y);
return true;
}
void WebMediaPlayerMS::OnFrameAvailable(
const scoped_refptr<media::VideoFrame>& frame) {
DVLOG(3) << __FUNCTION__;
DCHECK(thread_checker_.CalledOnValidThread());
if (render_frame_suspended_)
return;
base::TimeTicks render_time;
if (frame->metadata()->GetTimeTicks(media::VideoFrameMetadata::REFERENCE_TIME,
&render_time)) {
TRACE_EVENT1("webrtc", "WebMediaPlayerMS::OnFrameAvailable",
"Ideal Render Instant", render_time.ToInternalValue());
} else {
TRACE_EVENT0("webrtc", "WebMediaPlayerMS::OnFrameAvailable");
}
if (!received_first_frame_) {
received_first_frame_ = true;
SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
if (video_frame_provider_.get()) {
video_weblayer_.reset(new cc_blink::WebLayerImpl(
cc::VideoLayer::Create(cc_blink::WebLayerImpl::LayerSettings(),
compositor_.get(), media::VIDEO_ROTATION_0)));
video_weblayer_->layer()->SetContentsOpaque(true);
video_weblayer_->SetContentsOpaqueIsFixed(true);
get_client()->setWebLayer(video_weblayer_.get());
}
}
// As EnqueueFrame can potentially change |current_frame_|, we need to do
// the size change check before it. Otherwise, we are running the risk of not
// detecting a size change event.
const bool size_changed =
compositor_->GetCurrentSize() != frame->natural_size();
compositor_->EnqueueFrame(frame);
if (size_changed)
get_client()->sizeChanged();
}
void WebMediaPlayerMS::RepaintInternal() {
DVLOG(1) << __FUNCTION__;
DCHECK(thread_checker_.CalledOnValidThread());
get_client()->repaint();
}
void WebMediaPlayerMS::OnSourceError() {
DCHECK(thread_checker_.CalledOnValidThread());
SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
RepaintInternal();
}
void WebMediaPlayerMS::SetNetworkState(WebMediaPlayer::NetworkState state) {
DCHECK(thread_checker_.CalledOnValidThread());
network_state_ = state;
// Always notify to ensure client has the latest value.
get_client()->networkStateChanged();
}
void WebMediaPlayerMS::SetReadyState(WebMediaPlayer::ReadyState state) {
DCHECK(thread_checker_.CalledOnValidThread());
ready_state_ = state;
// Always notify to ensure client has the latest value.
get_client()->readyStateChanged();
}
media::SkCanvasVideoRenderer* WebMediaPlayerMS::GetSkCanvasVideoRenderer() {
return &video_renderer_;
}
void WebMediaPlayerMS::ResetCanvasCache() {
DCHECK(thread_checker_.CalledOnValidThread());
video_renderer_.ResetCache();
}
} // namespace content