OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "content/browser/media/android/media_session.h" | |
6 | |
7 #include "base/android/context_utils.h" | |
8 #include "base/android/jni_android.h" | |
9 #include "content/browser/media/android/media_session_observer.h" | |
10 #include "content/browser/web_contents/web_contents_impl.h" | |
11 #include "content/public/browser/web_contents.h" | |
12 #include "content/public/browser/web_contents_delegate.h" | |
13 #include "jni/MediaSession_jni.h" | |
14 #include "media/base/android/media_player_android.h" | |
15 | |
16 namespace content { | |
17 | |
18 using MediaSessionSuspendedSource = | |
19 MediaSessionUmaHelper::MediaSessionSuspendedSource; | |
20 | |
21 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MediaSession); | |
22 | |
23 MediaSession::PlayerIdentifier::PlayerIdentifier(MediaSessionObserver* observer, | |
24 int player_id) | |
25 : observer(observer), | |
26 player_id(player_id) { | |
27 } | |
28 | |
29 bool MediaSession::PlayerIdentifier::operator==( | |
30 const PlayerIdentifier& other) const { | |
31 return this->observer == other.observer && this->player_id == other.player_id; | |
32 } | |
33 | |
34 size_t MediaSession::PlayerIdentifier::Hash::operator()( | |
35 const PlayerIdentifier& player_identifier) const { | |
36 size_t hash = BASE_HASH_NAMESPACE::hash<MediaSessionObserver*>()( | |
37 player_identifier.observer); | |
38 hash += BASE_HASH_NAMESPACE::hash<int>()(player_identifier.player_id); | |
39 return hash; | |
40 } | |
41 | |
42 // static | |
43 bool content::MediaSession::RegisterMediaSession(JNIEnv* env) { | |
44 return RegisterNativesImpl(env); | |
45 } | |
46 | |
47 // static | |
48 MediaSession* MediaSession::Get(WebContents* web_contents) { | |
49 MediaSession* session = FromWebContents(web_contents); | |
50 if (!session) { | |
51 CreateForWebContents(web_contents); | |
52 session = FromWebContents(web_contents); | |
53 session->Initialize(); | |
54 } | |
55 return session; | |
56 } | |
57 | |
58 MediaSession::~MediaSession() { | |
59 DCHECK(players_.empty()); | |
60 DCHECK(audio_focus_state_ == State::INACTIVE); | |
61 } | |
62 | |
63 bool MediaSession::AddPlayer(MediaSessionObserver* observer, | |
64 int player_id, | |
65 Type type) { | |
66 observer->OnSetVolumeMultiplier(player_id, volume_multiplier_); | |
67 | |
68 // If the audio focus is already granted and is of type Content, there is | |
69 // nothing to do. If it is granted of type Transient the requested type is | |
70 // also transient, there is also nothing to do. Otherwise, the session needs | |
71 // to request audio focus again. | |
72 if (audio_focus_state_ == State::ACTIVE && | |
73 (audio_focus_type_ == Type::Content || audio_focus_type_ == type)) { | |
74 players_.insert(PlayerIdentifier(observer, player_id)); | |
75 return true; | |
76 } | |
77 | |
78 State old_audio_focus_state = audio_focus_state_; | |
79 State audio_focus_state = RequestSystemAudioFocus(type) ? State::ACTIVE | |
80 : State::INACTIVE; | |
81 SetAudioFocusState(audio_focus_state); | |
82 audio_focus_type_ = type; | |
83 | |
84 if (audio_focus_state_ != State::ACTIVE) | |
85 return false; | |
86 | |
87 // The session should be reset if a player is starting while all players are | |
88 // suspended. | |
89 if (old_audio_focus_state != State::ACTIVE) | |
90 players_.clear(); | |
91 | |
92 players_.insert(PlayerIdentifier(observer, player_id)); | |
93 UpdateWebContents(); | |
94 | |
95 return true; | |
96 } | |
97 | |
98 void MediaSession::RemovePlayer(MediaSessionObserver* observer, | |
99 int player_id) { | |
100 auto it = players_.find(PlayerIdentifier(observer, player_id)); | |
101 if (it != players_.end()) | |
102 players_.erase(it); | |
103 | |
104 AbandonSystemAudioFocusIfNeeded(); | |
105 } | |
106 | |
107 void MediaSession::RemovePlayers(MediaSessionObserver* observer) { | |
108 for (auto it = players_.begin(); it != players_.end();) { | |
109 if (it->observer == observer) | |
110 players_.erase(it++); | |
111 else | |
112 ++it; | |
113 } | |
114 | |
115 AbandonSystemAudioFocusIfNeeded(); | |
116 } | |
117 | |
118 void MediaSession::OnSuspend(JNIEnv* env, | |
119 const JavaParamRef<jobject>& obj, | |
120 jboolean temporary) { | |
121 // TODO(mlamouri): this check makes it so that if a MediaSession is paused and | |
122 // then loses audio focus, it will still stay in the Suspended state. | |
123 // See https://crbug.com/539998 | |
124 if (audio_focus_state_ != State::ACTIVE) | |
125 return; | |
126 | |
127 OnSuspendInternal(SuspendType::SYSTEM, | |
128 temporary ? State::SUSPENDED : State::INACTIVE); | |
129 | |
130 } | |
131 | |
132 void MediaSession::OnResume(JNIEnv* env, const JavaParamRef<jobject>& obj) { | |
133 if (audio_focus_state_ != State::SUSPENDED) | |
134 return; | |
135 | |
136 OnResumeInternal(SuspendType::SYSTEM); | |
137 } | |
138 | |
139 void MediaSession::RecordSessionDuck(JNIEnv* env, | |
140 const JavaParamRef<jobject>& obj) { | |
141 uma_helper_.RecordSessionSuspended( | |
142 MediaSessionSuspendedSource::SystemTransientDuck); | |
143 } | |
144 | |
145 void MediaSession::OnPlayerPaused(MediaSessionObserver* observer, | |
146 int player_id) { | |
147 // If a playback is completed, BrowserMediaPlayerManager will call | |
148 // OnPlayerPaused() after RemovePlayer(). This is a workaround. | |
149 // Also, this method may be called when a player that is not added | |
150 // to this session (e.g. a silent video) is paused. MediaSession | |
151 // should ignore the paused player for this case. | |
152 if (!players_.count(PlayerIdentifier(observer, player_id))) | |
153 return; | |
154 | |
155 // If there is more than one observer, remove the paused one from the session. | |
156 if (players_.size() != 1) { | |
157 RemovePlayer(observer, player_id); | |
158 return; | |
159 } | |
160 | |
161 // Otherwise, suspend the session. | |
162 DCHECK(!IsSuspended()); | |
163 OnSuspendInternal(SuspendType::CONTENT, State::SUSPENDED); | |
164 } | |
165 void MediaSession::OnSetVolumeMultiplier(JNIEnv *env, jobject obj, | |
166 jdouble volume_multiplier) { | |
167 OnSetVolumeMultiplierInternal(volume_multiplier); | |
168 } | |
169 | |
170 void MediaSession::Resume() { | |
171 DCHECK(IsSuspended()); | |
172 | |
173 // Request audio focus again in case we lost it because another app started | |
174 // playing while the playback was paused. | |
175 State audio_focus_state = RequestSystemAudioFocus(audio_focus_type_) | |
176 ? State::ACTIVE | |
177 : State::INACTIVE; | |
178 SetAudioFocusState(audio_focus_state); | |
179 | |
180 if (audio_focus_state_ != State::ACTIVE) | |
181 return; | |
182 | |
183 OnResumeInternal(SuspendType::UI); | |
184 } | |
185 | |
186 void MediaSession::Suspend() { | |
187 DCHECK(!IsSuspended()); | |
188 | |
189 OnSuspendInternal(SuspendType::UI, State::SUSPENDED); | |
190 } | |
191 | |
192 void MediaSession::Stop() { | |
193 DCHECK(audio_focus_state_ != State::INACTIVE); | |
194 | |
195 if (audio_focus_state_ != State::SUSPENDED) | |
196 OnSuspendInternal(SuspendType::UI, State::SUSPENDED); | |
197 | |
198 DCHECK(audio_focus_state_ == State::SUSPENDED); | |
199 players_.clear(); | |
200 AbandonSystemAudioFocusIfNeeded(); | |
201 } | |
202 | |
203 bool MediaSession::IsSuspended() const { | |
204 // TODO(mlamouri): should be == State::SUSPENDED. | |
205 return audio_focus_state_ != State::ACTIVE; | |
206 } | |
207 | |
208 bool MediaSession::IsControllable() const { | |
209 // Only content type media session can be controllable unless it is inactive. | |
210 return audio_focus_state_ != State::INACTIVE && | |
211 audio_focus_type_ == Type::Content; | |
212 } | |
213 | |
214 void MediaSession::ResetJavaRefForTest() { | |
215 j_media_session_.Reset(); | |
216 } | |
217 | |
218 bool MediaSession::IsActiveForTest() const { | |
219 return audio_focus_state_ == State::ACTIVE; | |
220 } | |
221 | |
222 MediaSession::Type MediaSession::audio_focus_type_for_test() const { | |
223 return audio_focus_type_; | |
224 } | |
225 | |
226 MediaSessionUmaHelper* MediaSession::uma_helper_for_test() { | |
227 return &uma_helper_; | |
228 } | |
229 | |
230 void MediaSession::RemoveAllPlayersForTest() { | |
231 players_.clear(); | |
232 AbandonSystemAudioFocusIfNeeded(); | |
233 } | |
234 | |
235 void MediaSession::OnSuspendInternal(SuspendType type, State new_state) { | |
236 DCHECK(new_state == State::SUSPENDED || new_state == State::INACTIVE); | |
237 // UI suspend cannot use State::INACTIVE. | |
238 DCHECK(type == SuspendType::SYSTEM || new_state == State::SUSPENDED); | |
239 | |
240 switch (type) { | |
241 case SuspendType::UI: | |
242 uma_helper_.RecordSessionSuspended(MediaSessionSuspendedSource::UI); | |
243 break; | |
244 case SuspendType::SYSTEM: | |
245 switch (new_state) { | |
246 case State::SUSPENDED: | |
247 uma_helper_.RecordSessionSuspended( | |
248 MediaSessionSuspendedSource::SystemTransient); | |
249 break; | |
250 case State::INACTIVE: | |
251 uma_helper_.RecordSessionSuspended( | |
252 MediaSessionSuspendedSource::SystemPermanent); | |
253 break; | |
254 case State::ACTIVE: | |
255 NOTREACHED(); | |
256 break; | |
257 } | |
258 break; | |
259 case SuspendType::CONTENT: | |
260 uma_helper_.RecordSessionSuspended(MediaSessionSuspendedSource::CONTENT); | |
261 break; | |
262 } | |
263 | |
264 SetAudioFocusState(new_state); | |
265 suspend_type_ = type; | |
266 | |
267 if (type != SuspendType::CONTENT) { | |
268 // SuspendType::CONTENT happens when the suspend action came from | |
269 // the page in which case the player is already paused. | |
270 // Otherwise, the players need to be paused. | |
271 for (const auto& it : players_) | |
272 it.observer->OnSuspend(it.player_id); | |
273 } | |
274 | |
275 UpdateWebContents(); | |
276 } | |
277 | |
278 void MediaSession::OnResumeInternal(SuspendType type) { | |
279 if (type == SuspendType::SYSTEM && suspend_type_ != type) | |
280 return; | |
281 | |
282 SetAudioFocusState(State::ACTIVE); | |
283 | |
284 for (const auto& it : players_) | |
285 it.observer->OnResume(it.player_id); | |
286 | |
287 UpdateWebContents(); | |
288 } | |
289 | |
290 void MediaSession::OnSetVolumeMultiplierInternal(double volume_multiplier) { | |
291 volume_multiplier_ = volume_multiplier; | |
292 for (const auto& it : players_) | |
293 it.observer->OnSetVolumeMultiplier(it.player_id, volume_multiplier_); | |
294 } | |
295 | |
296 MediaSession::MediaSession(WebContents* web_contents) | |
297 : WebContentsObserver(web_contents), | |
298 audio_focus_state_(State::INACTIVE), | |
299 audio_focus_type_(Type::Transient), | |
300 volume_multiplier_(media::MediaPlayerAndroid::kDefaultVolumeMultiplier) { | |
301 } | |
302 | |
303 void MediaSession::Initialize() { | |
304 JNIEnv* env = base::android::AttachCurrentThread(); | |
305 DCHECK(env); | |
306 j_media_session_.Reset(Java_MediaSession_createMediaSession( | |
307 env, | |
308 base::android::GetApplicationContext(), | |
309 reinterpret_cast<intptr_t>(this))); | |
310 } | |
311 | |
312 bool MediaSession::RequestSystemAudioFocus(Type type) { | |
313 // During tests, j_media_session_ might be null. | |
314 if (j_media_session_.is_null()) | |
315 return true; | |
316 | |
317 JNIEnv* env = base::android::AttachCurrentThread(); | |
318 DCHECK(env); | |
319 bool result = Java_MediaSession_requestAudioFocus(env, j_media_session_.obj(), | |
320 type == Type::Transient); | |
321 uma_helper_.RecordRequestAudioFocusResult(result); | |
322 return result; | |
323 } | |
324 | |
325 void MediaSession::AbandonSystemAudioFocusIfNeeded() { | |
326 if (audio_focus_state_ == State::INACTIVE || !players_.empty()) | |
327 return; | |
328 | |
329 // During tests, j_media_session_ might be null. | |
330 if (!j_media_session_.is_null()) { | |
331 JNIEnv* env = base::android::AttachCurrentThread(); | |
332 DCHECK(env); | |
333 Java_MediaSession_abandonAudioFocus(env, j_media_session_.obj()); | |
334 } | |
335 | |
336 SetAudioFocusState(State::INACTIVE); | |
337 UpdateWebContents(); | |
338 } | |
339 | |
340 void MediaSession::UpdateWebContents() { | |
341 static_cast<WebContentsImpl*>(web_contents())->OnMediaSessionStateChanged(); | |
342 } | |
343 | |
344 void MediaSession::SetAudioFocusState(State audio_focus_state) { | |
345 if (audio_focus_state == audio_focus_state_) | |
346 return; | |
347 | |
348 audio_focus_state_ = audio_focus_state; | |
349 switch (audio_focus_state_) { | |
350 case State::ACTIVE: | |
351 uma_helper_.OnSessionActive(); | |
352 break; | |
353 case State::SUSPENDED: | |
354 uma_helper_.OnSessionSuspended(); | |
355 break; | |
356 case State::INACTIVE: | |
357 uma_helper_.OnSessionInactive(); | |
358 break; | |
359 } | |
360 } | |
361 | |
362 } // namespace content | |
OLD | NEW |