Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/media/mediacontrol/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 19 kB image not shown  

Quelle  MediaStatusManager.cpp

  Sprache: C
 

/* 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 "MediaStatusManager.h"

#include "MediaControlService.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/MediaControlUtils.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "nsContentUtils.h"
#include "nsIChromeRegistry.h"
#include "nsIObserverService.h"
#include "nsIXULAppInfo.h"
#include "nsNetUtil.h"

#ifdef MOZ_PLACES
#  include "nsIFaviconService.h"
#endif  // MOZ_PLACES

extern mozilla::LazyLogModule gMediaControlLog;

// avoid redefined macro in unified build
#undef LOG
#define LOG(msg, ...)                        \
  MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
          ("MediaStatusManager=%p, " msg, this##__VA_ARGS__))

namespace mozilla::dom {

static bool IsMetadataEmpty(const Maybe<MediaMetadataBase>& aMetadata) {
  // Media session's metadata is null.
  if (!aMetadata) {
    return true;
  }

  // All attirbutes in metadata are empty.
  // https://w3c.github.io/mediasession/#empty-metadata
  const MediaMetadataBase& metadata = *aMetadata;
  return metadata.mTitle.IsEmpty() && metadata.mArtist.IsEmpty() &&
         metadata.mAlbum.IsEmpty() && metadata.mArtwork.IsEmpty();
}

MediaStatusManager::MediaStatusManager(uint64_t aBrowsingContextId)
    : mTopLevelBrowsingContextId(aBrowsingContextId) {
  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
                        "MediaStatusManager only runs on Chrome process!");
}

void MediaStatusManager::NotifyMediaAudibleChanged(uint64_t aBrowsingContextId,
                                                   MediaAudibleState aState) {
  Maybe<uint64_t> oldAudioFocusOwnerId =
      mPlaybackStatusDelegate.GetAudioFocusOwnerContextId();
  mPlaybackStatusDelegate.UpdateMediaAudibleState(aBrowsingContextId, aState);
  Maybe<uint64_t> newAudioFocusOwnerId =
      mPlaybackStatusDelegate.GetAudioFocusOwnerContextId();
  if (oldAudioFocusOwnerId != newAudioFocusOwnerId) {
    HandleAudioFocusOwnerChanged(newAudioFocusOwnerId);
  }
}

void MediaStatusManager::NotifySessionCreated(uint64_t aBrowsingContextId) {
  const bool created = mMediaSessionInfoMap.WithEntryHandle(
      aBrowsingContextId, [&](auto&& entry) {
        if (entry) return false;

        LOG("Session %" PRIu64 " has been created", aBrowsingContextId);
        entry.Insert(MediaSessionInfo::EmptyInfo());
        return true;
      });

  if (created && IsSessionOwningAudioFocus(aBrowsingContextId)) {
    // This can't be done from within the WithEntryHandle functor, since it
    // accesses mMediaSessionInfoMap.
    SetActiveMediaSessionContextId(aBrowsingContextId);
  }
}

void MediaStatusManager::NotifySessionDestroyed(uint64_t aBrowsingContextId) {
  if (mMediaSessionInfoMap.Remove(aBrowsingContextId)) {
    LOG("Session %" PRIu64 " has been destroyed", aBrowsingContextId);

    if (mActiveMediaSessionContextId &&
        *mActiveMediaSessionContextId == aBrowsingContextId) {
      ClearActiveMediaSessionContextIdIfNeeded();
    }
  }
}

void MediaStatusManager::UpdateMetadata(
    uint64_t aBrowsingContextId, const Maybe<MediaMetadataBase>& aMetadata) {
  auto info = mMediaSessionInfoMap.Lookup(aBrowsingContextId);
  if (!info) {
    return;
  }
  if (IsMetadataEmpty(aMetadata)) {
    LOG("Reset metadata for session %" PRIu64, aBrowsingContextId);
    info->mMetadata.reset();
  } else {
    LOG("Update metadata for session %" PRIu64 " title=%s artist=%s album=%s",
        aBrowsingContextId, NS_ConvertUTF16toUTF8((*aMetadata).mTitle).get(),
        NS_ConvertUTF16toUTF8(aMetadata->mArtist).get(),
        NS_ConvertUTF16toUTF8(aMetadata->mAlbum).get());
    info->mMetadata = aMetadata;
  }
  // Only notify the event if the changed metadata belongs to the active media
  // session.
  if (mActiveMediaSessionContextId &&
      *mActiveMediaSessionContextId == aBrowsingContextId) {
    LOG("Notify metadata change for active session %" PRIu64,
        aBrowsingContextId);
    mMetadataChangedEvent.Notify(GetCurrentMediaMetadata());
  }
  if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
      obs->NotifyObservers(nullptr, "media-session-controller-metadata-changed",
                           nullptr);
    }
  }
}

void MediaStatusManager::HandleAudioFocusOwnerChanged(
    Maybe<uint64_t>& aBrowsingContextId) {
  // No one is holding the audio focus.
  if (!aBrowsingContextId) {
    LOG("No one is owning audio focus");
    return ClearActiveMediaSessionContextIdIfNeeded();
  }

  // This owner of audio focus doesn't have media session, so we should deactive
  // the active session because the active session must own the audio focus.
  if (!mMediaSessionInfoMap.Contains(*aBrowsingContextId)) {
    LOG("The owner of audio focus doesn't have media session");
    return ClearActiveMediaSessionContextIdIfNeeded();
  }

  // This owner has media session so it should become an active session context.
  SetActiveMediaSessionContextId(*aBrowsingContextId);
}

void MediaStatusManager::SetActiveMediaSessionContextId(
    uint64_t aBrowsingContextId) {
  if (mActiveMediaSessionContextId &&
      *mActiveMediaSessionContextId == aBrowsingContextId) {
    LOG("Active session context %" PRIu64 " keeps unchanged",
        *mActiveMediaSessionContextId);
    return;
  }
  mActiveMediaSessionContextId = Some(aBrowsingContextId);
  StoreMediaSessionContextIdOnWindowContext();
  LOG("context %" PRIu64 " becomes active session context",
      *mActiveMediaSessionContextId);
  mMetadataChangedEvent.Notify(GetCurrentMediaMetadata());
  mSupportedActionsChangedEvent.Notify(GetSupportedActions());
  mPositionStateChangedEvent.Notify(GetCurrentPositionState());
  if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
      obs->NotifyObservers(nullptr, "active-media-session-changed", nullptr);
    }
  }
}

void MediaStatusManager::ClearActiveMediaSessionContextIdIfNeeded() {
  if (!mActiveMediaSessionContextId) {
    return;
  }
  LOG("Clear active session context");
  mActiveMediaSessionContextId.reset();
  StoreMediaSessionContextIdOnWindowContext();
  mMetadataChangedEvent.Notify(GetCurrentMediaMetadata());
  mSupportedActionsChangedEvent.Notify(GetSupportedActions());
  mPositionStateChangedEvent.Notify(GetCurrentPositionState());
  if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
      obs->NotifyObservers(nullptr, "active-media-session-changed", nullptr);
    }
  }
}

void MediaStatusManager::StoreMediaSessionContextIdOnWindowContext() {
  RefPtr<CanonicalBrowsingContext> bc =
      CanonicalBrowsingContext::Get(mTopLevelBrowsingContextId);
  if (bc && bc->GetTopWindowContext()) {
    Unused << bc->GetTopWindowContext()->SetActiveMediaSessionContextId(
        mActiveMediaSessionContextId);
  }
}

bool MediaStatusManager::IsSessionOwningAudioFocus(
    uint64_t aBrowsingContextId) const {
  Maybe<uint64_t> audioFocusContextId =
      mPlaybackStatusDelegate.GetAudioFocusOwnerContextId();
  return audioFocusContextId ? *audioFocusContextId == aBrowsingContextId
                             : false;
}

MediaMetadataBase MediaStatusManager::CreateDefaultMetadata() const {
  MediaMetadataBase metadata;
  metadata.mTitle = GetDefaultTitle();
  metadata.mUrl = GetUrl();
  metadata.mArtwork.AppendElement()->mSrc = GetDefaultFaviconURL();

  LOG("Default media metadata, title=%s, album src=%s",
      NS_ConvertUTF16toUTF8(metadata.mTitle).get(),
      NS_ConvertUTF16toUTF8(metadata.mArtwork[0].mSrc).get());
  return metadata;
}

nsString MediaStatusManager::GetDefaultTitle() const {
  RefPtr<MediaControlService> service = MediaControlService::GetService();
  nsString defaultTitle = service->GetFallbackTitle();

  RefPtr<CanonicalBrowsingContext> bc =
      CanonicalBrowsingContext::Get(mTopLevelBrowsingContextId);
  if (!bc) {
    return defaultTitle;
  }

  RefPtr<WindowGlobalParent> globalParent = bc->GetCurrentWindowGlobal();
  if (!globalParent) {
    return defaultTitle;
  }

  // The media metadata would be shown on the virtual controller interface. For
  // example, on Android, the interface would be shown on both notification bar
  // and lockscreen. Therefore, what information we provide via metadata is
  // quite important, because if we're in private browsing, we don't want to
  // expose details about what website the user is browsing on the lockscreen.
  // Therefore, using the default title when in the private browsing or the
  // document title is empty. Otherwise, use the document title.
  nsString documentTitle;
  if (!IsInPrivateBrowsing()) {
    globalParent->GetDocumentTitle(documentTitle);
  }
  return documentTitle.IsEmpty() ? defaultTitle : documentTitle;
}

nsCString MediaStatusManager::GetUrl() const {
  nsCString defaultUrl;

  RefPtr<CanonicalBrowsingContext> bc =
      CanonicalBrowsingContext::Get(mTopLevelBrowsingContextId);
  if (!bc) {
    return defaultUrl;
  }

  RefPtr<WindowGlobalParent> globalParent = bc->GetCurrentWindowGlobal();
  if (!globalParent) {
    return defaultUrl;
  }

  if (IsInPrivateBrowsing()) {
    return defaultUrl;
  }

  nsIURI* documentURI = globalParent->GetDocumentURI();
  if (!documentURI) {
    return defaultUrl;
  }

  return documentURI->GetSpecOrDefault();
}

nsString MediaStatusManager::GetDefaultFaviconURL() const {
#ifdef MOZ_PLACES
  nsCOMPtr<nsIURI> faviconURI;
  nsresult rv = NS_NewURI(getter_AddRefs(faviconURI),
                          nsLiteralCString(FAVICON_DEFAULT_URL));
  NS_ENSURE_SUCCESS(rv, u""_ns);

  // Convert URI from `chrome://XXX` to `file://XXX` because we would like to
  // let OS related frameworks, such as SMTC and MPRIS, handle this URL in order
  // to show the icon on virtual controller interface.
  nsCOMPtr<nsIChromeRegistry> regService = services::GetChromeRegistry();
  if (!regService) {
    return u""_ns;
  }
  nsCOMPtr<nsIURI> processedURI;
  regService->ConvertChromeURL(faviconURI, getter_AddRefs(processedURI));

  nsAutoCString spec;
  if (NS_FAILED(processedURI->GetSpec(spec))) {
    return u""_ns;
  }
  return NS_ConvertUTF8toUTF16(spec);
#else
  return u""_ns;
#endif
}

void MediaStatusManager::SetDeclaredPlaybackState(
    uint64_t aBrowsingContextId, MediaSessionPlaybackState aState) {
  auto info = mMediaSessionInfoMap.Lookup(aBrowsingContextId);
  if (!info) {
    return;
  }
  LOG("SetDeclaredPlaybackState from %s to %s",
      ToMediaSessionPlaybackStateStr(info->mDeclaredPlaybackState),
      ToMediaSessionPlaybackStateStr(aState));
  info->mDeclaredPlaybackState = aState;
  UpdateActualPlaybackState();
}

MediaSessionPlaybackState MediaStatusManager::GetCurrentDeclaredPlaybackState()
    const {
  if (!mActiveMediaSessionContextId) {
    return MediaSessionPlaybackState::None;
  }
  return mMediaSessionInfoMap.Get(*mActiveMediaSessionContextId)
      .mDeclaredPlaybackState;
}

void MediaStatusManager::NotifyMediaPlaybackChanged(uint64_t aBrowsingContextId,
                                                    MediaPlaybackState aState) {
  LOG("UpdateMediaPlaybackState %s for context %" PRIu64,
      EnumValueToString(aState), aBrowsingContextId);
  const bool oldPlaying = mPlaybackStatusDelegate.IsPlaying();
  mPlaybackStatusDelegate.UpdateMediaPlaybackState(aBrowsingContextId, aState);

  // Playback state doesn't change, we don't need to update the guessed playback
  // state. This is used to prevent the state from changing from `none` to
  // `paused` when receiving `MediaPlaybackState::eStarted`.
  if (mPlaybackStatusDelegate.IsPlaying() == oldPlaying) {
    return;
  }
  if (mPlaybackStatusDelegate.IsPlaying()) {
    SetGuessedPlayState(MediaSessionPlaybackState::Playing);
  } else {
    SetGuessedPlayState(MediaSessionPlaybackState::Paused);
  }
}

void MediaStatusManager::SetGuessedPlayState(MediaSessionPlaybackState aState) {
  if (aState == mGuessedPlaybackState) {
    return;
  }
  LOG("SetGuessedPlayState : '%s'", ToMediaSessionPlaybackStateStr(aState));
  mGuessedPlaybackState = aState;
  UpdateActualPlaybackState();
}

void MediaStatusManager::UpdateActualPlaybackState() {
  // The way to compute the actual playback state is based on the spec.
  // https://w3c.github.io/mediasession/#actual-playback-state
  MediaSessionPlaybackState newState =
      GetCurrentDeclaredPlaybackState() == MediaSessionPlaybackState::Playing
          ? MediaSessionPlaybackState::Playing
          : mGuessedPlaybackState;
  if (mActualPlaybackState == newState) {
    return;
  }
  mActualPlaybackState = newState;
  LOG("UpdateActualPlaybackState : '%s'",
      ToMediaSessionPlaybackStateStr(mActualPlaybackState));
  mPlaybackStateChangedEvent.Notify(mActualPlaybackState);
}

void MediaStatusManager::EnableAction(uint64_t aBrowsingContextId,
                                      MediaSessionAction aAction) {
  auto info = mMediaSessionInfoMap.Lookup(aBrowsingContextId);
  if (!info) {
    return;
  }
  if (info->IsActionSupported(aAction)) {
    LOG("Action '%s' has already been enabled for context %" PRIu64,
        GetEnumString(aAction).get(), aBrowsingContextId);
    return;
  }
  LOG("Enable action %s for context %" PRIu64, GetEnumString(aAction).get(),
      aBrowsingContextId);
  info->EnableAction(aAction);
  NotifySupportedKeysChangedIfNeeded(aBrowsingContextId);
}

void MediaStatusManager::DisableAction(uint64_t aBrowsingContextId,
                                       MediaSessionAction aAction) {
  auto info = mMediaSessionInfoMap.Lookup(aBrowsingContextId);
  if (!info) {
    return;
  }
  if (!info->IsActionSupported(aAction)) {
    LOG("Action '%s' hasn't been enabled yet for context %" PRIu64,
        GetEnumString(aAction).get(), aBrowsingContextId);
    return;
  }
  LOG("Disable action %s for context %" PRIu64, GetEnumString(aAction).get(),
      aBrowsingContextId);
  info->DisableAction(aAction);
  NotifySupportedKeysChangedIfNeeded(aBrowsingContextId);
}

void MediaStatusManager::UpdatePositionState(
    uint64_t aBrowsingContextId, const Maybe<PositionState>& aState) {
  auto info = mMediaSessionInfoMap.Lookup(aBrowsingContextId);
  if (info) {
    LOG("Update position state for context %" PRIu64, aBrowsingContextId);
    info->mPositionState = aState;
  }

  // The position state comes from non-active media session which we don't care.
  if (!mActiveMediaSessionContextId ||
      *mActiveMediaSessionContextId != aBrowsingContextId) {
    return;
  }
  mPositionStateChangedEvent.Notify(aState);
}

void MediaStatusManager::UpdateGuessedPositionState(
    uint64_t aBrowsingContextId, const nsID& aMediaId,
    const Maybe<PositionState>& aGuessedState) {
  mPlaybackStatusDelegate.UpdateGuessedPositionState(aBrowsingContextId,
                                                     aMediaId, aGuessedState);

  // The position state comes from a non-active media session and
  // there is another one active (with some metadata).
  if (mActiveMediaSessionContextId &&
      *mActiveMediaSessionContextId != aBrowsingContextId) {
    return;
  }

  // media session is declared for the updated session, but there's no active
  // session - it will get emitted once the session becomes active
  if (mMediaSessionInfoMap.Contains(aBrowsingContextId) &&
      !mActiveMediaSessionContextId) {
    return;
  }

  mPositionStateChangedEvent.Notify(GetCurrentPositionState());
}

void MediaStatusManager::NotifySupportedKeysChangedIfNeeded(
    uint64_t aBrowsingContextId) {
  // Only the active media session's supported actions would be shown in virtual
  // control interface, so we only notify the event when supported actions
  // change happens on the active media session.
  if (!mActiveMediaSessionContextId ||
      *mActiveMediaSessionContextId != aBrowsingContextId) {
    return;
  }
  mSupportedActionsChangedEvent.Notify(GetSupportedActions());
}

CopyableTArray<MediaSessionAction> MediaStatusManager::GetSupportedActions()
    const {
  CopyableTArray<MediaSessionAction> supportedActions;
  if (!mActiveMediaSessionContextId) {
    return supportedActions;
  }

  MediaSessionInfo info =
      mMediaSessionInfoMap.Get(*mActiveMediaSessionContextId);
  for (MediaSessionAction action :
       MakeWebIDLEnumeratedRange<MediaSessionAction>()) {
    if (info.IsActionSupported(action)) {
      supportedActions.AppendElement(action);
    }
  }
  return supportedActions;
}

MediaMetadataBase MediaStatusManager::GetCurrentMediaMetadata() const {
  // If we don't have active media session, active media session doesn't have
  // media metadata, or we're in private browsing mode, then we should create a
  // default metadata which is using website's title and favicon as title and
  // artwork.
  if (mActiveMediaSessionContextId && !IsInPrivateBrowsing()) {
    MediaSessionInfo info =
        mMediaSessionInfoMap.Get(*mActiveMediaSessionContextId);
    if (!info.mMetadata) {
      return CreateDefaultMetadata();
    }
    MediaMetadataBase& metadata = *(info.mMetadata);
    FillMissingTitleAndArtworkIfNeeded(metadata);
    metadata.mUrl = GetUrl();
    return metadata;
  }
  return CreateDefaultMetadata();
}

Maybe<PositionState> MediaStatusManager::GetCurrentPositionState() const {
  if (mActiveMediaSessionContextId) {
    auto info = mMediaSessionInfoMap.Lookup(*mActiveMediaSessionContextId);
    if (info && info->mPositionState) {
      return info->mPositionState;
    }
  }

  return mPlaybackStatusDelegate.GuessedMediaPositionState(
      mActiveMediaSessionContextId);
}

void MediaStatusManager::FillMissingTitleAndArtworkIfNeeded(
    MediaMetadataBase& aMetadata) const {
  // If the metadata doesn't set its title and artwork properly, we would like
  // to use default title and favicon instead in order to prevent showing
  // nothing on the virtual control interface.
  if (aMetadata.mTitle.IsEmpty()) {
    aMetadata.mTitle = GetDefaultTitle();
  }
  if (aMetadata.mArtwork.IsEmpty()) {
    aMetadata.mArtwork.AppendElement()->mSrc = GetDefaultFaviconURL();
  }
}

bool MediaStatusManager::IsInPrivateBrowsing() const {
  RefPtr<CanonicalBrowsingContext> bc =
      CanonicalBrowsingContext::Get(mTopLevelBrowsingContextId);
  if (!bc) {
    return false;
  }
  RefPtr<Element> element = bc->GetEmbedderElement();
  if (!element) {
    return false;
  }
  return element->OwnerDoc()->IsInPrivateBrowsing();
}

MediaSessionPlaybackState MediaStatusManager::PlaybackState() const {
  return mActualPlaybackState;
}

bool MediaStatusManager::IsMediaAudible() const {
  return mPlaybackStatusDelegate.IsAudible();
}

bool MediaStatusManager::IsMediaPlaying() const {
  return mActualPlaybackState == MediaSessionPlaybackState::Playing;
}

bool MediaStatusManager::IsAnyMediaBeingControlled() const {
  return mPlaybackStatusDelegate.IsAnyMediaBeingControlled();
}

void MediaStatusManager::NotifyPageTitleChanged() {
  // If active media session has set non-empty metadata, then we would use that
  // instead of using default metadata.
  if (mActiveMediaSessionContextId &&
      mMediaSessionInfoMap.Lookup(*mActiveMediaSessionContextId)->mMetadata) {
    return;
  }
  // In private browsing mode, we won't show page title on default metadata so
  // we don't need to update that.
  if (IsInPrivateBrowsing()) {
    return;
  }
  LOG("page title changed, update default metadata");
  mMetadataChangedEvent.Notify(GetCurrentMediaMetadata());
}

}  // namespace mozilla::dom

Messung V0.5 in Prozent
C=88 H=99 G=93

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet am  2026-05-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.