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

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

Issue 2453623003: Decouple MediaSession messages from WebContents (full patch) (Closed)
Patch Set: nit Created 4 years, 1 month 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/session/media_session.h"
6
7 #include "content/browser/media/session/audio_focus_delegate.h"
8 #include "content/browser/media/session/media_session_player_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 "media/base/media_content_type.h"
13
14 namespace content {
15
16 namespace {
17
18 const double kDefaultVolumeMultiplier = 1.0;
19 const double kDuckingVolumeMultiplier = 0.2;
20
21 } // anonymous namespace
22
23 using MediaSessionSuspendedSource =
24 MediaSessionUmaHelper::MediaSessionSuspendedSource;
25
26 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MediaSession);
27
28 MediaSession::PlayerIdentifier::PlayerIdentifier(
29 MediaSessionPlayerObserver* observer,
30 int player_id)
31 : observer(observer), player_id(player_id) {}
32
33 bool MediaSession::PlayerIdentifier::operator==(
34 const PlayerIdentifier& other) const {
35 return this->observer == other.observer && this->player_id == other.player_id;
36 }
37
38 size_t MediaSession::PlayerIdentifier::Hash::operator()(
39 const PlayerIdentifier& player_identifier) const {
40 size_t hash = BASE_HASH_NAMESPACE::hash<MediaSessionPlayerObserver*>()(
41 player_identifier.observer);
42 hash += BASE_HASH_NAMESPACE::hash<int>()(player_identifier.player_id);
43 return hash;
44 }
45
46 // static
47 MediaSession* MediaSession::Get(WebContents* web_contents) {
48 MediaSession* session = FromWebContents(web_contents);
49 if (!session) {
50 CreateForWebContents(web_contents);
51 session = FromWebContents(web_contents);
52 session->Initialize();
53 }
54 return session;
55 }
56
57 MediaSession::~MediaSession() {
58 DCHECK(players_.empty());
59 DCHECK(audio_focus_state_ == State::INACTIVE);
60 }
61
62 void MediaSession::WebContentsDestroyed() {
63 // This should only work for tests. In production, all the players should have
64 // already been removed before WebContents is destroyed.
65
66 // TODO(zqzhang): refactor MediaSession, maybe move the interface used to talk
67 // with AudioFocusManager out to a seperate class. The AudioFocusManager unit
68 // tests then could mock the interface and abandon audio focus when
69 // WebContents is destroyed. See https://crbug.com/651069
70 players_.clear();
71 pepper_players_.clear();
72 AbandonSystemAudioFocusIfNeeded();
73 }
74
75 void MediaSession::SetMetadata(const base::Optional<MediaMetadata>& metadata) {
76 metadata_ = metadata;
77 static_cast<WebContentsImpl*>(web_contents())
78 ->OnMediaSessionMetadataChanged();
79 }
80
81 bool MediaSession::AddPlayer(MediaSessionPlayerObserver* observer,
82 int player_id,
83 media::MediaContentType media_content_type) {
84 if (media_content_type == media::MediaContentType::Uncontrollable)
85 return true;
86 if (media_content_type == media::MediaContentType::Pepper)
87 return AddPepperPlayer(observer, player_id);
88
89 observer->OnSetVolumeMultiplier(player_id, GetVolumeMultiplier());
90
91 // Determine the audio focus type required for playing the new player.
92 // TODO(zqzhang): handle duckable and uncontrollable.
93 // See https://crbug.com/639277.
94 AudioFocusManager::AudioFocusType required_audio_focus_type;
95 if (media_content_type == media::MediaContentType::Persistent) {
96 required_audio_focus_type = AudioFocusManager::AudioFocusType::Gain;
97 } else {
98 required_audio_focus_type =
99 AudioFocusManager::AudioFocusType::GainTransientMayDuck;
100 }
101
102 // If the audio focus is already granted and is of type Content, there is
103 // nothing to do. If it is granted of type Transient the requested type is
104 // also transient, there is also nothing to do. Otherwise, the session needs
105 // to request audio focus again.
106 if (audio_focus_state_ == State::ACTIVE &&
107 (audio_focus_type_ == AudioFocusManager::AudioFocusType::Gain ||
108 audio_focus_type_ == required_audio_focus_type)) {
109 players_.insert(PlayerIdentifier(observer, player_id));
110 return true;
111 }
112
113 State old_audio_focus_state = audio_focus_state_;
114 RequestSystemAudioFocus(required_audio_focus_type);
115
116 if (audio_focus_state_ != State::ACTIVE)
117 return false;
118
119 // The session should be reset if a player is starting while all players are
120 // suspended.
121 if (old_audio_focus_state != State::ACTIVE)
122 players_.clear();
123
124 players_.insert(PlayerIdentifier(observer, player_id));
125 UpdateWebContents();
126
127 return true;
128 }
129
130 void MediaSession::RemovePlayer(MediaSessionPlayerObserver* observer,
131 int player_id) {
132 auto it = players_.find(PlayerIdentifier(observer, player_id));
133 if (it != players_.end())
134 players_.erase(it);
135
136 it = pepper_players_.find(PlayerIdentifier(observer, player_id));
137 if (it != pepper_players_.end())
138 pepper_players_.erase(it);
139
140 AbandonSystemAudioFocusIfNeeded();
141 }
142
143 void MediaSession::RemovePlayers(MediaSessionPlayerObserver* observer) {
144 for (auto it = players_.begin(); it != players_.end(); ) {
145 if (it->observer == observer)
146 players_.erase(it++);
147 else
148 ++it;
149 }
150
151 for (auto it = pepper_players_.begin(); it != pepper_players_.end(); ) {
152 if (it->observer == observer)
153 pepper_players_.erase(it++);
154 else
155 ++it;
156 }
157
158 AbandonSystemAudioFocusIfNeeded();
159 }
160
161 void MediaSession::RecordSessionDuck() {
162 uma_helper_.RecordSessionSuspended(
163 MediaSessionSuspendedSource::SystemTransientDuck);
164 }
165
166 void MediaSession::OnPlayerPaused(MediaSessionPlayerObserver* observer,
167 int player_id) {
168 // If a playback is completed, BrowserMediaPlayerManager will call
169 // OnPlayerPaused() after RemovePlayer(). This is a workaround.
170 // Also, this method may be called when a player that is not added
171 // to this session (e.g. a silent video) is paused. MediaSession
172 // should ignore the paused player for this case.
173 if (!players_.count(PlayerIdentifier(observer, player_id)) &&
174 !pepper_players_.count(PlayerIdentifier(observer, player_id))) {
175 return;
176 }
177
178 // If the player to be removed is a pepper player, or there is more than one
179 // observer, remove the paused one from the session.
180 if (pepper_players_.count(PlayerIdentifier(observer, player_id)) ||
181 players_.size() != 1) {
182 RemovePlayer(observer, player_id);
183 return;
184 }
185
186 // Otherwise, suspend the session.
187 DCHECK(!IsSuspended());
188 OnSuspendInternal(SuspendType::CONTENT, State::SUSPENDED);
189 }
190
191 void MediaSession::Resume(SuspendType suspend_type) {
192 DCHECK(IsReallySuspended());
193
194 // When the resume requests comes from another source than system, audio focus
195 // must be requested.
196 if (suspend_type != SuspendType::SYSTEM) {
197 // Request audio focus again in case we lost it because another app started
198 // playing while the playback was paused.
199 State audio_focus_state = RequestSystemAudioFocus(audio_focus_type_)
200 ? State::ACTIVE
201 : State::INACTIVE;
202 SetAudioFocusState(audio_focus_state);
203
204 if (audio_focus_state_ != State::ACTIVE)
205 return;
206 }
207
208 OnResumeInternal(suspend_type);
209 }
210
211 void MediaSession::Suspend(SuspendType suspend_type) {
212 DCHECK(!IsSuspended());
213
214 OnSuspendInternal(suspend_type, State::SUSPENDED);
215 }
216
217 void MediaSession::Stop(SuspendType suspend_type) {
218 DCHECK(audio_focus_state_ != State::INACTIVE);
219 DCHECK(suspend_type != SuspendType::CONTENT);
220 DCHECK(!HasPepper());
221
222 // TODO(mlamouri): merge the logic between UI and SYSTEM.
223 if (suspend_type == SuspendType::SYSTEM) {
224 OnSuspendInternal(suspend_type, State::INACTIVE);
225 return;
226 }
227
228 if (audio_focus_state_ != State::SUSPENDED)
229 OnSuspendInternal(suspend_type, State::SUSPENDED);
230
231 DCHECK(audio_focus_state_ == State::SUSPENDED);
232 players_.clear();
233
234 AbandonSystemAudioFocusIfNeeded();
235 }
236
237 void MediaSession::StartDucking() {
238 if (is_ducking_)
239 return;
240 is_ducking_ = true;
241 UpdateVolumeMultiplier();
242 }
243
244 void MediaSession::StopDucking() {
245 if (!is_ducking_)
246 return;
247 is_ducking_ = false;
248 UpdateVolumeMultiplier();
249 }
250
251 void MediaSession::UpdateVolumeMultiplier() {
252 for (const auto& it : players_)
253 it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
254 for (const auto& it : pepper_players_)
255 it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
256 }
257
258 double MediaSession::GetVolumeMultiplier() const {
259 return is_ducking_ ? kDuckingVolumeMultiplier : kDefaultVolumeMultiplier;
260 }
261
262 bool MediaSession::IsActive() const {
263 return audio_focus_state_ == State::ACTIVE;
264 }
265
266 bool MediaSession::IsReallySuspended() const {
267 return audio_focus_state_ == State::SUSPENDED;
268 }
269
270 bool MediaSession::IsSuspended() const {
271 // TODO(mlamouri): should be == State::SUSPENDED.
272 return audio_focus_state_ != State::ACTIVE;
273 }
274
275 bool MediaSession::IsControllable() const {
276 // Only media session having focus Gain can be controllable unless it is
277 // inactive.
278 return audio_focus_state_ != State::INACTIVE &&
279 audio_focus_type_ == AudioFocusManager::AudioFocusType::Gain;
280 }
281
282 bool MediaSession::HasPepper() const {
283 return !pepper_players_.empty();
284 }
285
286 std::unique_ptr<base::CallbackList<void(MediaSession::State)>::Subscription>
287 MediaSession::RegisterMediaSessionStateChangedCallbackForTest(
288 const StateChangedCallback& cb) {
289 return media_session_state_listeners_.Add(cb);
290 }
291
292 void MediaSession::SetDelegateForTests(
293 std::unique_ptr<AudioFocusDelegate> delegate) {
294 delegate_ = std::move(delegate);
295 }
296
297 bool MediaSession::IsActiveForTest() const {
298 return audio_focus_state_ == State::ACTIVE;
299 }
300
301 MediaSessionUmaHelper* MediaSession::uma_helper_for_test() {
302 return &uma_helper_;
303 }
304
305 void MediaSession::RemoveAllPlayersForTest() {
306 players_.clear();
307 AbandonSystemAudioFocusIfNeeded();
308 }
309
310 void MediaSession::OnSuspendInternal(SuspendType suspend_type,
311 State new_state) {
312 DCHECK(!HasPepper());
313
314 DCHECK(new_state == State::SUSPENDED || new_state == State::INACTIVE);
315 // UI suspend cannot use State::INACTIVE.
316 DCHECK(suspend_type == SuspendType::SYSTEM || new_state == State::SUSPENDED);
317
318 if (audio_focus_state_ != State::ACTIVE)
319 return;
320
321 switch (suspend_type) {
322 case SuspendType::UI:
323 uma_helper_.RecordSessionSuspended(MediaSessionSuspendedSource::UI);
324 break;
325 case SuspendType::SYSTEM:
326 switch (new_state) {
327 case State::SUSPENDED:
328 uma_helper_.RecordSessionSuspended(
329 MediaSessionSuspendedSource::SystemTransient);
330 break;
331 case State::INACTIVE:
332 uma_helper_.RecordSessionSuspended(
333 MediaSessionSuspendedSource::SystemPermanent);
334 break;
335 case State::ACTIVE:
336 NOTREACHED();
337 break;
338 }
339 break;
340 case SuspendType::CONTENT:
341 uma_helper_.RecordSessionSuspended(MediaSessionSuspendedSource::CONTENT);
342 break;
343 }
344
345 SetAudioFocusState(new_state);
346 suspend_type_ = suspend_type;
347
348 if (suspend_type != SuspendType::CONTENT) {
349 // SuspendType::CONTENT happens when the suspend action came from
350 // the page in which case the player is already paused.
351 // Otherwise, the players need to be paused.
352 for (const auto& it : players_)
353 it.observer->OnSuspend(it.player_id);
354 }
355
356 for (const auto& it : pepper_players_)
357 it.observer->OnSetVolumeMultiplier(it.player_id, kDuckingVolumeMultiplier);
358
359 UpdateWebContents();
360 }
361
362 void MediaSession::OnResumeInternal(SuspendType suspend_type) {
363 if (suspend_type == SuspendType::SYSTEM && suspend_type_ != suspend_type)
364 return;
365
366 SetAudioFocusState(State::ACTIVE);
367
368 for (const auto& it : players_)
369 it.observer->OnResume(it.player_id);
370
371 for (const auto& it : pepper_players_)
372 it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
373
374 UpdateWebContents();
375 }
376
377 MediaSession::MediaSession(WebContents* web_contents)
378 : WebContentsObserver(web_contents),
379 audio_focus_state_(State::INACTIVE),
380 audio_focus_type_(
381 AudioFocusManager::AudioFocusType::GainTransientMayDuck),
382 is_ducking_(false) {}
383
384 void MediaSession::Initialize() {
385 delegate_ = AudioFocusDelegate::Create(this);
386 }
387
388 bool MediaSession::RequestSystemAudioFocus(
389 AudioFocusManager::AudioFocusType audio_focus_type) {
390 bool result = delegate_->RequestAudioFocus(audio_focus_type);
391 uma_helper_.RecordRequestAudioFocusResult(result);
392
393 // MediaSession must change its state & audio focus type AFTER requesting
394 // audio focus.
395 SetAudioFocusState(result ? State::ACTIVE : State::INACTIVE);
396 audio_focus_type_ = audio_focus_type;
397 return result;
398 }
399
400 void MediaSession::AbandonSystemAudioFocusIfNeeded() {
401 if (audio_focus_state_ == State::INACTIVE || !players_.empty() ||
402 !pepper_players_.empty()) {
403 return;
404 }
405 delegate_->AbandonAudioFocus();
406
407 SetAudioFocusState(State::INACTIVE);
408 UpdateWebContents();
409 }
410
411 void MediaSession::UpdateWebContents() {
412 media_session_state_listeners_.Notify(audio_focus_state_);
413 static_cast<WebContentsImpl*>(web_contents())->OnMediaSessionStateChanged();
414 }
415
416 void MediaSession::SetAudioFocusState(State audio_focus_state) {
417 if (audio_focus_state == audio_focus_state_)
418 return;
419
420 audio_focus_state_ = audio_focus_state;
421 switch (audio_focus_state_) {
422 case State::ACTIVE:
423 uma_helper_.OnSessionActive();
424 break;
425 case State::SUSPENDED:
426 uma_helper_.OnSessionSuspended();
427 break;
428 case State::INACTIVE:
429 uma_helper_.OnSessionInactive();
430 break;
431 }
432 }
433
434 bool MediaSession::AddPepperPlayer(MediaSessionPlayerObserver* observer,
435 int player_id) {
436 bool success = RequestSystemAudioFocus(
437 AudioFocusManager::AudioFocusType::Gain);
438 DCHECK(success);
439
440 pepper_players_.insert(PlayerIdentifier(observer, player_id));
441
442 observer->OnSetVolumeMultiplier(player_id, GetVolumeMultiplier());
443
444 return true;
445 }
446
447 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/media/session/media_session.h ('k') | content/browser/media/session/media_session_android.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698