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"
16 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MediaSession
);
18 MediaSession::PlayerIdentifier::PlayerIdentifier(MediaSessionObserver
* 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
);
38 bool content::MediaSession::RegisterMediaSession(JNIEnv
* env
) {
39 return RegisterNativesImpl(env
);
43 MediaSession
* MediaSession::Get(WebContents
* web_contents
) {
44 MediaSession
* session
= FromWebContents(web_contents
);
46 CreateForWebContents(web_contents
);
47 session
= FromWebContents(web_contents
);
48 session
->Initialize();
53 MediaSession::~MediaSession() {
54 DCHECK(players_
.empty());
55 DCHECK(audio_focus_state_
== State::INACTIVE
);
58 bool MediaSession::AddPlayer(MediaSessionObserver
* observer
,
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
));
71 State old_audio_focus_state
= audio_focus_state_
;
72 audio_focus_state_
= RequestSystemAudioFocus(type
) ? State::ACTIVE
74 audio_focus_type_
= type
;
76 if (audio_focus_state_
!= State::ACTIVE
)
79 // The session should be reset if a player is starting while all players are
81 if (old_audio_focus_state
!= State::ACTIVE
)
84 players_
.insert(PlayerIdentifier(observer
, player_id
));
90 void MediaSession::RemovePlayer(MediaSessionObserver
* observer
,
92 auto it
= players_
.find(PlayerIdentifier(observer
, player_id
));
93 if (it
!= players_
.end())
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
++);
107 AbandonSystemAudioFocusIfNeeded();
110 void MediaSession::OnSuspend(JNIEnv
* env
, jobject obj
, jboolean temporary
) {
111 if (audio_focus_state_
!= State::ACTIVE
)
114 OnSuspendInternal(SuspendType::SYSTEM
);
116 audio_focus_state_
= State::INACTIVE
;
120 void MediaSession::OnResume(JNIEnv
* env
, jobject obj
) {
121 if (audio_focus_state_
!= State::SUSPENDED
)
124 OnResumeInternal(SuspendType::SYSTEM
);
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_
)
136 if (audio_focus_state_
!= State::ACTIVE
)
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
);
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() {
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
)
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();
213 j_media_session_
.Reset(Java_MediaSession_createMediaSession(
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())
224 JNIEnv
* env
= base::android::AttachCurrentThread();
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())
234 // During tests, j_media_session_ might be null.
235 if (!j_media_session_
.is_null()) {
236 JNIEnv
* env
= base::android::AttachCurrentThread();
238 Java_MediaSession_abandonAudioFocus(env
, j_media_session_
.obj());
241 audio_focus_state_
= State::INACTIVE
;
245 void MediaSession::UpdateWebContents() {
246 static_cast<WebContentsImpl
*>(web_contents())->OnMediaSessionStateChanged();
249 } // namespace content