// Copyright 2018 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/browser/media/flinging_renderer.h" #include #include "base/memory/ptr_util.h" #include "content/browser/renderer_host/render_frame_host_delegate.h" #include "content/browser/renderer_host/render_frame_host_impl.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/presentation_service_delegate.h" #include "content/public/browser/render_frame_host.h" #include "content/public/common/content_client.h" namespace content { FlingingRenderer::FlingingRenderer( std::unique_ptr controller, mojo::PendingRemote client_extension) : client_extension_(std::move(client_extension)), controller_(std::move(controller)) { controller_->AddMediaStatusObserver(this); } FlingingRenderer::~FlingingRenderer() { controller_->RemoveMediaStatusObserver(this); } // static std::unique_ptr FlingingRenderer::Create( RenderFrameHost* render_frame_host, const std::string& presentation_id, mojo::PendingRemote client_extension) { DVLOG(1) << __func__; ContentClient* content_client = GetContentClient(); if (!content_client) return nullptr; ContentBrowserClient* browser_client = content_client->browser(); if (!browser_client) return nullptr; ControllerPresentationServiceDelegate* presentation_delegate = browser_client->GetControllerPresentationServiceDelegate( static_cast(render_frame_host) ->delegate() ->GetAsWebContents()); if (!presentation_delegate) return nullptr; auto flinging_controller = presentation_delegate->GetFlingingController( render_frame_host->GetProcess()->GetID(), render_frame_host->GetRoutingID(), presentation_id); if (!flinging_controller) return nullptr; return base::WrapUnique(new FlingingRenderer( std::move(flinging_controller), std::move(client_extension))); } // media::Renderer implementation void FlingingRenderer::Initialize(media::MediaResource* media_resource, media::RendererClient* client, media::PipelineStatusCallback init_cb) { DVLOG(2) << __func__; client_ = client; std::move(init_cb).Run(media::PIPELINE_OK); } void FlingingRenderer::SetLatencyHint( base::Optional latency_hint) {} void FlingingRenderer::Flush(base::OnceClosure flush_cb) { DVLOG(2) << __func__; // There is nothing to reset, we can no-op the call. std::move(flush_cb).Run(); } void FlingingRenderer::StartPlayingFrom(base::TimeDelta time) { DVLOG(2) << __func__; controller_->GetMediaController()->Seek(time); // After a seek when using the FlingingRenderer, WMPI will never get back to // BUFFERING_HAVE_ENOUGH. This prevents Blink from getting the appropriate // seek completion signals, and time updates are never re-scheduled. // // The FlingingRenderer doesn't need to buffer, since playback happens on a // different device. This means it's ok to always send BUFFERING_HAVE_ENOUGH // when sending buffering state changes. That being said, sending state // changes here might be surprising, but the same signals are sent from // MediaPlayerRenderer::StartPlayingFrom(), and it has been working mostly // smoothly for all HLS playback. client_->OnBufferingStateChange(media::BUFFERING_HAVE_ENOUGH, media::BUFFERING_CHANGE_REASON_UNKNOWN); } void FlingingRenderer::SetPlaybackRate(double playback_rate) { DVLOG(2) << __func__; if (playback_rate == 0) { SetExpectedPlayState(PlayState::PAUSED); controller_->GetMediaController()->Pause(); } else { SetExpectedPlayState(PlayState::PLAYING); controller_->GetMediaController()->Play(); } } void FlingingRenderer::SetVolume(float volume) { DVLOG(2) << __func__; controller_->GetMediaController()->SetVolume(volume); } base::TimeDelta FlingingRenderer::GetMediaTime() { return controller_->GetApproximateCurrentTime(); } void FlingingRenderer::SetExpectedPlayState(PlayState state) { DVLOG(3) << __func__ << " : state " << static_cast(state); DCHECK(state == PlayState::PLAYING || state == PlayState::PAUSED); expected_play_state_ = state; play_state_is_stable_ = (expected_play_state_ == last_play_state_received_); } void FlingingRenderer::OnMediaStatusUpdated(const media::MediaStatus& status) { const auto& current_state = status.state; if (current_state == expected_play_state_) play_state_is_stable_ = true; // Because we can get a MediaStatus update at any time from the device, only // handle state updates after we have reached the target state. // If we do not, we can encounter the following scenario: // - A user pauses the video. // - While the PAUSE command is in flight, an unrelated MediaStatus with a // PLAYING state is sent from the cast device. // - We call OnRemotePlaybackStateChange(PLAYING). // - As the PAUSE command completes and we receive a PlayState::PAUSE, we // queue a new PLAYING. // - The local device enters a tick/tock feedback loop of constantly // requesting the wrong state of PLAYING/PAUSED. if (!play_state_is_stable_) return; // Ignore all non PLAYING/PAUSED states. // UNKNOWN and BUFFERING states are uninteresting and can be safely ignored. // STOPPED normally causes the session to teardown, and |this| is destroyed // shortly after. if (current_state != PlayState::PLAYING && current_state != PlayState::PAUSED) { DVLOG(3) << __func__ << " : external state ignored: " << static_cast(current_state); return; } // Save whether the remote device is currently playing or paused. last_play_state_received_ = current_state; // If the remote device's play state has toggled and we didn't initiate it, // notify WMPI to update it's own play/pause state. if (last_play_state_received_ != expected_play_state_) client_extension_->OnRemotePlayStateChange(current_state); } } // namespace content