1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172

content / browser / media / flinging_renderer.cc [blame]

// Copyright 2018 The Chromium Authors
// 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 <utility>

#include "base/memory/ptr_util.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/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"

namespace content {

FlingingRenderer::FlingingRenderer(
    std::unique_ptr<media::FlingingController> controller,
    mojo::PendingRemote<ClientExtension> 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> FlingingRenderer::Create(
    RenderFrameHost* render_frame_host,
    const std::string& presentation_id,
    mojo::PendingRemote<ClientExtension> 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(
          WebContents::FromRenderFrameHost(render_frame_host));

  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<FlingingRenderer>(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(
    std::optional<base::TimeDelta> 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::kPaused);
    controller_->GetMediaController()->Pause();
  } else {
    SetExpectedPlayState(PlayState::kPlaying);
    controller_->GetMediaController()->Play();
  }
}

void FlingingRenderer::SetVolume(float volume) {
  DVLOG(2) << __func__;
  controller_->GetMediaController()->SetVolume(volume);
}

base::TimeDelta FlingingRenderer::GetMediaTime() {
  return controller_->GetApproximateCurrentTime();
}

media::RendererType FlingingRenderer::GetRendererType() {
  return media::RendererType::kFlinging;
}

void FlingingRenderer::SetExpectedPlayState(PlayState state) {
  DVLOG(3) << __func__ << " : state " << static_cast<int>(state);
  DCHECK(state == PlayState::kPlaying || state == PlayState::kPaused);

  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::kPlaying &&
      current_state != PlayState::kPaused) {
    DVLOG(3) << __func__ << " : external state ignored: "
             << static_cast<int>(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