Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / media / android / media_session.cc
blob37a6995fcdf861926c1a6fc45ee9616d24d2b2ec
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/media/android/media_session.h"
7 #include "base/android/jni_android.h"
8 #include "content/browser/media/android/media_session_observer.h"
9 #include "content/browser/web_contents/web_contents_impl.h"
10 #include "content/public/browser/web_contents.h"
11 #include "content/public/browser/web_contents_delegate.h"
12 #include "jni/MediaSession_jni.h"
14 namespace content {
16 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MediaSession);
18 MediaSession::PlayerIdentifier::PlayerIdentifier(MediaSessionObserver* observer,
19 int player_id)
20 : observer(observer),
21 player_id(player_id) {
24 bool MediaSession::PlayerIdentifier::operator==(
25 const PlayerIdentifier& other) const {
26 return this->observer == other.observer && this->player_id == other.player_id;
29 size_t MediaSession::PlayerIdentifier::Hash::operator()(
30 const PlayerIdentifier& player_identifier) const {
31 size_t hash = BASE_HASH_NAMESPACE::hash<MediaSessionObserver*>()(
32 player_identifier.observer);
33 hash += BASE_HASH_NAMESPACE::hash<int>()(player_identifier.player_id);
34 return hash;
37 // static
38 bool content::MediaSession::RegisterMediaSession(JNIEnv* env) {
39 return RegisterNativesImpl(env);
42 // static
43 MediaSession* MediaSession::Get(WebContents* web_contents) {
44 MediaSession* session = FromWebContents(web_contents);
45 if (!session) {
46 CreateForWebContents(web_contents);
47 session = FromWebContents(web_contents);
48 session->Initialize();
50 return session;
53 MediaSession::~MediaSession() {
54 DCHECK(players_.empty());
55 DCHECK(audio_focus_state_ == State::INACTIVE);
58 bool MediaSession::AddPlayer(MediaSessionObserver* observer,
59 int player_id,
60 Type type) {
61 // If the audio focus is already granted and is of type Content, there is
62 // nothing to do. If it is granted of type Transient the requested type is
63 // also transient, there is also nothing to do. Otherwise, the session needs
64 // to request audio focus again.
65 if (audio_focus_state_ == State::ACTIVE &&
66 (audio_focus_type_ == Type::Content || audio_focus_type_ == type)) {
67 players_.insert(PlayerIdentifier(observer, player_id));
68 return true;
71 State old_audio_focus_state = audio_focus_state_;
72 audio_focus_state_ = RequestSystemAudioFocus(type) ? State::ACTIVE
73 : State::INACTIVE;
74 audio_focus_type_ = type;
76 if (audio_focus_state_ != State::ACTIVE)
77 return false;
79 // The session should be reset if a player is starting while all players are
80 // suspended.
81 if (old_audio_focus_state != State::ACTIVE)
82 players_.clear();
84 players_.insert(PlayerIdentifier(observer, player_id));
85 UpdateWebContents();
87 return true;
90 void MediaSession::RemovePlayer(MediaSessionObserver* observer,
91 int player_id) {
92 auto it = players_.find(PlayerIdentifier(observer, player_id));
93 if (it != players_.end())
94 players_.erase(it);
96 AbandonSystemAudioFocusIfNeeded();
99 void MediaSession::RemovePlayers(MediaSessionObserver* observer) {
100 for (auto it = players_.begin(); it != players_.end();) {
101 if (it->observer == observer)
102 players_.erase(it++);
103 else
104 ++it;
107 AbandonSystemAudioFocusIfNeeded();
110 void MediaSession::OnSuspend(JNIEnv* env, jobject obj, jboolean temporary) {
111 if (audio_focus_state_ != State::ACTIVE)
112 return;
114 OnSuspendInternal(SuspendType::SYSTEM);
115 if (!temporary)
116 audio_focus_state_ = State::INACTIVE;
117 UpdateWebContents();
120 void MediaSession::OnResume(JNIEnv* env, jobject obj) {
121 if (audio_focus_state_ != State::SUSPENDED)
122 return;
124 OnResumeInternal(SuspendType::SYSTEM);
125 UpdateWebContents();
128 void MediaSession::Resume() {
129 DCHECK(IsSuspended());
131 // Request audio focus again in case we lost it because another app started
132 // playing while the playback was paused.
133 audio_focus_state_ = RequestSystemAudioFocus(audio_focus_type_)
134 ? State::ACTIVE
135 : State::INACTIVE;
136 if (audio_focus_state_ != State::ACTIVE)
137 return;
139 OnResumeInternal(SuspendType::UI);
142 void MediaSession::Suspend() {
143 DCHECK(!IsSuspended());
145 OnSuspendInternal(SuspendType::UI);
148 void MediaSession::Stop() {
149 DCHECK(audio_focus_state_ != State::INACTIVE);
151 if (audio_focus_state_ != State::SUSPENDED)
152 OnSuspendInternal(SuspendType::UI);
154 DCHECK(audio_focus_state_ == State::SUSPENDED);
155 players_.clear();
156 AbandonSystemAudioFocusIfNeeded();
159 bool MediaSession::IsSuspended() const {
160 // TODO(mlamouri): should be == State::SUSPENDED.
161 return audio_focus_state_ != State::ACTIVE;
164 bool MediaSession::IsControllable() const {
165 // Only content type media session can be controllable unless it is inactive.
166 return audio_focus_state_ != State::INACTIVE &&
167 audio_focus_type_ == Type::Content;
170 void MediaSession::ResetJavaRefForTest() {
171 j_media_session_.Reset();
174 bool MediaSession::IsActiveForTest() const {
175 return audio_focus_state_ == State::ACTIVE;
178 MediaSession::Type MediaSession::audio_focus_type_for_test() const {
179 return audio_focus_type_;
182 void MediaSession::RemoveAllPlayersForTest() {
183 players_.clear();
184 AbandonSystemAudioFocusIfNeeded();
187 void MediaSession::OnSuspendInternal(SuspendType type) {
188 audio_focus_state_ = State::SUSPENDED;
189 suspend_type_ = type;
191 for (const auto& it : players_)
192 it.observer->OnSuspend(it.player_id);
195 void MediaSession::OnResumeInternal(SuspendType type) {
196 if (suspend_type_ != type && type != SuspendType::UI)
197 return;
199 audio_focus_state_ = State::ACTIVE;
201 for (const auto& it : players_)
202 it.observer->OnResume(it.player_id);
205 MediaSession::MediaSession(WebContents* web_contents)
206 : WebContentsObserver(web_contents),
207 audio_focus_state_(State::INACTIVE),
208 audio_focus_type_(Type::Transient) {}
210 void MediaSession::Initialize() {
211 JNIEnv* env = base::android::AttachCurrentThread();
212 DCHECK(env);
213 j_media_session_.Reset(Java_MediaSession_createMediaSession(
214 env,
215 base::android::GetApplicationContext(),
216 reinterpret_cast<intptr_t>(this)));
219 bool MediaSession::RequestSystemAudioFocus(Type type) {
220 // During tests, j_media_session_ might be null.
221 if (j_media_session_.is_null())
222 return true;
224 JNIEnv* env = base::android::AttachCurrentThread();
225 DCHECK(env);
226 return Java_MediaSession_requestAudioFocus(env, j_media_session_.obj(),
227 type == Type::Transient);
230 void MediaSession::AbandonSystemAudioFocusIfNeeded() {
231 if (audio_focus_state_ == State::INACTIVE || !players_.empty())
232 return;
234 // During tests, j_media_session_ might be null.
235 if (!j_media_session_.is_null()) {
236 JNIEnv* env = base::android::AttachCurrentThread();
237 DCHECK(env);
238 Java_MediaSession_abandonAudioFocus(env, j_media_session_.obj());
241 audio_focus_state_ = State::INACTIVE;
242 UpdateWebContents();
245 void MediaSession::UpdateWebContents() {
246 static_cast<WebContentsImpl*>(web_contents())->OnMediaSessionStateChanged();
249 } // namespace content