blob: 3bea8a6ab3b36c29f485b9177726def5569ef8c0 [file] [log] [blame]
// Copyright 2016 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/session/media_session_controller.h"
#include "content/browser/media/media_devices_util.h"
#include "content/browser/media/media_web_contents_observer.h"
#include "content/browser/media/session/media_session_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "media/base/media_content_type.h"
namespace content {
int MediaSessionController::player_count_ = 0;
MediaSessionController::MediaSessionController(const MediaPlayerId& id,
WebContentsImpl* web_contents)
: id_(id),
web_contents_(web_contents),
media_session_(MediaSessionImpl::Get(web_contents)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
MediaSessionController::~MediaSessionController() {
media_session_->RemovePlayer(this, player_id_);
}
void MediaSessionController::SetMetadata(
bool has_audio,
bool has_video,
media::MediaContentType media_content_type) {
has_audio_ = has_audio;
has_video_ = has_video;
media_content_type_ = media_content_type;
AddOrRemovePlayer();
}
bool MediaSessionController::OnPlaybackStarted() {
is_paused_ = false;
is_playback_in_progress_ = true;
return AddOrRemovePlayer();
}
void MediaSessionController::OnSuspend(int player_id) {
DCHECK_EQ(player_id_, player_id);
// TODO(crbug.com/953645): Set triggered_by_user to true ONLY if that action
// was actually triggered by user as this will activate the frame.
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestPause(/*triggered_by_user=*/true);
}
void MediaSessionController::OnResume(int player_id) {
DCHECK_EQ(player_id_, player_id);
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestPlay();
}
void MediaSessionController::OnSeekForward(int player_id,
base::TimeDelta seek_time) {
DCHECK_EQ(player_id_, player_id);
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestSeekForward(seek_time);
}
void MediaSessionController::OnSeekBackward(int player_id,
base::TimeDelta seek_time) {
DCHECK_EQ(player_id_, player_id);
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestSeekBackward(seek_time);
}
void MediaSessionController::OnSeekTo(int player_id,
base::TimeDelta seek_time) {
DCHECK_EQ(player_id_, player_id);
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestSeekTo(seek_time);
}
void MediaSessionController::OnSetVolumeMultiplier(int player_id,
double volume_multiplier) {
DCHECK_EQ(player_id_, player_id);
auto* observer = web_contents_->media_web_contents_observer();
// The MediaPlayer mojo interface may not be available in tests.
if (!observer->IsMediaPlayerRemoteAvailable(id_))
return;
observer->GetMediaPlayerRemote(id_)->SetVolumeMultiplier(volume_multiplier);
}
void MediaSessionController::OnEnterPictureInPicture(int player_id) {
DCHECK_EQ(player_id_, player_id);
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestEnterPictureInPicture();
}
void MediaSessionController::OnExitPictureInPicture(int player_id) {
DCHECK_EQ(player_id_, player_id);
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestExitPictureInPicture();
}
void MediaSessionController::OnSetAudioSinkId(
int player_id,
const std::string& raw_device_id) {
DCHECK_EQ(player_id_, player_id);
auto* render_frame_host = RenderFrameHost::FromID(id_.frame_routing_id);
if (!render_frame_host)
return;
content::GetMediaDeviceSaltAndOrigin(
render_frame_host->GetGlobalId(),
base::BindOnce(&MediaSessionController::OnMediaDeviceSaltReceived,
weak_factory_.GetWeakPtr(), raw_device_id));
}
void MediaSessionController::OnMediaDeviceSaltReceived(
const std::string& raw_device_id,
const MediaDeviceSaltAndOrigin& salt_and_origin) {
// The sink id needs to be hashed before it is suitable for use in the
// renderer process.
std::string hashed_sink_id = GetHMACForMediaDeviceID(
salt_and_origin.device_id_salt, salt_and_origin.origin, raw_device_id);
// Grant the renderer the permission to use this audio output device.
auto* render_frame_host_impl =
RenderFrameHostImpl::FromID(id_.frame_routing_id);
if (!render_frame_host_impl) {
return;
}
render_frame_host_impl->SetAudioOutputDeviceIdForGlobalMediaControls(
hashed_sink_id);
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->SetAudioSinkId(hashed_sink_id);
}
void MediaSessionController::OnSetMute(int player_id, bool mute) {
DCHECK_EQ(player_id_, player_id);
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestMute(mute);
}
void MediaSessionController::OnRequestMediaRemoting(int player_id) {
DCHECK_EQ(player_id_, player_id);
// Media Remoting can't start if the media is paused. So we should start
// playing before requesting Media Remoting.
if (is_paused_) {
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestPlay();
}
web_contents_->media_web_contents_observer()
->GetMediaPlayerRemote(id_)
->RequestMediaRemoting();
}
RenderFrameHost* MediaSessionController::render_frame_host() const {
return RenderFrameHost::FromID(id_.frame_routing_id);
}
absl::optional<media_session::MediaPosition>
MediaSessionController::GetPosition(int player_id) const {
DCHECK_EQ(player_id_, player_id);
return position_;
}
bool MediaSessionController::IsPictureInPictureAvailable(int player_id) const {
DCHECK_EQ(player_id_, player_id);
return is_picture_in_picture_available_;
}
void MediaSessionController::OnPlaybackPaused(bool reached_end_of_stream) {
is_paused_ = true;
if (reached_end_of_stream) {
is_playback_in_progress_ = false;
AddOrRemovePlayer();
}
// We check for suspension here since the renderer may issue its own pause
// in response to or while a pause from the browser is in flight.
if (media_session_->IsActive())
media_session_->OnPlayerPaused(this, player_id_);
}
void MediaSessionController::PictureInPictureStateChanged(
bool is_picture_in_picture) {
AddOrRemovePlayer();
}
void MediaSessionController::WebContentsMutedStateChanged(bool muted) {
AddOrRemovePlayer();
}
void MediaSessionController::OnMediaPositionStateChanged(
const media_session::MediaPosition& position) {
position_ = position;
media_session_->RebuildAndNotifyMediaPositionChanged();
}
void MediaSessionController::OnMediaMutedStatusChanged(bool mute) {
media_session_->OnMediaMutedStatusChanged(mute);
}
void MediaSessionController::OnPictureInPictureAvailabilityChanged(
bool available) {
is_picture_in_picture_available_ = available;
media_session_->OnPictureInPictureAvailabilityChanged();
}
void MediaSessionController::OnAudioOutputSinkChanged(
const std::string& raw_device_id) {
audio_output_sink_id_ = raw_device_id;
media_session_->OnAudioOutputSinkIdChanged();
}
void MediaSessionController::OnAudioOutputSinkChangingDisabled() {
supports_audio_output_device_switching_ = false;
media_session_->OnAudioOutputSinkChangingDisabled();
}
void MediaSessionController::OnRemotePlaybackMetadataChanged(
media_session::mojom::RemotePlaybackMetadataPtr metadata) {
media_session_->SetRemotePlaybackMetadata(std::move(metadata));
}
bool MediaSessionController::IsMediaSessionNeeded() const {
if (web_contents_->HasPictureInPictureVideo())
return true;
if (!is_playback_in_progress_)
return false;
// We want to make sure we do not request audio focus on a muted tab as it
// would break user expectations by pausing/ducking other playbacks.
return has_audio_ && !web_contents_->IsAudioMuted();
}
bool MediaSessionController::AddOrRemovePlayer() {
const bool needs_session = IsMediaSessionNeeded();
if (needs_session) {
// Attempt to add a session even if we already have one. MediaSession
// expects AddPlayer() to be called after OnPlaybackPaused() to reactivate
// the session.
if (!media_session_->AddPlayer(this, player_id_)) {
// If a session can't be created, force a pause immediately.
OnSuspend(player_id_);
return false;
}
// Need to synchronise paused/playing state in case we're adding the player
// because of entering Picture-In-Picture.
if (is_paused_)
media_session_->OnPlayerPaused(this, player_id_);
return true;
}
media_session_->RemovePlayer(this, player_id_);
return true;
}
bool MediaSessionController::HasAudio(int player_id) const {
DCHECK_EQ(player_id_, player_id);
return has_audio_;
}
bool MediaSessionController::HasVideo(int player_id) const {
DCHECK_EQ(player_id_, player_id);
return has_video_;
}
std::string MediaSessionController::GetAudioOutputSinkId(int player_id) const {
DCHECK_EQ(player_id_, player_id);
return audio_output_sink_id_;
}
bool MediaSessionController::SupportsAudioOutputDeviceSwitching(
int player_id) const {
DCHECK_EQ(player_id_, player_id);
return supports_audio_output_device_switching_;
}
media::MediaContentType MediaSessionController::GetMediaContentType() const {
return media_content_type_;
}
} // namespace content