Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(494)

Side by Side Diff: content/browser/media/android/media_session.cc

Issue 1698933004: Make MediaSession a runtime-enabled feature on Desktop. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW
« no previous file with comments | « content/browser/media/android/media_session.h ('k') | content/browser/media/android/media_session_browsertest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698