summaryrefslogtreecommitdiffstats
path: root/dom/media/mediacontrol/ContentPlaybackController.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/mediacontrol/ContentPlaybackController.cpp210
1 files changed, 210 insertions, 0 deletions
diff --git a/dom/media/mediacontrol/ContentPlaybackController.cpp b/dom/media/mediacontrol/ContentPlaybackController.cpp
new file mode 100644
index 0000000000..ba48c0a5ce
--- /dev/null
+++ b/dom/media/mediacontrol/ContentPlaybackController.cpp
@@ -0,0 +1,210 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ContentPlaybackController.h"
+
+#include "MediaControlUtils.h"
+#include "mozilla/dom/ContentMediaController.h"
+#include "mozilla/dom/MediaSession.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/Telemetry.h"
+#include "nsFocusManager.h"
+
+// avoid redefined macro in unified build
+#undef LOG
+#define LOG(msg, ...) \
+ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
+ ("ContentPlaybackController=%p, " msg, this, ##__VA_ARGS__))
+
+namespace mozilla::dom {
+
+ContentPlaybackController::ContentPlaybackController(
+ BrowsingContext* aContext) {
+ MOZ_ASSERT(aContext);
+ mBC = aContext;
+}
+
+MediaSession* ContentPlaybackController::GetMediaSession() const {
+ RefPtr<nsPIDOMWindowOuter> window = mBC->GetDOMWindow();
+ if (!window) {
+ return nullptr;
+ }
+
+ RefPtr<Navigator> navigator = window->GetNavigator();
+ if (!navigator) {
+ return nullptr;
+ }
+
+ return navigator->HasCreatedMediaSession() ? navigator->MediaSession()
+ : nullptr;
+}
+
+void ContentPlaybackController::NotifyContentMediaControlKeyReceiver(
+ MediaControlKey aKey) {
+ if (RefPtr<ContentMediaControlKeyReceiver> receiver =
+ ContentMediaControlKeyReceiver::Get(mBC)) {
+ LOG("Handle '%s' in default behavior for BC %" PRIu64,
+ ToMediaControlKeyStr(aKey), mBC->Id());
+ receiver->HandleMediaKey(aKey);
+ }
+}
+
+void ContentPlaybackController::NotifyMediaSession(MediaSessionAction aAction) {
+ MediaSessionActionDetails details;
+ details.mAction = aAction;
+ NotifyMediaSession(details);
+}
+
+void ContentPlaybackController::NotifyMediaSession(
+ const MediaSessionActionDetails& aDetails) {
+ if (RefPtr<MediaSession> session = GetMediaSession()) {
+ LOG("Handle '%s' in media session behavior for BC %" PRIu64,
+ ToMediaSessionActionStr(aDetails.mAction), mBC->Id());
+ MOZ_ASSERT(session->IsActive(), "Notify inactive media session!");
+ session->NotifyHandler(aDetails);
+ }
+}
+
+void ContentPlaybackController::NotifyMediaSessionWhenActionIsSupported(
+ MediaSessionAction aAction) {
+ if (IsMediaSessionActionSupported(aAction)) {
+ NotifyMediaSession(aAction);
+ }
+}
+
+bool ContentPlaybackController::IsMediaSessionActionSupported(
+ MediaSessionAction aAction) const {
+ RefPtr<MediaSession> session = GetMediaSession();
+ return session ? session->IsActive() && session->IsSupportedAction(aAction)
+ : false;
+}
+
+Maybe<uint64_t> ContentPlaybackController::GetActiveMediaSessionId() const {
+ RefPtr<WindowContext> wc = mBC->GetTopWindowContext();
+ return wc ? wc->GetActiveMediaSessionContextId() : Nothing();
+}
+
+void ContentPlaybackController::Focus() {
+ // Focus is not part of the MediaSession standard, so always use the
+ // default behavior and focus the window currently playing media.
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = mBC->GetDOMWindow()) {
+ nsFocusManager::FocusWindow(win, CallerType::System);
+ }
+}
+
+void ContentPlaybackController::Play() {
+ const MediaSessionAction action = MediaSessionAction::Play;
+ RefPtr<MediaSession> session = GetMediaSession();
+ if (IsMediaSessionActionSupported(action)) {
+ NotifyMediaSession(action);
+ }
+ // We don't want to arbitrarily call play default handler, because we want to
+ // resume the frame which a user really gets interest in, not all media in the
+ // same page. Therefore, we would only call default handler for `play` when
+ // (1) We don't have an active media session (If we have one, the play action
+ // handler should only be triggered on that session)
+ // (2) Active media session without setting action handler for `play`
+ else if (!GetActiveMediaSessionId() || (session && session->IsActive())) {
+ NotifyContentMediaControlKeyReceiver(MediaControlKey::Play);
+ }
+}
+
+void ContentPlaybackController::Pause() {
+ const MediaSessionAction action = MediaSessionAction::Pause;
+ if (IsMediaSessionActionSupported(action)) {
+ NotifyMediaSession(action);
+ } else {
+ NotifyContentMediaControlKeyReceiver(MediaControlKey::Pause);
+ }
+}
+
+void ContentPlaybackController::SeekBackward() {
+ NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Seekbackward);
+}
+
+void ContentPlaybackController::SeekForward() {
+ NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Seekforward);
+}
+
+void ContentPlaybackController::PreviousTrack() {
+ NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Previoustrack);
+}
+
+void ContentPlaybackController::NextTrack() {
+ NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Nexttrack);
+}
+
+void ContentPlaybackController::SkipAd() {
+ NotifyMediaSessionWhenActionIsSupported(MediaSessionAction::Skipad);
+}
+
+void ContentPlaybackController::Stop() {
+ const MediaSessionAction action = MediaSessionAction::Stop;
+ if (IsMediaSessionActionSupported(action)) {
+ NotifyMediaSession(action);
+ } else {
+ NotifyContentMediaControlKeyReceiver(MediaControlKey::Stop);
+ }
+}
+
+void ContentPlaybackController::SeekTo(double aSeekTime, bool aFastSeek) {
+ MediaSessionActionDetails details;
+ details.mAction = MediaSessionAction::Seekto;
+ details.mSeekTime.Construct(aSeekTime);
+ if (aFastSeek) {
+ details.mFastSeek.Construct(aFastSeek);
+ }
+ if (IsMediaSessionActionSupported(details.mAction)) {
+ NotifyMediaSession(details);
+ }
+}
+
+void ContentMediaControlKeyHandler::HandleMediaControlAction(
+ BrowsingContext* aContext, const MediaControlAction& aAction) {
+ MOZ_ASSERT(aContext);
+ // The web content doesn't exist in this browsing context.
+ if (!aContext->GetDocShell()) {
+ return;
+ }
+ ContentPlaybackController controller(aContext);
+ switch (aAction.mKey) {
+ case MediaControlKey::Focus:
+ controller.Focus();
+ return;
+ case MediaControlKey::Play:
+ controller.Play();
+ return;
+ case MediaControlKey::Pause:
+ controller.Pause();
+ return;
+ case MediaControlKey::Stop:
+ controller.Stop();
+ return;
+ case MediaControlKey::Previoustrack:
+ controller.PreviousTrack();
+ return;
+ case MediaControlKey::Nexttrack:
+ controller.NextTrack();
+ return;
+ case MediaControlKey::Seekbackward:
+ controller.SeekBackward();
+ return;
+ case MediaControlKey::Seekforward:
+ controller.SeekForward();
+ return;
+ case MediaControlKey::Skipad:
+ controller.SkipAd();
+ return;
+ case MediaControlKey::Seekto: {
+ const SeekDetails& details = *aAction.mDetails;
+ controller.SeekTo(details.mSeekTime, details.mFastSeek);
+ return;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid media control key.");
+ };
+}
+
+} // namespace mozilla::dom