| 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 |