ContentPlaybackController.cpp (8616B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "ContentPlaybackController.h" 6 7 #include "MediaControlUtils.h" 8 #include "mozilla/dom/ContentMediaController.h" 9 #include "mozilla/dom/MediaSession.h" 10 #include "mozilla/dom/Navigator.h" 11 #include "mozilla/dom/WindowContext.h" 12 #include "nsFocusManager.h" 13 14 // avoid redefined macro in unified build 15 #undef LOG 16 #define LOG(msg, ...) \ 17 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ 18 ("ContentPlaybackController=%p, " msg, this, ##__VA_ARGS__)) 19 20 namespace mozilla::dom { 21 22 ContentPlaybackController::ContentPlaybackController( 23 BrowsingContext* aContext) { 24 MOZ_ASSERT(aContext); 25 mBC = aContext; 26 } 27 28 MediaSession* ContentPlaybackController::GetMediaSession() const { 29 RefPtr<nsPIDOMWindowOuter> window = mBC->GetDOMWindow(); 30 if (!window) { 31 return nullptr; 32 } 33 34 RefPtr<Navigator> navigator = window->GetNavigator(); 35 if (!navigator) { 36 return nullptr; 37 } 38 39 return navigator->HasCreatedMediaSession() ? navigator->MediaSession() 40 : nullptr; 41 } 42 43 void ContentPlaybackController::NotifyContentMediaControlKeyReceiver( 44 MediaControlKey aKey, Maybe<SeekDetails> aDetails) { 45 if (RefPtr<ContentMediaControlKeyReceiver> receiver = 46 ContentMediaControlKeyReceiver::Get(mBC)) { 47 LOG("Handle '%s' in default behavior for BC %" PRIu64, 48 GetEnumString(aKey).get(), mBC->Id()); 49 receiver->HandleMediaKey(aKey, aDetails); 50 } 51 } 52 53 void ContentPlaybackController::NotifyMediaSession(MediaSessionAction aAction) { 54 MediaSessionActionDetails details; 55 details.mAction = aAction; 56 NotifyMediaSession(details); 57 } 58 59 void ContentPlaybackController::NotifyMediaSession( 60 const MediaSessionActionDetails& aDetails) { 61 if (RefPtr<MediaSession> session = GetMediaSession()) { 62 LOG("Handle '%s' in media session behavior for BC %" PRIu64, 63 GetEnumString(aDetails.mAction).get(), mBC->Id()); 64 MOZ_ASSERT(session->IsActive(), "Notify inactive media session!"); 65 session->NotifyHandler(aDetails); 66 } 67 } 68 69 void ContentPlaybackController::NotifyMediaSessionWhenActionIsSupported( 70 MediaSessionAction aAction) { 71 if (IsMediaSessionActionSupported(aAction)) { 72 NotifyMediaSession(aAction); 73 } 74 } 75 76 bool ContentPlaybackController::IsMediaSessionActionSupported( 77 MediaSessionAction aAction) const { 78 RefPtr<MediaSession> session = GetMediaSession(); 79 return session ? session->IsActive() && session->IsSupportedAction(aAction) 80 : false; 81 } 82 83 Maybe<uint64_t> ContentPlaybackController::GetActiveMediaSessionId() const { 84 RefPtr<WindowContext> wc = mBC->GetTopWindowContext(); 85 return wc ? wc->GetActiveMediaSessionContextId() : Nothing(); 86 } 87 88 void ContentPlaybackController::Focus() { 89 // Focus is not part of the MediaSession standard, so always use the 90 // default behavior and focus the window currently playing media. 91 if (nsCOMPtr<nsPIDOMWindowOuter> win = mBC->GetDOMWindow()) { 92 nsFocusManager::FocusWindow(win, CallerType::System); 93 } 94 } 95 96 void ContentPlaybackController::Play() { 97 const MediaSessionAction action = MediaSessionAction::Play; 98 RefPtr<MediaSession> session = GetMediaSession(); 99 if (IsMediaSessionActionSupported(action)) { 100 NotifyMediaSession(action); 101 } 102 // We don't want to arbitrarily call play default handler, because we want to 103 // resume the frame which a user really gets interest in, not all media in the 104 // same page. Therefore, we would only call default handler for `play` when 105 // (1) We don't have an active media session (If we have one, the play action 106 // handler should only be triggered on that session) 107 // (2) Active media session without setting action handler for `play` 108 else if (!GetActiveMediaSessionId() || (session && session->IsActive())) { 109 NotifyContentMediaControlKeyReceiver(MediaControlKey::Play); 110 } 111 } 112 113 void ContentPlaybackController::Pause() { 114 const MediaSessionAction action = MediaSessionAction::Pause; 115 if (IsMediaSessionActionSupported(action)) { 116 NotifyMediaSession(action); 117 } else { 118 NotifyContentMediaControlKeyReceiver(MediaControlKey::Pause); 119 } 120 } 121 122 void ContentPlaybackController::SeekBackward(double aSeekOffset) { 123 MediaSessionActionDetails details; 124 details.mAction = MediaSessionAction::Seekbackward; 125 details.mSeekOffset.Construct(aSeekOffset); 126 RefPtr<MediaSession> session = GetMediaSession(); 127 if (IsMediaSessionActionSupported(details.mAction)) { 128 NotifyMediaSession(details); 129 } else if (!GetActiveMediaSessionId() || (session && session->IsActive())) { 130 NotifyContentMediaControlKeyReceiver(MediaControlKey::Seekbackward, 131 Some(SeekDetails(aSeekOffset))); 132 } 133 } 134 135 void ContentPlaybackController::SeekForward(double aSeekOffset) { 136 MediaSessionActionDetails details; 137 details.mAction = MediaSessionAction::Seekforward; 138 details.mSeekOffset.Construct(aSeekOffset); 139 RefPtr<MediaSession> session = GetMediaSession(); 140 if (IsMediaSessionActionSupported(details.mAction)) { 141 NotifyMediaSession(details); 142 } else if (!GetActiveMediaSessionId() || (session && session->IsActive())) { 143 NotifyContentMediaControlKeyReceiver(MediaControlKey::Seekforward, 144 Some(SeekDetails(aSeekOffset))); 145 } 146 } 147 148 void ContentPlaybackController::PreviousTrack() { 149 NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Previoustrack); 150 } 151 152 void ContentPlaybackController::NextTrack() { 153 NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Nexttrack); 154 } 155 156 void ContentPlaybackController::SkipAd() { 157 NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Skipad); 158 } 159 160 void ContentPlaybackController::Stop() { 161 const MediaSessionAction action = MediaSessionAction::Stop; 162 if (IsMediaSessionActionSupported(action)) { 163 NotifyMediaSession(action); 164 } else { 165 NotifyContentMediaControlKeyReceiver(MediaControlKey::Stop); 166 } 167 } 168 169 void ContentPlaybackController::SeekTo(double aSeekTime, bool aFastSeek) { 170 MediaSessionActionDetails details; 171 details.mAction = MediaSessionAction::Seekto; 172 details.mSeekTime.Construct(aSeekTime); 173 RefPtr<MediaSession> session = GetMediaSession(); 174 if (aFastSeek) { 175 details.mFastSeek.Construct(aFastSeek); 176 } 177 if (IsMediaSessionActionSupported(details.mAction)) { 178 NotifyMediaSession(details); 179 } else if (!GetActiveMediaSessionId() || (session && session->IsActive())) { 180 NotifyContentMediaControlKeyReceiver( 181 MediaControlKey::Seekto, Some(SeekDetails(aSeekTime, aFastSeek))); 182 } 183 } 184 185 void ContentMediaControlKeyHandler::HandleMediaControlAction( 186 BrowsingContext* aContext, const MediaControlAction& aAction) { 187 MOZ_ASSERT(aContext); 188 // The web content doesn't exist in this browsing context. 189 if (!aContext->GetDocShell()) { 190 return; 191 } 192 if (aAction.mKey.isNothing()) { 193 MOZ_ASSERT_UNREACHABLE("Invalid media control key."); 194 return; 195 } 196 ContentPlaybackController controller(aContext); 197 switch (aAction.mKey.value()) { 198 case MediaControlKey::Focus: 199 controller.Focus(); 200 return; 201 case MediaControlKey::Play: 202 controller.Play(); 203 return; 204 case MediaControlKey::Pause: 205 controller.Pause(); 206 return; 207 case MediaControlKey::Playpause: 208 MOZ_ASSERT_UNREACHABLE("Invalid media control key."); 209 return; 210 case MediaControlKey::Stop: 211 controller.Stop(); 212 return; 213 case MediaControlKey::Previoustrack: 214 controller.PreviousTrack(); 215 return; 216 case MediaControlKey::Nexttrack: 217 controller.NextTrack(); 218 return; 219 case MediaControlKey::Seekbackward: { 220 const SeekDetails& details = *aAction.mDetails; 221 MOZ_ASSERT(details.mRelativeSeekOffset); 222 controller.SeekBackward(details.mRelativeSeekOffset.value()); 223 return; 224 } 225 case MediaControlKey::Seekforward: { 226 const SeekDetails& details = *aAction.mDetails; 227 MOZ_ASSERT(details.mRelativeSeekOffset); 228 controller.SeekForward(details.mRelativeSeekOffset.value()); 229 return; 230 } 231 case MediaControlKey::Skipad: 232 controller.SkipAd(); 233 return; 234 case MediaControlKey::Seekto: { 235 const SeekDetails& details = *aAction.mDetails; 236 MOZ_ASSERT(details.mAbsolute); 237 controller.SeekTo(details.mAbsolute->mSeekTime, 238 details.mAbsolute->mFastSeek); 239 return; 240 } 241 default: 242 MOZ_ASSERT_UNREACHABLE("Invalid media control key."); 243 }; 244 } 245 246 } // namespace mozilla::dom