Music Hub ..
A session-wide music playback service
Loading...
Searching...
No Matches
player_skeleton.cpp
Go to the documentation of this file.
1/*
2 * Copyright © 2013-2015 Canonical Ltd.
3 * Copyright © 2022 UBports Foundation.
4 *
5 * Contact: Alberto Mardegan <mardy@users.sourceforge.net>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License version 3,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Authored by: Thomas Voß <thomas.voss@canonical.com>
20 * Jim Hodapp <jim.hodapp@canonical.com>
21 */
22
23#include "player_skeleton.h"
24
26#include "engine.h"
27#include "logging.h"
28#include "mpris.h"
30#include "xesam.h"
31
32#include "apparmor/lomiri.h"
33
34#include "util/uri_check.h"
35
36#include <QDBusArgument>
37#include <QDBusMessage>
38#include <QDateTime>
39#include <QMetaEnum>
40#include <QTimer>
41
42using namespace lomiri::MediaHubService;
43
44namespace lomiri {
45namespace MediaHubService {
46
51
53{
54public:
57
58 void openUri(const QDBusMessage &in, const QDBusConnection &bus,
59 OpenUriCall callType);
60
61private:
62 friend class PlayerSkeleton;
63 PlayerImplementation *m_player;
64 QScopedPointer<DBusPropertyNotifier> m_propertyNotifier;
65 QDBusConnection m_connection;
66 apparmor::lomiri::RequestContextResolver::Ptr request_context_resolver;
68 QTimer m_bufferingTimer;
69 QDateTime m_bufferingLastEmission;
70 int m_bufferingValue;
71 PlayerSkeleton *q_ptr;
72};
73
74}} // namespace
75
79 m_player(conf.player),
80 m_connection(conf.connection),
81 request_context_resolver{conf.request_context_resolver},
82 request_authenticator{conf.request_authenticator},
83 q_ptr(q)
84{
85 auto impl = m_player;
86 QObject::connect(impl, &PlayerImplementation::seekedTo,
88 QObject::connect(impl, &PlayerImplementation::aboutToFinish,
90 QObject::connect(impl, &PlayerImplementation::endOfStream,
91 q, [q, impl]() {
92 /* For compatibility with the old media-hub (and the music
93 * app), do not emit the signal unless this is the very
94 * last track of the playlist.
95 */
96 if (!impl->canGoNext()) {
97 Q_EMIT q->EndOfStream();
98 }
99 });
100 QObject::connect(impl, &PlayerImplementation::playbackStatusChanged,
101 q, [q,impl]() {
102 Q_EMIT q->PlaybackStatusChanged(impl->playbackStatus());
103 });
104 QObject::connect(impl, &PlayerImplementation::videoDimensionChanged,
105 q, [q,impl]() {
106 const QSize size = impl->videoDimension();
107 Q_EMIT q->VideoDimensionChanged(size.height(), size.width());
108 });
109 QObject::connect(impl, &PlayerImplementation::errorOccurred,
110 q, [q](Player::Error error) {
111 Q_EMIT q->Error(error);
112 });
113 QObject::connect(impl, &PlayerImplementation::bufferingChanged,
114 q, [this, q](int buffering) {
115 QDateTime now = QDateTime::currentDateTime();
116 if (!m_bufferingLastEmission.isValid() ||
117 m_bufferingLastEmission.msecsTo(now) > 500) {
118 /* More than 0.5 seconds have passed, emit immediately */
119 Q_EMIT q->Buffering(buffering);
120 m_bufferingLastEmission = now;
121 } else {
122 /* wait 0.5 seconds since the previous emission */
123 QDateTime nextEmission = m_bufferingLastEmission.addMSecs(500);
124 m_bufferingValue = buffering;
125 m_bufferingTimer.start(now.msecsTo(nextEmission));
126 }
127 });
128 m_bufferingTimer.setSingleShot(true);
129 m_bufferingTimer.callOnTimeout(q, [this, q]() {
130 Q_EMIT q->Buffering(m_bufferingValue);
131 m_bufferingLastEmission = QDateTime::currentDateTime();
132 });
133
134 QObject::connect(impl, &PlayerImplementation::volumeChanged,
136
137 /* Property signals */
138 QObject::connect(impl, &PlayerImplementation::mprisPropertiesChanged,
140 QObject::connect(impl, &PlayerImplementation::mprisPropertiesChanged,
142 QObject::connect(impl, &PlayerImplementation::mprisPropertiesChanged,
144 QObject::connect(impl, &PlayerImplementation::mprisPropertiesChanged,
146 QObject::connect(impl, &PlayerImplementation::isVideoSourceChanged,
148 QObject::connect(impl, &PlayerImplementation::isAudioSourceChanged,
152 QObject::connect(impl, &PlayerImplementation::orientationChanged,
154}
155
156void PlayerSkeletonPrivate::openUri(const QDBusMessage &in,
157 const QDBusConnection &bus,
158 OpenUriCall callType)
159{
160 in.setDelayedReply(true);
161 request_context_resolver->resolve_context_for_dbus_name_async(in.service(),
162 [=](const apparmor::lomiri::Context& context)
163 {
164 using Headers = Player::HeadersType;
165
166 const auto args = in.arguments();
167 QUrl uri = QUrl::fromUserInput(args.value(0).toString());
168 Headers headers;
169 if (callType == OpenUriCall::UriWithHeaders) {
170 const QDBusArgument dbusHeaders = args.value(1).value<QDBusArgument>();
171 dbusHeaders >> headers;
172 }
173
174 QDBusMessage reply;
175 UriCheck uri_check(uri);
176 const bool valid_uri = !uri.isEmpty() and
177 (!uri_check.is_local_file() or uri_check.file_exists());
178 if (!valid_uri)
179 {
180 const QString err_str = {"Warning: Failed to open uri " + uri.toString() +
181 " because it can't be found."};
182 MH_ERROR("%s", qUtf8Printable(err_str));
183 reply = in.createErrorReply(
185 err_str);
186 }
187 else
188 {
189 // Make sure the client has adequate apparmor permissions to open the URI
190 const auto result = request_authenticator->authenticate_open_uri_request(context, uri);
191 if (std::get<0>(result))
192 {
193 reply = in.createReply();
194 reply << (std::get<0>(result) ? m_player->open_uri(uri, headers) : false);
195 }
196 else
197 {
198 const QString err_str = {"Warning: Failed to authenticate necessary "
199 "apparmor permissions to open uri: " + std::get<1>(result)};
200 MH_ERROR("%s", qUtf8Printable(err_str));
201 reply = in.createErrorReply(
203 err_str);
204 }
205 }
206
207 /* Before sending the reply, emit any pending property notifications;
208 * in this way, by the time the client gets the reply, it will also
209 * have all the properties up to date.
210 */
211 m_propertyNotifier->notify();
212 bus.send(reply);
213 });
214}
215
217 QObject *parent):
218 QObject(parent),
219 d_ptr(new PlayerSkeletonPrivate(configuration, this))
220{
221}
222
224
226 Q_D(PlayerSkeleton);
227 return d->m_player;
228}
229
231 Q_D(const PlayerSkeleton);
232 return d->m_player;
233}
234
235bool PlayerSkeleton::registerAt(const QString &objectPath)
236{
237 Q_D(PlayerSkeleton);
238 d->m_propertyNotifier.reset(new DBusPropertyNotifier(d->m_connection,
239 objectPath, this));
240 return d->m_connection.registerObject(
241 objectPath,
242 this,
243 QDBusConnection::ExportAllSlots |
244 QDBusConnection::ExportScriptableSignals |
245 QDBusConnection::ExportAllProperties);
246}
247
249{
250 return player()->canPlay();
251}
252
254{
255 return player()->canPause();
256}
257
259{
260 return player()->canSeek();
261}
262
264{
265 return player()->canGoPrevious();
266}
267
269{
270 return player()->canGoNext();
271}
272
274{
275 return player()->isVideoSource();
276}
277
279{
280 return player()->isAudioSource();
281}
282
284{
286 switch (s) {
288 return QStringLiteral("Playing");
291 return QStringLiteral("Paused");
292 default:
293 return QStringLiteral("Stopped");
294 }
295}
296
297void PlayerSkeleton::setLoopStatus(const QString &status)
298{
299 bool ok;
300 int value = QMetaEnum::fromType<LoopStatus>().
301 keyToValue(status.toUtf8().constData(), &ok);
302 if (!ok) {
303 MH_ERROR("Invalid loop status: %s", qUtf8Printable(status));
304 return;
305 }
306 player()->setLoopStatus(static_cast<Player::LoopStatus>(value));
307}
308
310{
311 int value = static_cast<LoopStatus>(player()->loopStatus());
312 return QMetaEnum::fromType<LoopStatus>().valueToKey(value);
313}
314
316{
317 player()->setLoopStatus(static_cast<Player::LoopStatus>(status));
318}
319
321{
322 return static_cast<LoopStatus>(player()->loopStatus());
323}
324
326{
327 return player()->setPlaybackRate(rate);
328}
329
331{
332 return player()->playbackRate();
333}
334
336{
337 return player()->setShuffle(shuffle);
338}
339
341{
342 return player()->shuffle();
343}
344
345QVariantMap PlayerSkeleton::metadata() const
346{
348}
349
354
356{
357 return player()->volume();
358}
359
361{
362 return player()->minimumRate();
363}
364
366{
367 return player()->maximumRate();
368}
369
371{
372 return player()->position();
373}
374
376{
377 return player()->duration();
378}
379
381{
382 return player()->backend();
383}
384
386{
387 return player()->orientation();
388}
389
391{
392 return player()->lifetime();
393}
394
396{
398}
399
401{
402 return player()->audioStreamRole();
403}
404
406{
407 player()->next();
408}
409
411{
412 player()->previous();
413}
414
416{
417 player()->pause();
418}
419
438
440{
441 player()->stop();
442}
443
445{
446 player()->play();
447 /* FIXME: workaround for the client library: if we don't emit the signal
448 * right away, it gets confused and will pause the playback.
449 */
451}
452
453void PlayerSkeleton::Seek(quint64 microSeconds)
454{
455 player()->seek_to(std::chrono::microseconds(microSeconds));
456}
457
458void PlayerSkeleton::SetPosition(const QDBusObjectPath &, quint64)
459{
460 // TODO: implement (this was never implemented in media-hub)
461}
462
463void PlayerSkeleton::CreateVideoSink(quint32 textureId)
464{
465 try
466 {
468 }
470 {
471 sendErrorReply(
473 e.what());
474 }
475 catch (...)
476 {
477 sendErrorReply(
479 QString());
480 }
481}
482
483uint32_t PlayerSkeleton::Key() const
484{
485 return player()->key();
486}
487
488void PlayerSkeleton::OpenUri(const QDBusMessage &)
489{
490 Q_D(PlayerSkeleton);
491 d->openUri(message(), connection(), OpenUriCall::OnlyUri);
492}
493
494void PlayerSkeleton::OpenUriExtended(const QDBusMessage &)
495{
496 Q_D(PlayerSkeleton);
497 d->openUri(message(), connection(), OpenUriCall::UriWithHeaders);
498}
void create_gl_texture_video_sink(std::uint32_t texture_id)
void setAudioStreamRole(Player::AudioStreamRole role)
void seek_to(const std::chrono::microseconds &offset)
void openUri(const QDBusMessage &in, const QDBusConnection &bus, OpenUriCall callType)
PlayerSkeletonPrivate(const PlayerSkeleton::Configuration &conf, PlayerSkeleton *q)
bool registerAt(const QString &objectPath)
Q_SCRIPTABLE void Seeked(quint64 microSeconds)
void OpenUriExtended(const QDBusMessage &)
Q_SCRIPTABLE void Buffering(int percent)
Q_SCRIPTABLE void VideoDimensionChanged(quint32 height, quint32 width)
Q_SCRIPTABLE void Error(qint16 code)
PlayerSkeleton(const Configuration &configuration, QObject *parent=nullptr)
Q_SCRIPTABLE void PlaybackStatusChanged(qint16 status)
void setLoopStatus(const QString &status)
void SetPosition(const QDBusObjectPath &trackObject, quint64 microSeconds)
QSharedPointer< RequestContextResolver > Ptr
Definition lomiri.h:85
#define MH_ERROR(...)
Definition logging.h:41
static constexpr const char * name
Definition mpris.h:207