summaryrefslogtreecommitdiffstats
path: root/mediaplayer/MediaplayerMpdBackend.cpp
diff options
context:
space:
mode:
authorScott Murray <scott.murray@konsulko.com>2022-02-28 13:04:45 -0500
committerScott Murray <scott.murray@konsulko.com>2022-02-28 13:04:45 -0500
commit1332cc7d0a618ee88b4d19813340665332d406ca (patch)
treefe500276505160f5fd8ba8acb46334633ed0788a /mediaplayer/MediaplayerMpdBackend.cpp
parent0de8ac83e6a190d5fc124587d1f9f0a7f0198ce3 (diff)
Add Bluetooth media control supportmarlin_12.93.0marlin/12.93.012.93.0
Rework to expose Bluetooth AVRCP media control in the Bluetooth class, and use that support to implement a Bluetooth backend in the Mediaplayer class. This replaces the scheme that existed with the agl-service-mediaplayer binding where it abstracted away the Bluetooth support itself. However, care has been take to make sure that the exposed API to users of libqtappfw-mediaplayer has not changed. Bug-AGL: SPEC-4231 Signed-off-by: Scott Murray <scott.murray@konsulko.com> Change-Id: I76b4f75621ce0121364eea3259b074bf3067ee88
Diffstat (limited to 'mediaplayer/MediaplayerMpdBackend.cpp')
-rw-r--r--mediaplayer/MediaplayerMpdBackend.cpp139
1 files changed, 124 insertions, 15 deletions
diff --git a/mediaplayer/MediaplayerMpdBackend.cpp b/mediaplayer/MediaplayerMpdBackend.cpp
index c42d18a..5cc9dce 100644
--- a/mediaplayer/MediaplayerMpdBackend.cpp
+++ b/mediaplayer/MediaplayerMpdBackend.cpp
@@ -29,8 +29,7 @@
#define MPD_CONNECTION_TIMEOUT 60000
MediaplayerMpdBackend::MediaplayerMpdBackend(Mediaplayer *player, QQmlContext *context, QObject *parent) :
- QObject(parent),
- m_player(player)
+ MediaplayerBackend(player, parent)
{
struct mpd_connection *conn = mpd_connection_new(NULL, 0, MPD_CONNECTION_TIMEOUT);
if (!conn) {
@@ -49,36 +48,53 @@ MediaplayerMpdBackend::MediaplayerMpdBackend(Mediaplayer *player, QQmlContext *c
m_song_pos_timer = new QTimer(this);
connect(m_song_pos_timer, &QTimer::timeout, this, &MediaplayerMpdBackend::songPositionTimeout);
+ // Connect signal to push the timer-driven position updates to the player
+ connect(this,
+ &MediaplayerMpdBackend::positionMetadataUpdate,
+ player,
+ &Mediaplayer::updateLocalMetadata);
+
+ // Create thread to handle polling for MPD events
MpdEventHandler *handler = new MpdEventHandler();
handler->moveToThread(&m_handlerThread);
connect(&m_handlerThread, &QThread::finished, handler, &QObject::deleteLater);
- connect(this, &MediaplayerMpdBackend::start, handler, &MpdEventHandler::handleEvents);
+ connect(this,
+ &MediaplayerMpdBackend::startHandler,
+ handler,
+ &MpdEventHandler::handleEvents);
// Connect playback state updates from the backend handler thread
// so our view of the state is kept in sync with MPD.
connect(handler,
&MpdEventHandler::playbackStateUpdate,
this,
- &MediaplayerMpdBackend::updatePlaybackState);
-
- // Connect updates from backend handler thread to parent.
- // Note that we should not have to explicitly specify the
- // Qt::QueuedConnection option here since the handler is constructed
- // by adding a worker object to a QThread and it should be automatic
- // in that case.
+ &MediaplayerMpdBackend::updatePlaybackState,
+ Qt::QueuedConnection);
+
+ // Connect updates from backend handler thread.
+ // For now, playlist updates go directly to the parent to keep its
+ // model in sync. This would have to change if there's ever another
+ // backend with playlist support, or improving abstraction is desired.
+ // Current song metadata is directed to a slot here for caching before
+ // sending to the parent, as having the backends be where that is
+ // done seems inherently more logical.
connect(handler,
&MpdEventHandler::playlistUpdate,
player,
- &Mediaplayer::updatePlaylist);
+ &Mediaplayer::updateLocalPlaylist);
connect(handler,
&MpdEventHandler::metadataUpdate,
+ this,
+ &MediaplayerMpdBackend::updateMetadata);
+ connect(this,
+ &MediaplayerMpdBackend::metadataUpdate,
player,
- &Mediaplayer::updateMetadata);
+ &Mediaplayer::updateLocalMetadata);
m_handlerThread.start();
// Start event handler worker loop
- emit start();
+ emit startHandler();
}
MediaplayerMpdBackend::~MediaplayerMpdBackend()
@@ -92,6 +108,62 @@ MediaplayerMpdBackend::~MediaplayerMpdBackend()
mpd_connection_free(m_mpd_conn);
}
+void MediaplayerMpdBackend::start()
+{
+ // An explicit refresh of the playlist is likely necessary
+ // here to handle starting in situations where MPD has been
+ // running before we were (e.g. restarting an app after a
+ // crash). At present the timing seems to be such that we
+ // do not have to worry about signals being sent before the
+ // app is ready, but this could be an issue down the road.
+}
+
+
+void MediaplayerMpdBackend::refresh_metadata()
+{
+ if (m_cached_metadata.isEmpty()) {
+ // This can happen if the app starts up with Bluetooth
+ // connected and then the source is switched. Provide
+ // empty metadata to clear up the app's state.
+ //
+ // Getting the metadata for the first entry in the playlist
+ // to provide it here intersects with how to handle hitting
+ // the end of the playlist and MPD stopping, handling that
+ // is complicated enough that it is being left as future
+ // development.
+
+ QVariantMap track;
+ track.insert("title", "");
+ track.insert("artist", "");
+ track.insert("album", "");
+ track.insert("duration", 0);
+ m_cached_metadata["track"] = track;
+ m_cached_metadata["position"] = 0;
+ m_cached_metadata["status"] = "stopped";
+ }
+
+ // The demo app currently ignores other information in an update with
+ // album art, which is a historical artifact of it arriving in a second
+ // update. Until that assumption is perhaps changed, to avoid having to
+ // complicate things wrt caching, we recreate this behavior using the
+ // metadata we have if it contains album art.
+ if (m_cached_metadata.contains("track")) {
+ QVariantMap tmp = m_cached_metadata;
+ QVariantMap track = tmp.value("track").toMap();
+ if (track.contains("image")) {
+ track.remove("image");
+ tmp["track"] = track;
+
+ // Send this as the initial no art update
+ emit metadataUpdate(tmp);
+ }
+ }
+
+ emit metadataUpdate(m_cached_metadata);
+}
+
+// Slots
+
void MediaplayerMpdBackend::connectionKeepaliveTimeout(void)
{
m_mpd_conn_mutex.lock();
@@ -128,7 +200,8 @@ void MediaplayerMpdBackend::songPositionTimeout(void)
m_song_pos_ms += 250;
QVariantMap metadata;
metadata["position"] = m_song_pos_ms;
- m_player->updateMetadata(metadata);
+ m_cached_metadata["position"] = m_song_pos_ms;
+ emit positionMetadataUpdate(metadata);
}
m_state_mutex.unlock();
@@ -147,7 +220,6 @@ void MediaplayerMpdBackend::updatePlaybackState(int queue_pos, int song_pos_ms,
} else {
// Stop position timer
m_song_pos_timer->stop();
- //m_song_pos_ms = 0;
}
}
m_playing = state;
@@ -155,6 +227,43 @@ void MediaplayerMpdBackend::updatePlaybackState(int queue_pos, int song_pos_ms,
m_state_mutex.unlock();
}
+void MediaplayerMpdBackend::updateMetadata(QVariantMap metadata)
+{
+ // Update our cached metadata to allow fast refreshes upon request
+ // without having to make an out of band query to MPD.
+ //
+ // Updates from the event handler thread that contain track
+ // information are assumed to be complete with the exception of the
+ // album art, which may be included in a follow up update if it is
+ // present.
+
+ if (metadata.contains("status")) {
+ QString status = metadata.value("status").toString();
+ if (status == "stopped") {
+ // There's likely no track information as chances are
+ // this is from hitting the end of the playlist, so
+ // clear things out.
+ // If there actually is track metadata, it'll be
+ // handled by the logic below.
+ m_cached_metadata.clear();
+ }
+ m_cached_metadata["status"] = metadata["status"];
+ }
+
+ if (metadata.contains("track")) {
+ QVariantMap track = metadata.value("track").toMap();
+ m_cached_metadata["track"] = track;
+ }
+
+ // After playback starts, position updates will come from our local
+ // timer, but take the initial value if present.
+ if (metadata.contains("position"))
+ m_cached_metadata["position"] = metadata["position"];
+
+ // Send update up to front end
+ emit metadataUpdate(metadata);
+}
+
// Control methods
void MediaplayerMpdBackend::play()