Add support for setting cover with MPRIS
Cherry-picked from https://github.com/telegramdesktop/tdesktop/pull/16074.
This commit is contained in:
parent
273200adbf
commit
196909f416
1 changed files with 106 additions and 54 deletions
|
|
@ -11,13 +11,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_document_media.h"
|
||||||
#include "core/sandbox.h"
|
#include "core/sandbox.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/core_settings.h"
|
#include "core/core_settings.h"
|
||||||
|
#include "main/main_domain.h"
|
||||||
|
#include "main/main_account.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
|
||||||
|
#include <QtCore/QBuffer>
|
||||||
#include <QtGui/QGuiApplication>
|
#include <QtGui/QGuiApplication>
|
||||||
|
|
||||||
#include <glibmm.h>
|
#include <glibmm.h>
|
||||||
|
|
@ -36,61 +42,63 @@ constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
|
||||||
constexpr auto kSongType = AudioMsgId::Type::Song;
|
constexpr auto kSongType = AudioMsgId::Type::Song;
|
||||||
|
|
||||||
constexpr auto kIntrospectionXML = R"INTROSPECTION(<node>
|
constexpr auto kIntrospectionXML = R"INTROSPECTION(<node>
|
||||||
<interface name='org.mpris.MediaPlayer2'>
|
<interface name='org.mpris.MediaPlayer2'>
|
||||||
<method name='Raise'/>
|
<method name='Raise'/>
|
||||||
<method name='Quit'/>
|
<method name='Quit'/>
|
||||||
<property name='CanQuit' type='b' access='read'/>
|
<property name='CanQuit' type='b' access='read'/>
|
||||||
<property name='CanRaise' type='b' access='read'/>
|
<property name='CanRaise' type='b' access='read'/>
|
||||||
<property name='HasTrackList' type='b' access='read'/>
|
<property name='HasTrackList' type='b' access='read'/>
|
||||||
<property name='Identity' type='s' access='read'/>
|
<property name='Identity' type='s' access='read'/>
|
||||||
<property name='DesktopEntry' type='s' access='read'/>
|
<property name='DesktopEntry' type='s' access='read'/>
|
||||||
<property name='SupportedUriSchemes' type='as' access='read'/>
|
<property name='SupportedUriSchemes' type='as' access='read'/>
|
||||||
<property name='SupportedMimeTypes' type='as' access='read'/>
|
<property name='SupportedMimeTypes' type='as' access='read'/>
|
||||||
<property name='Fullscreen' type='b' access='readwrite'/>
|
<property name='Fullscreen' type='b' access='readwrite'/>
|
||||||
<property name='CanSetFullscreen' type='b' access='read'/>
|
<property name='CanSetFullscreen' type='b' access='read'/>
|
||||||
</interface>
|
</interface>
|
||||||
</node>)INTROSPECTION"_cs;
|
</node>)INTROSPECTION"_cs;
|
||||||
|
|
||||||
constexpr auto kPlayerIntrospectionXML = R"INTROSPECTION(<node>
|
constexpr auto kPlayerIntrospectionXML = R"INTROSPECTION(<node>
|
||||||
<interface name='org.mpris.MediaPlayer2.Player'>
|
<interface name='org.mpris.MediaPlayer2.Player'>
|
||||||
<method name='Next'/>
|
<method name='Next'/>
|
||||||
<method name='Previous'/>
|
<method name='Previous'/>
|
||||||
<method name='Pause'/>
|
<method name='Pause'/>
|
||||||
<method name='PlayPause'/>
|
<method name='PlayPause'/>
|
||||||
<method name='Stop'/>
|
<method name='Stop'/>
|
||||||
<method name='Play'/>
|
<method name='Play'/>
|
||||||
<method name='Seek'>
|
<method name='Seek'>
|
||||||
<arg direction='in' name='Offset' type='x'/>
|
<arg direction='in' name='Offset' type='x'/>
|
||||||
</method>
|
</method>
|
||||||
<method name='SetPosition'>
|
<method name='SetPosition'>
|
||||||
<arg direction='in' name='TrackId' type='o'/>
|
<arg direction='in' name='TrackId' type='o'/>
|
||||||
<arg direction='in' name='Position' type='x'/>
|
<arg direction='in' name='Position' type='x'/>
|
||||||
</method>
|
</method>
|
||||||
<method name='OpenUri'>
|
<method name='OpenUri'>
|
||||||
<arg direction='in' name='Uri' type='s'/>
|
<arg direction='in' name='Uri' type='s'/>
|
||||||
</method>
|
</method>
|
||||||
<signal name='Seeked'>
|
<signal name='Seeked'>
|
||||||
<arg name='Position' type='x'/>
|
<arg name='Position' type='x'/>
|
||||||
</signal>
|
</signal>
|
||||||
<property name='PlaybackStatus' type='s' access='read'/>
|
<property name='PlaybackStatus' type='s' access='read'/>
|
||||||
<property name='Rate' type='d' access='readwrite'/>
|
<property name='Rate' type='d' access='readwrite'/>
|
||||||
<property name='Metadata' type='a{sv}' access='read'>
|
<property name='Metadata' type='a{sv}' access='read'>
|
||||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
|
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
|
||||||
</property>
|
</property>
|
||||||
<property name='Volume' type='d' access='readwrite'/>
|
<property name='Volume' type='d' access='readwrite'/>
|
||||||
<property name='Position' type='x' access='read'/>
|
<property name='Position' type='x' access='read'/>
|
||||||
<property name='MinimumRate' type='d' access='read'/>
|
<property name='MinimumRate' type='d' access='read'/>
|
||||||
<property name='MaximumRate' type='d' access='read'/>
|
<property name='MaximumRate' type='d' access='read'/>
|
||||||
<property name='CanGoNext' type='b' access='read'/>
|
<property name='CanGoNext' type='b' access='read'/>
|
||||||
<property name='CanGoPrevious' type='b' access='read'/>
|
<property name='CanGoPrevious' type='b' access='read'/>
|
||||||
<property name='CanPlay' type='b' access='read'/>
|
<property name='CanPlay' type='b' access='read'/>
|
||||||
<property name='CanPause' type='b' access='read'/>
|
<property name='CanPause' type='b' access='read'/>
|
||||||
<property name='CanSeek' type='b' access='read'/>
|
<property name='CanSeek' type='b' access='read'/>
|
||||||
<property name='CanControl' type='b' access='read'/>
|
<property name='CanControl' type='b' access='read'/>
|
||||||
</interface>
|
</interface>
|
||||||
</node>)INTROSPECTION"_cs;
|
</node>)INTROSPECTION"_cs;
|
||||||
|
|
||||||
auto CreateMetadata(const Media::Player::TrackState &state) {
|
auto CreateMetadata(
|
||||||
|
const Media::Player::TrackState &state,
|
||||||
|
Data::DocumentMedia *trackView) {
|
||||||
std::map<Glib::ustring, Glib::VariantBase> result;
|
std::map<Glib::ustring, Glib::VariantBase> result;
|
||||||
|
|
||||||
if (!Media::Player::IsStoppedOrStopping(state.state)) {
|
if (!Media::Player::IsStoppedOrStopping(state.state)) {
|
||||||
|
|
@ -119,6 +127,25 @@ auto CreateMetadata(const Media::Player::TrackState &state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trackView) {
|
||||||
|
trackView->thumbnailWanted(Data::FileOrigin());
|
||||||
|
if (trackView->thumbnail()) {
|
||||||
|
QByteArray thumbnailData;
|
||||||
|
QBuffer thumbnailBuffer(&thumbnailData);
|
||||||
|
trackView->thumbnail()->original().save(
|
||||||
|
&thumbnailBuffer,
|
||||||
|
"JPG",
|
||||||
|
87);
|
||||||
|
|
||||||
|
result["mpris:artUrl"] = Glib::Variant<
|
||||||
|
Glib::ustring
|
||||||
|
>::create("data:image/jpeg;base64,"
|
||||||
|
+ thumbnailData
|
||||||
|
.toBase64()
|
||||||
|
.toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -237,8 +264,16 @@ void HandleGetProperty(
|
||||||
const auto state = Media::Player::instance()->getState(
|
const auto state = Media::Player::instance()->getState(
|
||||||
kSongType);
|
kSongType);
|
||||||
|
|
||||||
|
const auto trackView = [&]() -> std::shared_ptr<Data::DocumentMedia> {
|
||||||
|
const auto audioData = state.id.audio();
|
||||||
|
if (audioData && audioData->isSongWithCover()) {
|
||||||
|
return audioData->activeMediaView();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}();
|
||||||
|
|
||||||
property = base::Platform::MakeGlibVariant(
|
property = base::Platform::MakeGlibVariant(
|
||||||
CreateMetadata(state));
|
CreateMetadata(state, trackView.get()));
|
||||||
} else if (property_name == "MinimumRate") {
|
} else if (property_name == "MinimumRate") {
|
||||||
property = Glib::Variant<float64>::create(1.0);
|
property = Glib::Variant<float64>::create(1.0);
|
||||||
} else if (property_name == "PlaybackStatus") {
|
} else if (property_name == "PlaybackStatus") {
|
||||||
|
|
@ -349,6 +384,7 @@ public:
|
||||||
std::map<Glib::ustring, Glib::VariantBase> metadata;
|
std::map<Glib::ustring, Glib::VariantBase> metadata;
|
||||||
Glib::ustring playbackStatus;
|
Glib::ustring playbackStatus;
|
||||||
gint64 position = 0;
|
gint64 position = 0;
|
||||||
|
std::shared_ptr<Data::DocumentMedia> trackView;
|
||||||
|
|
||||||
rpl::lifetime lifetime;
|
rpl::lifetime lifetime;
|
||||||
};
|
};
|
||||||
|
|
@ -359,7 +395,17 @@ void MPRISSupport::Private::updateTrackState(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto currentMetadata = CreateMetadata(state);
|
const auto audioData = state.id.audio();
|
||||||
|
if (audioData && audioData->isSongWithCover()) {
|
||||||
|
const auto view = audioData->createMediaView();
|
||||||
|
if (!trackView || trackView->owner() != view->owner()) {
|
||||||
|
trackView = view;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trackView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto currentMetadata = CreateMetadata(state, trackView.get());
|
||||||
const auto currentPosition = state.position * 1000;
|
const auto currentPosition = state.position * 1000;
|
||||||
const auto currentPlaybackStatus = PlaybackStatus(state.state);
|
const auto currentPlaybackStatus = PlaybackStatus(state.state);
|
||||||
|
|
||||||
|
|
@ -423,6 +469,12 @@ MPRISSupport::MPRISSupport()
|
||||||
_private->updateTrackState(
|
_private->updateTrackState(
|
||||||
Media::Player::instance()->getState(kSongType));
|
Media::Player::instance()->getState(kSongType));
|
||||||
|
|
||||||
|
Core::App().domain().active().session().downloaderTaskFinished(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_private->updateTrackState(
|
||||||
|
Media::Player::instance()->getState(kSongType));
|
||||||
|
}, _private->lifetime);
|
||||||
|
|
||||||
Media::Player::instance()->updatedNotifier(
|
Media::Player::instance()->updatedNotifier(
|
||||||
) | rpl::start_with_next([=](
|
) | rpl::start_with_next([=](
|
||||||
const Media::Player::TrackState &state) {
|
const Media::Player::TrackState &state) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue