| // 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 <stddef.h> |
| #include <limits> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.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/child/child_process.h" |
| #include "content/public/renderer/media_stream_audio_renderer.h" |
| #include "content/public/renderer/media_stream_renderer_factory.h" |
| #include "content/public/renderer/media_stream_video_renderer.h" |
| #include "content/renderer/media/media_stream_audio_track.h" |
| #include "content/renderer/media/media_stream_video_track.h" |
| #include "content/renderer/media/web_media_element_source_utils.h" |
| #include "content/renderer/media/webmediaplayer_ms_compositor.h" |
| #include "content/renderer/media/webrtc_logging.h" |
| #include "content/renderer/render_frame_impl.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/media_content_type.h" |
| #include "media/base/media_log.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_rotation.h" |
| #include "media/base/video_types.h" |
| #include "media/blink/webmediaplayer_util.h" |
| #include "services/ui/public/cpp/gpu/context_provider_command_buffer.h" |
| #include "third_party/WebKit/public/platform/WebMediaPlayerClient.h" |
| #include "third_party/WebKit/public/platform/WebMediaPlayerSource.h" |
| #include "third_party/WebKit/public/platform/WebRect.h" |
| #include "third_party/WebKit/public/platform/WebSize.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| |
| namespace { |
| enum class RendererReloadAction { |
| KEEP_RENDERER, |
| REMOVE_RENDERER, |
| NEW_RENDERER |
| }; |
| } // namespace |
| |
| namespace content { |
| |
| // FrameDeliverer is responsible for delivering frames received on |
| // the IO thread by calling of EnqueueFrame() method of |compositor_|. |
| // |
| // It is created on the main thread, but methods should be called and class |
| // should be destructed on the IO thread. |
| class WebMediaPlayerMS::FrameDeliverer { |
| public: |
| FrameDeliverer(const base::WeakPtr<WebMediaPlayerMS>& player, |
| const MediaStreamVideoRenderer::RepaintCB& enqueue_frame_cb) |
| : last_frame_opaque_(true), |
| last_frame_rotation_(media::VIDEO_ROTATION_0), |
| received_first_frame_(false), |
| main_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| player_(player), |
| enqueue_frame_cb_(enqueue_frame_cb), |
| weak_factory_(this) { |
| io_thread_checker_.DetachFromThread(); |
| } |
| |
| ~FrameDeliverer() { DCHECK(io_thread_checker_.CalledOnValidThread()); } |
| |
| void OnVideoFrame(scoped_refptr<media::VideoFrame> frame) { |
| DCHECK(io_thread_checker_.CalledOnValidThread()); |
| |
| #if defined(OS_ANDROID) |
| if (render_frame_suspended_) |
| return; |
| #endif // defined(OS_ANDROID) |
| |
| base::TimeTicks render_time; |
| if (frame->metadata()->GetTimeTicks( |
| media::VideoFrameMetadata::REFERENCE_TIME, &render_time)) { |
| TRACE_EVENT1("webmediaplayerms", "OnVideoFrame", "Ideal Render Instant", |
| render_time.ToInternalValue()); |
| } else { |
| TRACE_EVENT0("webmediaplayerms", "OnVideoFrame"); |
| } |
| |
| const bool is_opaque = media::IsOpaque(frame->format()); |
| media::VideoRotation video_rotation = media::VIDEO_ROTATION_0; |
| ignore_result(frame->metadata()->GetRotation( |
| media::VideoFrameMetadata::ROTATION, &video_rotation)); |
| |
| if (!received_first_frame_) { |
| received_first_frame_ = true; |
| last_frame_opaque_ = is_opaque; |
| last_frame_rotation_ = video_rotation; |
| main_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&WebMediaPlayerMS::OnFirstFrameReceived, |
| player_, video_rotation, is_opaque)); |
| } else { |
| if (last_frame_opaque_ != is_opaque) { |
| last_frame_opaque_ = is_opaque; |
| main_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&WebMediaPlayerMS::OnOpacityChanged, |
| player_, is_opaque)); |
| } |
| if (last_frame_rotation_ != video_rotation) { |
| last_frame_rotation_ = video_rotation; |
| main_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&WebMediaPlayerMS::OnRotationChanged, |
| player_, video_rotation, is_opaque)); |
| } |
| } |
| |
| enqueue_frame_cb_.Run(frame); |
| } |
| |
| #if defined(OS_ANDROID) |
| void SetRenderFrameSuspended(bool render_frame_suspended) { |
| DCHECK(io_thread_checker_.CalledOnValidThread()); |
| render_frame_suspended_ = render_frame_suspended; |
| } |
| #endif // defined(OS_ANDROID) |
| |
| MediaStreamVideoRenderer::RepaintCB GetRepaintCallback() { |
| return base::Bind(&FrameDeliverer::OnVideoFrame, |
| weak_factory_.GetWeakPtr()); |
| } |
| |
| private: |
| bool last_frame_opaque_; |
| media::VideoRotation last_frame_rotation_; |
| bool received_first_frame_; |
| |
| #if defined(OS_ANDROID) |
| bool render_frame_suspended_ = false; |
| #endif // defined(OS_ANDROID) |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| const base::WeakPtr<WebMediaPlayerMS> player_; |
| const MediaStreamVideoRenderer::RepaintCB enqueue_frame_cb_; |
| |
| // Used for DCHECKs to ensure method calls are executed on the correct thread. |
| base::ThreadChecker io_thread_checker_; |
| |
| base::WeakPtrFactory<FrameDeliverer> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FrameDeliverer); |
| }; |
| |
| WebMediaPlayerMS::WebMediaPlayerMS( |
| blink::WebLocalFrame* frame, |
| blink::WebMediaPlayerClient* client, |
| media::WebMediaPlayerDelegate* delegate, |
| std::unique_ptr<media::MediaLog> media_log, |
| std::unique_ptr<MediaStreamRendererFactory> factory, |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> media_task_runner, |
| scoped_refptr<base::TaskRunner> worker_task_runner, |
| media::GpuVideoAcceleratorFactories* gpu_factories, |
| const blink::WebString& sink_id, |
| const blink::WebSecurityOrigin& security_origin) |
| : frame_(frame), |
| network_state_(WebMediaPlayer::kNetworkStateEmpty), |
| ready_state_(WebMediaPlayer::kReadyStateHaveNothing), |
| buffered_(static_cast<size_t>(0)), |
| client_(client), |
| delegate_(delegate), |
| delegate_id_(0), |
| paused_(true), |
| video_rotation_(media::VIDEO_ROTATION_0), |
| media_log_(std::move(media_log)), |
| renderer_factory_(std::move(factory)), |
| io_task_runner_(io_task_runner), |
| compositor_task_runner_(compositor_task_runner), |
| media_task_runner_(media_task_runner), |
| worker_task_runner_(worker_task_runner), |
| gpu_factories_(gpu_factories), |
| initial_audio_output_device_id_(sink_id.Utf8()), |
| initial_security_origin_(security_origin.IsNull() |
| ? url::Origin() |
| : url::Origin(security_origin)), |
| volume_(1.0), |
| volume_multiplier_(1.0), |
| should_play_upon_shown_(false) { |
| DVLOG(1) << __func__; |
| DCHECK(client); |
| DCHECK(delegate_); |
| delegate_id_ = delegate_->AddObserver(this); |
| |
| media_log_->AddEvent( |
| media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_CREATED)); |
| } |
| |
| WebMediaPlayerMS::~WebMediaPlayerMS() { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!web_stream_.IsNull()) |
| web_stream_.RemoveObserver(this); |
| |
| // Destruct compositor resources in the proper order. |
| get_client()->SetWebLayer(nullptr); |
| if (video_weblayer_) |
| static_cast<cc::VideoLayer*>(video_weblayer_->layer())->StopUsingProvider(); |
| |
| if (frame_deliverer_) |
| io_task_runner_->DeleteSoon(FROM_HERE, frame_deliverer_.release()); |
| |
| if (compositor_) |
| compositor_->StopUsingProvider(); |
| |
| if (video_frame_provider_) |
| video_frame_provider_->Stop(); |
| |
| if (audio_renderer_) |
| audio_renderer_->Stop(); |
| |
| media_log_->AddEvent( |
| media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED)); |
| |
| delegate_->PlayerGone(delegate_id_); |
| delegate_->RemoveObserver(delegate_id_); |
| } |
| |
| void WebMediaPlayerMS::Load(LoadType load_type, |
| const blink::WebMediaPlayerSource& source, |
| CORSMode /*cors_mode*/) { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // TODO(acolwell): Change this to DCHECK_EQ(load_type, LoadTypeMediaStream) |
| // once Blink-side changes land. |
| DCHECK_NE(load_type, kLoadTypeMediaSource); |
| web_stream_ = GetWebMediaStreamFromWebMediaPlayerSource(source); |
| if (!web_stream_.IsNull()) |
| web_stream_.AddObserver(this); |
| |
| compositor_ = new WebMediaPlayerMSCompositor( |
| compositor_task_runner_, io_task_runner_, web_stream_, AsWeakPtr()); |
| |
| SetNetworkState(WebMediaPlayer::kNetworkStateLoading); |
| SetReadyState(WebMediaPlayer::kReadyStateHaveNothing); |
| std::string stream_id = |
| web_stream_.IsNull() ? std::string() : web_stream_.Id().Utf8(); |
| media_log_->AddEvent(media_log_->CreateLoadEvent(stream_id)); |
| |
| frame_deliverer_.reset(new WebMediaPlayerMS::FrameDeliverer( |
| AsWeakPtr(), |
| base::Bind(&WebMediaPlayerMSCompositor::EnqueueFrame, compositor_))); |
| video_frame_provider_ = renderer_factory_->GetVideoRenderer( |
| web_stream_, |
| media::BindToCurrentLoop( |
| base::Bind(&WebMediaPlayerMS::OnSourceError, AsWeakPtr())), |
| frame_deliverer_->GetRepaintCallback(), io_task_runner_, |
| media_task_runner_, worker_task_runner_, gpu_factories_); |
| |
| RenderFrame* const frame = RenderFrame::FromWebFrame(frame_); |
| |
| int routing_id = MSG_ROUTING_NONE; |
| GURL url = source.IsURL() ? GURL(source.GetAsURL()) : GURL(); |
| |
| if (frame) { |
| // Report UMA and RAPPOR metrics. |
| media::ReportMetrics(load_type, url, frame_->GetSecurityOrigin(), |
| media_log_.get()); |
| routing_id = frame->GetRoutingID(); |
| } |
| |
| audio_renderer_ = renderer_factory_->GetAudioRenderer( |
| web_stream_, routing_id, initial_audio_output_device_id_, |
| initial_security_origin_); |
| |
| if (!audio_renderer_) |
| WebRtcLogMessage("Warning: Failed to instantiate audio renderer."); |
| |
| if (!video_frame_provider_ && !audio_renderer_) { |
| SetNetworkState(WebMediaPlayer::kNetworkStateNetworkError); |
| return; |
| } |
| |
| if (audio_renderer_) { |
| audio_renderer_->SetVolume(volume_); |
| audio_renderer_->Start(); |
| |
| // Store the ID of audio track being played in |current_video_track_id_| |
| blink::WebVector<blink::WebMediaStreamTrack> audio_tracks; |
| if (!web_stream_.IsNull()) { |
| web_stream_.AudioTracks(audio_tracks); |
| DCHECK_GT(audio_tracks.size(), 0U); |
| current_audio_track_id_ = audio_tracks[0].Id(); |
| } |
| } |
| |
| if (video_frame_provider_) { |
| video_frame_provider_->Start(); |
| |
| // Store the ID of video track being played in |current_video_track_id_| |
| if (!web_stream_.IsNull()) { |
| blink::WebVector<blink::WebMediaStreamTrack> video_tracks; |
| web_stream_.VideoTracks(video_tracks); |
| DCHECK_GT(video_tracks.size(), 0U); |
| current_video_track_id_ = video_tracks[0].Id(); |
| } |
| } |
| // When associated with an <audio> element, we don't want to wait for the |
| // first video fram to become available as we do for <video> elements |
| // (<audio> elements can also be assigned video tracks). |
| // For more details, see crbug.com/738379 |
| if (audio_renderer_ && |
| (client_->IsAudioElement() || !video_frame_provider_)) { |
| // This is audio-only mode. |
| SetReadyState(WebMediaPlayer::kReadyStateHaveMetadata); |
| SetReadyState(WebMediaPlayer::kReadyStateHaveEnoughData); |
| } |
| } |
| |
| void WebMediaPlayerMS::TrackAdded(const blink::WebMediaStreamTrack& track) { |
| Reload(); |
| } |
| |
| void WebMediaPlayerMS::TrackRemoved(const blink::WebMediaStreamTrack& track) { |
| Reload(); |
| } |
| |
| void WebMediaPlayerMS::Reload() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (web_stream_.IsNull()) |
| return; |
| |
| ReloadVideo(); |
| ReloadAudio(); |
| } |
| |
| void WebMediaPlayerMS::ReloadVideo() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(!web_stream_.IsNull()); |
| blink::WebVector<blink::WebMediaStreamTrack> video_tracks; |
| // VideoTracks() is a getter. |
| web_stream_.VideoTracks(video_tracks); |
| |
| RendererReloadAction renderer_action = RendererReloadAction::KEEP_RENDERER; |
| if (video_tracks.IsEmpty()) { |
| if (video_frame_provider_) |
| renderer_action = RendererReloadAction::REMOVE_RENDERER; |
| current_video_track_id_ = blink::WebString(); |
| } else if (video_tracks[0].Id() != current_video_track_id_) { |
| renderer_action = RendererReloadAction::NEW_RENDERER; |
| current_video_track_id_ = video_tracks[0].Id(); |
| } |
| |
| switch (renderer_action) { |
| case RendererReloadAction::NEW_RENDERER: |
| if (video_frame_provider_) |
| video_frame_provider_->Stop(); |
| |
| video_frame_provider_ = renderer_factory_->GetVideoRenderer( |
| web_stream_, |
| media::BindToCurrentLoop( |
| base::Bind(&WebMediaPlayerMS::OnSourceError, AsWeakPtr())), |
| frame_deliverer_->GetRepaintCallback(), io_task_runner_, |
| media_task_runner_, worker_task_runner_, gpu_factories_); |
| DCHECK(video_frame_provider_); |
| video_frame_provider_->Start(); |
| break; |
| |
| case RendererReloadAction::REMOVE_RENDERER: |
| video_frame_provider_->Stop(); |
| video_frame_provider_ = nullptr; |
| break; |
| |
| default: |
| return; |
| } |
| |
| DCHECK_NE(renderer_action, RendererReloadAction::KEEP_RENDERER); |
| if (!paused_) |
| delegate_->DidPlayerSizeChange(delegate_id_, NaturalSize()); |
| } |
| |
| void WebMediaPlayerMS::ReloadAudio() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(!web_stream_.IsNull()); |
| RenderFrame* const frame = RenderFrame::FromWebFrame(frame_); |
| if (!frame) |
| return; |
| |
| blink::WebVector<blink::WebMediaStreamTrack> audio_tracks; |
| // AudioTracks() is a getter. |
| web_stream_.AudioTracks(audio_tracks); |
| |
| RendererReloadAction renderer_action = RendererReloadAction::KEEP_RENDERER; |
| if (audio_tracks.IsEmpty()) { |
| if (audio_renderer_) |
| renderer_action = RendererReloadAction::REMOVE_RENDERER; |
| current_audio_track_id_ = blink::WebString(); |
| } else if (audio_tracks[0].Id() != current_video_track_id_) { |
| renderer_action = RendererReloadAction::NEW_RENDERER; |
| current_audio_track_id_ = audio_tracks[0].Id(); |
| } |
| |
| switch (renderer_action) { |
| case RendererReloadAction::NEW_RENDERER: |
| if (audio_renderer_) |
| audio_renderer_->Stop(); |
| |
| audio_renderer_ = renderer_factory_->GetAudioRenderer( |
| web_stream_, frame->GetRoutingID(), initial_audio_output_device_id_, |
| initial_security_origin_); |
| audio_renderer_->SetVolume(volume_); |
| audio_renderer_->Start(); |
| audio_renderer_->Play(); |
| break; |
| |
| case RendererReloadAction::REMOVE_RENDERER: |
| audio_renderer_->Stop(); |
| audio_renderer_ = nullptr; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| void WebMediaPlayerMS::Play() { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY)); |
| if (!paused_) |
| return; |
| |
| if (video_frame_provider_) |
| video_frame_provider_->Resume(); |
| |
| compositor_->StartRendering(); |
| |
| if (audio_renderer_) |
| audio_renderer_->Play(); |
| |
| if (HasVideo()) |
| delegate_->DidPlayerSizeChange(delegate_id_, NaturalSize()); |
| |
| // |delegate_| expects the notification only if there is at least one track |
| // actually playing. A media stream might have none since tracks can be |
| // removed from the stream. |
| if (HasAudio() || HasVideo()) { |
| // TODO(perkj, magjed): We use OneShot focus type here so that it takes |
| // audio focus once it starts, and then will not respond to further audio |
| // focus changes. See http://crbug.com/596516 for more details. |
| delegate_->DidPlay(delegate_id_, HasVideo(), HasAudio(), |
| media::MediaContentType::OneShot); |
| } |
| |
| delegate_->SetIdle(delegate_id_, false); |
| paused_ = false; |
| } |
| |
| void WebMediaPlayerMS::Pause() { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| should_play_upon_shown_ = false; |
| media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE)); |
| if (paused_) |
| return; |
| |
| if (video_frame_provider_) |
| video_frame_provider_->Pause(); |
| |
| compositor_->StopRendering(); |
| compositor_->ReplaceCurrentFrameWithACopy(); |
| |
| if (audio_renderer_) |
| audio_renderer_->Pause(); |
| |
| delegate_->DidPause(delegate_id_); |
| delegate_->SetIdle(delegate_id_, true); |
| |
| paused_ = true; |
| } |
| |
| 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) << __func__ << "(volume=" << volume << ")"; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| volume_ = volume; |
| if (audio_renderer_.get()) |
| audio_renderer_->SetVolume(volume_ * volume_multiplier_); |
| delegate_->DidPlayerMutedStatusChange(delegate_id_, volume == 0.0); |
| } |
| |
| void WebMediaPlayerMS::PictureInPicture() {} |
| |
| void WebMediaPlayerMS::SetSinkId( |
| const blink::WebString& sink_id, |
| const blink::WebSecurityOrigin& security_origin, |
| blink::WebSetSinkIdCallbacks* web_callback) { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| const media::OutputDeviceStatusCB callback = |
| media::ConvertToOutputDeviceStatusCB(web_callback); |
| if (audio_renderer_) { |
| audio_renderer_->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()); |
| if (!video_frame_provider_) |
| return blink::WebSize(); |
| |
| if (video_rotation_ == media::VIDEO_ROTATION_90 || |
| video_rotation_ == media::VideoRotation::VIDEO_ROTATION_270) { |
| const gfx::Size& current_size = compositor_->GetCurrentSize(); |
| return blink::WebSize(current_size.height(), current_size.width()); |
| } |
| return blink::WebSize(compositor_->GetCurrentSize()); |
| } |
| |
| blink::WebSize WebMediaPlayerMS::VisibleRect() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| scoped_refptr<media::VideoFrame> video_frame = |
| compositor_->GetCurrentFrameWithoutUpdatingStatistics(); |
| if (!video_frame) |
| return blink::WebSize(); |
| |
| const gfx::Rect& visible_rect = video_frame->visible_rect(); |
| if (video_rotation_ == media::VIDEO_ROTATION_90 || |
| video_rotation_ == media::VideoRotation::VIDEO_ROTATION_270) { |
| return blink::WebSize(visible_rect.height(), visible_rect.width()); |
| } |
| return blink::WebSize(visible_rect.width(), visible_rect.height()); |
| } |
| |
| 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::GetNetworkState() const { |
| DVLOG(1) << __func__ << ", state:" << network_state_; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return network_state_; |
| } |
| |
| blink::WebMediaPlayer::ReadyState WebMediaPlayerMS::GetReadyState() const { |
| DVLOG(1) << __func__ << ", state:" << ready_state_; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return ready_state_; |
| } |
| |
| blink::WebString WebMediaPlayerMS::GetErrorMessage() const { |
| return blink::WebString::FromUTF8(media_log_->GetErrorMessage()); |
| } |
| |
| 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, |
| cc::PaintFlags& flags, |
| int already_uploaded_id, |
| VideoFrameUploadMetadata* out_metadata) { |
| DVLOG(3) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| const scoped_refptr<media::VideoFrame> frame = |
| compositor_->GetCurrentFrameWithoutUpdatingStatistics(); |
| |
| media::Context3D context_3d; |
| if (frame && frame->HasTextures()) { |
| auto* 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, flags, video_rotation_, |
| context_3d); |
| } |
| |
| bool WebMediaPlayerMS::DidGetOpaqueResponseFromServiceWorker() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return false; |
| } |
| |
| 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(); |
| } |
| |
| size_t WebMediaPlayerMS::AudioDecodedByteCount() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| NOTIMPLEMENTED(); |
| return 0; |
| } |
| |
| size_t WebMediaPlayerMS::VideoDecodedByteCount() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| NOTIMPLEMENTED(); |
| return 0; |
| } |
| |
| void WebMediaPlayerMS::OnFrameHidden() { |
| #if defined(OS_ANDROID) |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // 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. |
| // |
| // During undoable tab closures OnHidden() may be called back to back, so we |
| // can't rely on |render_frame_suspended_| being false here. |
| if (frame_deliverer_) { |
| io_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&FrameDeliverer::SetRenderFrameSuspended, |
| base::Unretained(frame_deliverer_.get()), true)); |
| } |
| |
| if (!paused_) |
| compositor_->ReplaceCurrentFrameWithACopy(); |
| #endif // defined(OS_ANDROID) |
| } |
| |
| void WebMediaPlayerMS::OnFrameClosed() { |
| #if defined(OS_ANDROID) |
| if (!paused_) { |
| Pause(); |
| should_play_upon_shown_ = true; |
| } |
| |
| delegate_->PlayerGone(delegate_id_); |
| |
| if (frame_deliverer_) { |
| io_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&FrameDeliverer::SetRenderFrameSuspended, |
| base::Unretained(frame_deliverer_.get()), true)); |
| } |
| #endif // defined(OS_ANDROID) |
| } |
| |
| void WebMediaPlayerMS::OnFrameShown() { |
| #if defined(OS_ANDROID) |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (frame_deliverer_) { |
| io_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&FrameDeliverer::SetRenderFrameSuspended, |
| base::Unretained(frame_deliverer_.get()), false)); |
| } |
| |
| // Resume playback on visibility. play() clears |should_play_upon_shown_|. |
| if (should_play_upon_shown_) |
| Play(); |
| #endif // defined(OS_ANDROID) |
| } |
| |
| void WebMediaPlayerMS::OnIdleTimeout() {} |
| |
| void WebMediaPlayerMS::OnPlay() { |
| // TODO(perkj, magjed): It's not clear how WebRTC should work with an |
| // MediaSession, until these issues are resolved, disable session controls. |
| // http://crbug.com/595297. |
| } |
| |
| void WebMediaPlayerMS::OnPause() { |
| // TODO(perkj, magjed): See TODO in OnPlay(). |
| } |
| |
| void WebMediaPlayerMS::OnSeekForward(double seconds) { |
| // TODO(perkj, magjed): See TODO in OnPlay(). |
| } |
| |
| void WebMediaPlayerMS::OnSeekBackward(double seconds) { |
| // TODO(perkj, magjed): See TODO in OnPlay(). |
| } |
| |
| void WebMediaPlayerMS::OnVolumeMultiplierUpdate(double multiplier) { |
| // TODO(perkj, magjed): See TODO in OnPlay(). |
| } |
| |
| void WebMediaPlayerMS::OnBecamePersistentVideo(bool value) { |
| get_client()->OnBecamePersistentVideo(value); |
| } |
| |
| bool WebMediaPlayerMS::CopyVideoTextureToPlatformTexture( |
| gpu::gles2::GLES2Interface* gl, |
| unsigned target, |
| unsigned int texture, |
| unsigned internal_format, |
| unsigned format, |
| unsigned type, |
| int level, |
| bool premultiply_alpha, |
| bool flip_y, |
| int already_uploaded_id, |
| VideoFrameUploadMetadata* out_metadata) { |
| TRACE_EVENT0("webmediaplayerms", "copyVideoTextureToPlatformTexture"); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| scoped_refptr<media::VideoFrame> video_frame = |
| compositor_->GetCurrentFrameWithoutUpdatingStatistics(); |
| |
| if (!video_frame.get() || !video_frame->HasTextures()) |
| return false; |
| |
| media::Context3D context_3d; |
| auto* provider = |
| RenderThreadImpl::current()->SharedMainThreadContextProvider().get(); |
| // GPU Process crashed. |
| if (!provider) |
| return false; |
| context_3d = media::Context3D(provider->ContextGL(), provider->GrContext()); |
| DCHECK(context_3d.gl); |
| return video_renderer_.CopyVideoFrameTexturesToGLTexture( |
| context_3d, gl, video_frame.get(), target, texture, internal_format, |
| format, type, level, premultiply_alpha, flip_y); |
| } |
| |
| bool WebMediaPlayerMS::TexImageImpl(TexImageFunctionID functionID, |
| unsigned target, |
| gpu::gles2::GLES2Interface* gl, |
| unsigned int texture, |
| int level, |
| int internalformat, |
| unsigned format, |
| unsigned type, |
| int xoffset, |
| int yoffset, |
| int zoffset, |
| bool flip_y, |
| bool premultiply_alpha) { |
| TRACE_EVENT0("webmediaplayerms", "texImageImpl"); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| const scoped_refptr<media::VideoFrame> video_frame = |
| compositor_->GetCurrentFrameWithoutUpdatingStatistics(); |
| if (!video_frame || !video_frame->IsMappable() || |
| video_frame->HasTextures() || |
| video_frame->format() != media::PIXEL_FORMAT_Y16) { |
| return false; |
| } |
| |
| if (functionID == kTexImage2D) { |
| auto* provider = |
| RenderThreadImpl::current()->SharedMainThreadContextProvider().get(); |
| // GPU Process crashed. |
| if (!provider) |
| return false; |
| return media::PaintCanvasVideoRenderer::TexImage2D( |
| target, texture, gl, provider->ContextCapabilities(), video_frame.get(), |
| level, internalformat, format, type, flip_y, premultiply_alpha); |
| } else if (functionID == kTexSubImage2D) { |
| return media::PaintCanvasVideoRenderer::TexSubImage2D( |
| target, gl, video_frame.get(), level, format, type, xoffset, yoffset, |
| flip_y, premultiply_alpha); |
| } |
| return false; |
| } |
| |
| void WebMediaPlayerMS::OnFirstFrameReceived(media::VideoRotation video_rotation, |
| bool is_opaque) { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| SetReadyState(WebMediaPlayer::kReadyStateHaveMetadata); |
| SetReadyState(WebMediaPlayer::kReadyStateHaveEnoughData); |
| |
| OnRotationChanged(video_rotation, is_opaque); |
| } |
| |
| void WebMediaPlayerMS::OnOpacityChanged(bool is_opaque) { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // Opacity can be changed during the session without resetting |
| // |video_weblayer_|. |
| video_weblayer_->layer()->SetContentsOpaque(is_opaque); |
| } |
| |
| void WebMediaPlayerMS::OnRotationChanged(media::VideoRotation video_rotation, |
| bool is_opaque) { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| video_rotation_ = video_rotation; |
| |
| std::unique_ptr<cc_blink::WebLayerImpl> rotated_weblayer = |
| base::WrapUnique(new cc_blink::WebLayerImpl( |
| cc::VideoLayer::Create(compositor_.get(), video_rotation))); |
| rotated_weblayer->layer()->SetContentsOpaque(is_opaque); |
| rotated_weblayer->SetContentsOpaqueIsFixed(true); |
| get_client()->SetWebLayer(rotated_weblayer.get()); |
| video_weblayer_ = std::move(rotated_weblayer); |
| } |
| |
| void WebMediaPlayerMS::RepaintInternal() { |
| DVLOG(1) << __func__; |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| get_client()->Repaint(); |
| } |
| |
| void WebMediaPlayerMS::OnSourceError() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| SetNetworkState(WebMediaPlayer::kNetworkStateFormatError); |
| 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::PaintCanvasVideoRenderer* |
| WebMediaPlayerMS::GetPaintCanvasVideoRenderer() { |
| return &video_renderer_; |
| } |
| |
| void WebMediaPlayerMS::ResetCanvasCache() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| video_renderer_.ResetCache(); |
| } |
| |
| void WebMediaPlayerMS::TriggerResize() { |
| if (HasVideo()) |
| get_client()->SizeChanged(); |
| |
| delegate_->DidPlayerSizeChange(delegate_id_, NaturalSize()); |
| } |
| |
| } // namespace content |