| /* |
| * Copyright (C) 2021 Igalia S.L |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| #include "MediaSessionManagerGLib.h" |
| |
| #if USE(GLIB) && ENABLE(MEDIA_SESSION) |
| |
| #include "AudioSession.h" |
| #include "HTMLMediaElement.h" |
| #include "MediaPlayer.h" |
| #include "MediaStrategy.h" |
| #include "NowPlayingInfo.h" |
| #include "PlatformMediaSession.h" |
| #include "PlatformStrategies.h" |
| |
| #include <gio/gio.h> |
| |
| // https://specifications.freedesktop.org/mpris-spec/latest/ |
| static const char s_mprisInterface[] = |
| "<node>" |
| "<interface name=\"org.mpris.MediaPlayer2\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "<method name=\"Raise\"/>" |
| "<method name=\"Quit\"/>" |
| "<property name=\"CanQuit\" type=\"b\" access=\"read\"/>" |
| "<property name=\"CanRaise\" type=\"b\" access=\"read\"/>" |
| "<property name=\"HasTrackList\" type=\"b\" access=\"read\"/>" |
| "<property name=\"Identity\" type=\"s\" access=\"read\"/>" |
| "<property name=\"DesktopEntry\" type=\"s\" access=\"read\"/>" |
| "<property name=\"SupportedUriSchemes\" type=\"as\" access=\"read\"/>" |
| "<property name=\"SupportedMimeTypes\" type=\"as\" access=\"read\"/>" |
| "</interface>" |
| "<interface name=\"org.mpris.MediaPlayer2.Player\">" |
| "<method name=\"Next\"/>" |
| "<method name=\"Previous\"/>" |
| "<method name=\"Pause\"/>" |
| "<method name=\"PlayPause\"/>" |
| "<method name=\"Stop\"/>" |
| "<method name=\"Play\"/>" |
| "<method name=\"Seek\">" |
| "<arg direction=\"in\" type=\"x\" name=\"Offset\"/>" |
| "</method>" |
| "<method name=\"SetPosition\">" |
| "<arg direction=\"in\" type=\"o\" name=\"TrackId\"/>" |
| "<arg direction=\"in\" type=\"x\" name=\"Position\"/>" |
| "</method>" |
| "<method name=\"OpenUri\">" |
| "<arg direction=\"in\" type=\"s\" name=\"Uri\"/>" |
| "</method>" |
| "<property name=\"PlaybackStatus\" type=\"s\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"Rate\" type=\"d\" access=\"readwrite\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"Metadata\" type=\"a{sv}\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"Volume\" type=\"d\" access=\"readwrite\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"Position\" type=\"x\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>" |
| "</property>" |
| "<property name=\"MinimumRate\" type=\"d\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"MaximumRate\" type=\"d\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"CanGoNext\" type=\"b\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"CanGoPrevious\" type=\"b\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"CanPlay\" type=\"b\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"CanPause\" type=\"b\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"CanSeek\" type=\"b\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>" |
| "</property>" |
| "<property name=\"CanControl\" type=\"b\" access=\"read\">" |
| "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>" |
| "</property>" |
| "<signal name=\"Seeked\">" |
| "<arg name=\"Position\" type=\"x\"/>" |
| "</signal>" |
| "</interface>" |
| "</node>"; |
| |
| namespace WebCore { |
| |
| std::unique_ptr<PlatformMediaSessionManager> PlatformMediaSessionManager::create() |
| { |
| GUniqueOutPtr<GError> error; |
| auto mprisInterface = adoptGRef(g_dbus_node_info_new_for_xml(s_mprisInterface, &error.outPtr())); |
| if (!mprisInterface) { |
| g_warning("Failed at parsing XML Interface definition: %s", error->message); |
| return nullptr; |
| } |
| return makeUnique<MediaSessionManagerGLib>(WTFMove(mprisInterface)); |
| } |
| |
| MediaSessionManagerGLib::MediaSessionManagerGLib(GRefPtr<GDBusNodeInfo>&& mprisInterface) |
| : m_mprisInterface(WTFMove(mprisInterface)) |
| , m_nowPlayingManager(platformStrategies()->mediaStrategy().createNowPlayingManager()) |
| { |
| } |
| |
| MediaSessionManagerGLib::~MediaSessionManagerGLib() = default; |
| |
| void MediaSessionManagerGLib::beginInterruption(PlatformMediaSession::InterruptionType type) |
| { |
| if (type == PlatformMediaSession::InterruptionType::SystemInterruption) { |
| forEachSession([] (auto& session) { |
| session.clearHasPlayedSinceLastInterruption(); |
| }); |
| } |
| |
| PlatformMediaSessionManager::beginInterruption(type); |
| } |
| |
| void MediaSessionManagerGLib::scheduleSessionStatusUpdate() |
| { |
| callOnMainThread([this] () mutable { |
| m_nowPlayingManager->setSupportsSeeking(computeSupportsSeeking()); |
| updateNowPlayingInfo(); |
| |
| forEachSession([] (auto& session) { |
| session.updateMediaUsageIfChanged(); |
| }); |
| }); |
| } |
| |
| bool MediaSessionManagerGLib::sessionWillBeginPlayback(PlatformMediaSession& session) |
| { |
| if (!PlatformMediaSessionManager::sessionWillBeginPlayback(session)) |
| return false; |
| |
| scheduleSessionStatusUpdate(); |
| return true; |
| } |
| |
| void MediaSessionManagerGLib::sessionDidEndRemoteScrubbing(PlatformMediaSession&) |
| { |
| scheduleSessionStatusUpdate(); |
| } |
| |
| void MediaSessionManagerGLib::addSession(PlatformMediaSession& platformSession) |
| { |
| auto identifier = platformSession.mediaSessionIdentifier(); |
| auto session = MediaSessionGLib::create(*this, identifier); |
| if (!session) |
| return; |
| |
| m_sessions.add(identifier, WTFMove(session)); |
| m_nowPlayingManager->addClient(*this); |
| |
| PlatformMediaSessionManager::addSession(platformSession); |
| } |
| |
| void MediaSessionManagerGLib::removeSession(PlatformMediaSession& session) |
| { |
| PlatformMediaSessionManager::removeSession(session); |
| |
| m_sessions.remove(session.mediaSessionIdentifier()); |
| if (hasNoSession()) |
| m_nowPlayingManager->removeClient(*this); |
| |
| scheduleSessionStatusUpdate(); |
| } |
| |
| void MediaSessionManagerGLib::setCurrentSession(PlatformMediaSession& session) |
| { |
| PlatformMediaSessionManager::setCurrentSession(session); |
| |
| m_nowPlayingManager->updateSupportedCommands(); |
| } |
| |
| void MediaSessionManagerGLib::sessionWillEndPlayback(PlatformMediaSession& session, DelayCallingUpdateNowPlaying delayCallingUpdateNowPlaying) |
| { |
| PlatformMediaSessionManager::sessionWillEndPlayback(session, delayCallingUpdateNowPlaying); |
| |
| callOnMainThread([weakSession = WeakPtr { session }] { |
| if (weakSession) |
| weakSession->updateMediaUsageIfChanged(); |
| }); |
| |
| if (delayCallingUpdateNowPlaying == DelayCallingUpdateNowPlaying::No) |
| updateNowPlayingInfo(); |
| else { |
| callOnMainThread([this] { |
| updateNowPlayingInfo(); |
| }); |
| } |
| } |
| |
| void MediaSessionManagerGLib::sessionStateChanged(PlatformMediaSession& platformSession) |
| { |
| PlatformMediaSessionManager::sessionStateChanged(platformSession); |
| |
| auto session = m_sessions.get(platformSession.mediaSessionIdentifier()); |
| if (!session) |
| return; |
| |
| session->playbackStatusChanged(platformSession); |
| } |
| |
| void MediaSessionManagerGLib::clientCharacteristicsChanged(PlatformMediaSession& platformSession) |
| { |
| ALWAYS_LOG(LOGIDENTIFIER, platformSession.logIdentifier()); |
| if (m_isSeeking) { |
| m_isSeeking = false; |
| auto session = m_sessions.get(platformSession.mediaSessionIdentifier()); |
| session->emitPositionChanged(platformSession.nowPlayingInfo()->currentTime); |
| } |
| scheduleSessionStatusUpdate(); |
| } |
| |
| void MediaSessionManagerGLib::sessionCanProduceAudioChanged() |
| { |
| ALWAYS_LOG(LOGIDENTIFIER); |
| PlatformMediaSessionManager::sessionCanProduceAudioChanged(); |
| scheduleSessionStatusUpdate(); |
| } |
| |
| void MediaSessionManagerGLib::addSupportedCommand(PlatformMediaSession::RemoteControlCommandType command) |
| { |
| m_nowPlayingManager->addSupportedCommand(command); |
| } |
| |
| void MediaSessionManagerGLib::removeSupportedCommand(PlatformMediaSession::RemoteControlCommandType command) |
| { |
| m_nowPlayingManager->removeSupportedCommand(command); |
| } |
| |
| RemoteCommandListener::RemoteCommandsSet MediaSessionManagerGLib::supportedCommands() const |
| { |
| return m_nowPlayingManager->supportedCommands(); |
| } |
| |
| PlatformMediaSession* MediaSessionManagerGLib::nowPlayingEligibleSession() |
| { |
| // FIXME: Fix this layering violation. |
| if (auto element = HTMLMediaElement::bestMediaElementForRemoteControls(MediaElementSession::PlaybackControlsPurpose::NowPlaying)) |
| return &element->mediaSession(); |
| |
| return nullptr; |
| } |
| |
| void MediaSessionManagerGLib::updateNowPlayingInfo() |
| { |
| auto* platformSession = nowPlayingEligibleSession(); |
| if (!platformSession) { |
| if (m_registeredAsNowPlayingApplication) { |
| ALWAYS_LOG(LOGIDENTIFIER, "clearing now playing info"); |
| m_nowPlayingManager->clearNowPlayingInfo(); |
| } |
| |
| m_registeredAsNowPlayingApplication = false; |
| m_nowPlayingActive = false; |
| m_lastUpdatedNowPlayingTitle = emptyString(); |
| m_lastUpdatedNowPlayingDuration = NAN; |
| m_lastUpdatedNowPlayingElapsedTime = NAN; |
| m_lastUpdatedNowPlayingInfoUniqueIdentifier = { }; |
| return; |
| } |
| |
| auto nowPlayingInfo = platformSession->nowPlayingInfo(); |
| if (!nowPlayingInfo) |
| return; |
| |
| m_haveEverRegisteredAsNowPlayingApplication = true; |
| |
| if (m_nowPlayingManager->setNowPlayingInfo(*nowPlayingInfo)) |
| ALWAYS_LOG(LOGIDENTIFIER, "title = \"", nowPlayingInfo->title, "\", isPlaying = ", nowPlayingInfo->isPlaying, ", duration = ", nowPlayingInfo->duration, ", now = ", nowPlayingInfo->currentTime, ", id = ", nowPlayingInfo->uniqueIdentifier.toUInt64(), ", registered = ", m_registeredAsNowPlayingApplication, ", src = \"", nowPlayingInfo->artwork ? nowPlayingInfo->artwork->src : String(), "\""); |
| |
| if (!m_registeredAsNowPlayingApplication) { |
| m_registeredAsNowPlayingApplication = true; |
| providePresentingApplicationPIDIfNecessary(); |
| } |
| |
| if (!nowPlayingInfo->title.isEmpty()) |
| m_lastUpdatedNowPlayingTitle = nowPlayingInfo->title; |
| |
| double duration = nowPlayingInfo->duration; |
| if (std::isfinite(duration) && duration != MediaPlayer::invalidTime()) |
| m_lastUpdatedNowPlayingDuration = duration; |
| |
| m_lastUpdatedNowPlayingInfoUniqueIdentifier = nowPlayingInfo->uniqueIdentifier; |
| |
| double currentTime = nowPlayingInfo->currentTime; |
| if (std::isfinite(currentTime) && currentTime != MediaPlayer::invalidTime() && nowPlayingInfo->supportsSeeking) |
| m_lastUpdatedNowPlayingElapsedTime = currentTime; |
| |
| m_nowPlayingActive = nowPlayingInfo->allowsNowPlayingControlsVisibility; |
| |
| auto session = m_sessions.get(platformSession->mediaSessionIdentifier()); |
| session->updateNowPlaying(*nowPlayingInfo); |
| } |
| |
| void MediaSessionManagerGLib::dispatch(PlatformMediaSession::RemoteControlCommandType platformCommand, PlatformMediaSession::RemoteCommandArgument argument) |
| { |
| m_isSeeking = platformCommand == PlatformMediaSession::SeekToPlaybackPositionCommand; |
| m_nowPlayingManager->didReceiveRemoteControlCommand(platformCommand, argument); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // USE(GLIB) && ENABLE(MEDIA_SESSION) |