summaryrefslogtreecommitdiffstats
path: root/mediaplayer
diff options
context:
space:
mode:
Diffstat (limited to 'mediaplayer')
-rw-r--r--mediaplayer/CMakeLists.txt4
-rw-r--r--mediaplayer/MediaplayerBackend.cpp25
-rw-r--r--mediaplayer/MediaplayerBackend.h50
-rw-r--r--mediaplayer/MediaplayerBluezBackend.cpp149
-rw-r--r--mediaplayer/MediaplayerBluezBackend.h70
-rw-r--r--mediaplayer/MediaplayerMpdBackend.cpp139
-rw-r--r--mediaplayer/MediaplayerMpdBackend.h22
-rw-r--r--mediaplayer/MpdEventHandler.cpp16
-rw-r--r--mediaplayer/MpdEventHandler.h3
-rw-r--r--mediaplayer/mediaplayer.cpp138
-rw-r--r--mediaplayer/mediaplayer.h111
11 files changed, 635 insertions, 92 deletions
diff --git a/mediaplayer/CMakeLists.txt b/mediaplayer/CMakeLists.txt
index 3f77b8d..35dda69 100644
--- a/mediaplayer/CMakeLists.txt
+++ b/mediaplayer/CMakeLists.txt
@@ -5,13 +5,15 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qtappfw-mediaplayer.pc
add_library(qtappfw-mediaplayer SHARED
mediaplayer.cpp
+ MediaplayerBackend.cpp
MediaplayerMpdBackend.cpp
+ MediaplayerBluezBackend.cpp
MpdEventHandler.cpp)
target_include_directories(qtappfw-mediaplayer PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
target_include_directories(qtappfw-mediaplayer PUBLIC "${CMAKE_INSTALL_INCLUDEDIR}")
-target_link_libraries(qtappfw-mediaplayer Qt5::Qml PkgConfig::libmpdclient)
+target_link_libraries(qtappfw-mediaplayer qtappfw-bt Qt5::Qml PkgConfig::libmpdclient)
set_target_properties(qtappfw-mediaplayer PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 1
diff --git a/mediaplayer/MediaplayerBackend.cpp b/mediaplayer/MediaplayerBackend.cpp
new file mode 100644
index 0000000..19480ca
--- /dev/null
+++ b/mediaplayer/MediaplayerBackend.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MediaplayerBackend.h"
+
+// Stub to make library build self-contained, with header-only it seems the
+// moc generated bits don't get pulled in or something along those lines.
+
+MediaplayerBackend::MediaplayerBackend(Mediaplayer *player, QObject * parent) :
+ QObject(parent), m_player(player)
+{
+}
diff --git a/mediaplayer/MediaplayerBackend.h b/mediaplayer/MediaplayerBackend.h
new file mode 100644
index 0000000..31b0874
--- /dev/null
+++ b/mediaplayer/MediaplayerBackend.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEDIAPLAYER_BACKEND_H
+#define MEDIAPLAYER_BACKEND_H
+
+#include <QObject>
+#include <QtQml/QQmlContext>
+#include "mediaplayer.h"
+
+class MediaplayerBackend : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit MediaplayerBackend(Mediaplayer *player, QObject * parent = Q_NULLPTR);
+ virtual ~MediaplayerBackend() {};
+
+ virtual void start() = 0;
+ virtual void refresh_metadata() = 0;
+
+ virtual void play() = 0;
+ virtual void pause() = 0;
+ virtual void previous() = 0;
+ virtual void next() = 0;
+ virtual void seek(int) = 0;
+ virtual void fastforward(int) = 0;
+ virtual void rewind(int) = 0;
+ virtual void picktrack(int) = 0;
+ virtual void volume(int) = 0;
+ virtual void loop(QString) = 0;
+
+private:
+ Mediaplayer *m_player;
+};
+
+#endif // MEDIAPLAYER_MPD_BACKEND_H
diff --git a/mediaplayer/MediaplayerBluezBackend.cpp b/mediaplayer/MediaplayerBluezBackend.cpp
new file mode 100644
index 0000000..db189ac
--- /dev/null
+++ b/mediaplayer/MediaplayerBluezBackend.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QDebug>
+#include "MediaplayerBluezBackend.h"
+#include "mediaplayer.h"
+
+
+MediaplayerBluezBackend::MediaplayerBluezBackend(Mediaplayer *player, QQmlContext *context, QObject *parent) :
+ MediaplayerBackend(player, parent)
+{
+ m_bluetooth = new Bluetooth(false, context, true);
+ if (!m_bluetooth)
+ qFatal("Could not create Bluetooth");
+
+ // Connect up metadata updates
+ connect(m_bluetooth,
+ &Bluetooth::mediaConnectedChanged,
+ player,
+ &Mediaplayer::updateBluetoothMediaConnected);
+#if 0
+ connect(m_bluetooth,
+ &Bluetooth::mediaPropertiesChanged,
+ player,
+ &Mediaplayer::updateBluetoothMetadata);
+#else
+ // Proxy Bluetooth metadata updates so we can cache the
+ // latest update locally to allow for quick refreshes.
+ connect(m_bluetooth,
+ &Bluetooth::mediaPropertiesChanged,
+ this,
+ &MediaplayerBluezBackend::updateMetadata);
+ connect(this,
+ &MediaplayerBluezBackend::metadataUpdate,
+ player,
+ &Mediaplayer::updateBluetoothMetadata);
+#endif
+}
+
+MediaplayerBluezBackend::~MediaplayerBluezBackend()
+{
+ delete m_bluetooth;
+}
+
+void MediaplayerBluezBackend::start()
+{
+ m_bluetooth->start();
+}
+
+void MediaplayerBluezBackend::refresh_metadata()
+{
+ // Try to avoid driving a D-Bus request if we have a cached update
+ if (m_cached_metadata.isEmpty())
+ m_bluetooth->refresh_media_state();
+ else
+ emit metadataUpdate(m_cached_metadata);
+}
+
+// Slots
+
+void MediaplayerBluezBackend::updateMetadata(QVariantMap metadata)
+{
+ m_cached_metadata = metadata;
+ emit metadataUpdate(metadata);
+}
+
+// Control methods
+
+void MediaplayerBluezBackend::play()
+{
+ m_bluetooth->media_control(Bluetooth::MediaAction::Play);
+}
+
+void MediaplayerBluezBackend::pause()
+{
+ m_bluetooth->media_control(Bluetooth::MediaAction::Pause);
+}
+
+void MediaplayerBluezBackend::previous()
+{
+ m_bluetooth->media_control(Bluetooth::MediaAction::Previous);
+}
+
+void MediaplayerBluezBackend::next()
+{
+ m_bluetooth->media_control(Bluetooth::MediaAction::Next);
+}
+
+void MediaplayerBluezBackend::seek(int milliseconds)
+{
+ // Not implemented, currently not needed by demo app
+ // It is not quite obvious how this is implemented with the AVRCP
+ // commands not taking a position/offset.
+}
+
+// Relative to current position
+void MediaplayerBluezBackend::fastforward(int milliseconds)
+{
+ // Not implemented, currently not needed by demo app
+ // It is not quite obvious how this is implemented with the AVRCP
+ // commands not taking a position/offset.
+}
+
+// Relative to current position
+void MediaplayerBluezBackend::rewind(int milliseconds)
+{
+ // Not implemented, currently not needed by demo app
+ // It is not quite obvious how this is implemented with the AVRCP
+ // commands not taking a position/offset.
+}
+
+void MediaplayerBluezBackend::picktrack(int track)
+{
+ // Not implemented
+}
+
+void MediaplayerBluezBackend::volume(int volume)
+{
+ // Not implemented
+}
+
+void MediaplayerBluezBackend::loop(QString state)
+{
+ // Not implemented, but potentially possible by setting player property
+ // with bluez-glib addition
+}
+
+void MediaplayerBluezBackend::connect_media()
+{
+ m_bluetooth->media_control(Bluetooth::MediaAction::Connect);
+}
+
+void MediaplayerBluezBackend::disconnect_media()
+{
+ m_bluetooth->media_control(Bluetooth::MediaAction::Disconnect);
+}
diff --git a/mediaplayer/MediaplayerBluezBackend.h b/mediaplayer/MediaplayerBluezBackend.h
new file mode 100644
index 0000000..e37f35f
--- /dev/null
+++ b/mediaplayer/MediaplayerBluezBackend.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEDIAPLAYER_BLUEZ_BACKEND_H
+#define MEDIAPLAYER_BLUEZ_BACKEND_H
+
+#include <QObject>
+#include <QtQml/QQmlContext>
+#include <QThread>
+#include <QTimer>
+#include <QMutex>
+
+#include "MediaplayerBackend.h"
+#include "mediaplayer.h"
+#include "bluetooth.h"
+
+class MediaplayerBluezBackend : public MediaplayerBackend
+{
+ Q_OBJECT
+
+public:
+ explicit MediaplayerBluezBackend(Mediaplayer *player, QQmlContext *context, QObject * parent = Q_NULLPTR);
+ virtual ~MediaplayerBluezBackend();
+
+ void start();
+ void refresh_metadata();
+
+ void play();
+ void pause();
+ void previous();
+ void next();
+ void seek(int);
+ void fastforward(int);
+ void rewind(int);
+ void picktrack(int);
+ void volume(int);
+ void loop(QString);
+
+ // Bluetooth specific
+ void connect_media();
+ void disconnect_media();
+
+signals:
+ void metadataUpdate(QVariantMap metadata);
+
+private slots:
+ void updateMetadata(QVariantMap metadata);
+
+private:
+ Mediaplayer *m_player;
+ Bluetooth *m_bluetooth;
+
+ // Cached metadata to simplify refresh requests (e.g. on source switch)
+ QVariantMap m_cached_metadata;
+};
+
+#endif // MEDIAPLAYER_BLUEZ_BACKEND_H
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()
diff --git a/mediaplayer/MediaplayerMpdBackend.h b/mediaplayer/MediaplayerMpdBackend.h
index b181fda..4b8d74c 100644
--- a/mediaplayer/MediaplayerMpdBackend.h
+++ b/mediaplayer/MediaplayerMpdBackend.h
@@ -23,9 +23,10 @@
#include <QTimer>
#include <QMutex>
#include <mpd/client.h>
+#include "MediaplayerBackend.h"
#include "mediaplayer.h"
-class MediaplayerMpdBackend : public QObject
+class MediaplayerMpdBackend : public MediaplayerBackend
{
Q_OBJECT
@@ -33,6 +34,9 @@ public:
explicit MediaplayerMpdBackend(Mediaplayer *player, QQmlContext *context, QObject * parent = Q_NULLPTR);
virtual ~MediaplayerMpdBackend();
+ void start();
+ void refresh_metadata();
+
void play();
void pause();
void previous();
@@ -45,7 +49,15 @@ public:
void loop(QString);
signals:
- void start(void);
+ void startHandler(void);
+ void metadataUpdate(QVariantMap metadata);
+ void positionMetadataUpdate(QVariantMap metadata);
+
+private slots:
+ void connectionKeepaliveTimeout(void);
+ void songPositionTimeout(void);
+ void updatePlaybackState(int queue_pos, int song_pos_ms, bool state);
+ void updateMetadata(QVariantMap metadata);
private:
Mediaplayer *m_player;
@@ -63,10 +75,8 @@ private:
QThread m_handlerThread;
-private slots:
- void connectionKeepaliveTimeout(void);
- void songPositionTimeout(void);
- void updatePlaybackState(int queue_pos, int song_pos_ms, bool state);
+ // Cached metadata to simplify refresh requests (e.g. on source switch)
+ QVariantMap m_cached_metadata;
};
#endif // MEDIAPLAYER_MPD_BACKEND_H
diff --git a/mediaplayer/MpdEventHandler.cpp b/mediaplayer/MpdEventHandler.cpp
index 40d2999..56a4612 100644
--- a/mediaplayer/MpdEventHandler.cpp
+++ b/mediaplayer/MpdEventHandler.cpp
@@ -15,6 +15,7 @@
*/
#include <QDebug>
+#include <QFileInfo>
#include "MpdEventHandler.h"
MpdEventHandler::MpdEventHandler(QObject *parent) :
@@ -101,6 +102,13 @@ void MpdEventHandler::handleQueueEvent(void)
QString genre(mpd_song_get_tag(song, MPD_TAG_GENRE, 0));
QString uri(mpd_song_get_uri(song));
int pos = mpd_song_get_pos(song);
+
+ if (title.isEmpty()) {
+ // If there's no tag, use the filename
+ QFileInfo fi(uri);
+ title = fi.fileName();
+ }
+
//qDebug() << "Queue[" << pos << "]: " << artist << " - " << title << " / " << album << ", genre " << genre;
QVariantMap track;
@@ -138,6 +146,13 @@ void MpdEventHandler::handlePlayerEvent(void)
QString genre(mpd_song_get_tag(song, MPD_TAG_GENRE, 0));
pos = mpd_song_get_pos(song);
uri = mpd_song_get_uri(song);
+
+ if (title.isEmpty()) {
+ // If there's no tag, use the filename
+ QFileInfo fi(uri);
+ title = fi.fileName();
+ }
+
//qDebug() << "Current song[" << pos << "]: " << artist << " - " << title << " / " << album << ", genre " << genre;
track["title"] = title;
@@ -147,6 +162,7 @@ void MpdEventHandler::handlePlayerEvent(void)
track["index"] = pos;
track["duration"] = mpd_song_get_duration_ms(song);
track["path"] = uri;
+
mpd_song_free(song);
metadata["track"] = track;
diff --git a/mediaplayer/MpdEventHandler.h b/mediaplayer/MpdEventHandler.h
index fcd6155..d51c373 100644
--- a/mediaplayer/MpdEventHandler.h
+++ b/mediaplayer/MpdEventHandler.h
@@ -18,9 +18,6 @@
#define MPD_EVENT_HANDLER_H
#include <QObject>
-//#include <QThread>
-//#include <QTimer>
-//#include <QMutex>
#include <mpd/client.h>
// Use a 60s timeout on our MPD connection
diff --git a/mediaplayer/mediaplayer.cpp b/mediaplayer/mediaplayer.cpp
index f6a9cbd..f686fbb 100644
--- a/mediaplayer/mediaplayer.cpp
+++ b/mediaplayer/mediaplayer.cpp
@@ -15,10 +15,11 @@
*/
#include <QDebug>
-#include <QJsonObject>
+#include <QMutexLocker>
#include "mediaplayer.h"
#include "MediaplayerMpdBackend.h"
+#include "MediaplayerBluezBackend.h"
Playlist::Playlist(QVariantMap &item)
@@ -34,24 +35,37 @@ Playlist::Playlist(QVariantMap &item)
Playlist::~Playlist() {}
-Mediaplayer::Mediaplayer (QQmlContext *context, QObject * parent) :
+Mediaplayer::Mediaplayer(QQmlContext *context, QObject * parent) :
QObject(parent)
{
m_context = context;
m_context->setContextProperty("MediaplayerModel", QVariant::fromValue(m_playlist));
- m_backend = new MediaplayerMpdBackend(this, context);
- if (!m_backend)
- qFatal("Could not create MediaPlayerBackend");
+ m_mpd_backend = new MediaplayerMpdBackend(this, context);
+ if (!m_mpd_backend)
+ qFatal("Could not create MediaplayerMpdBackend");
+ m_backend = m_mpd_backend;
+
+ m_bluez_backend = new MediaplayerBluezBackend(this, context);
+ if (!m_bluez_backend)
+ qFatal("Could not create MediaplayerBluezBackend");
}
Mediaplayer::~Mediaplayer()
{
+ delete m_mpd_backend;
+ delete m_bluez_backend;
+}
+
+void Mediaplayer::start()
+{
+ m_mpd_backend->start();
+ m_bluez_backend->start();
}
// Qt UI Context
-void Mediaplayer::updatePlaylist(QVariantMap playlist)
+void Mediaplayer::updateLocalPlaylist(QVariantMap playlist)
{
QVariantList list = playlist["list"].toList();
@@ -82,30 +96,44 @@ void Mediaplayer::updatePlaylist(QVariantMap playlist)
m_context->setContextProperty("MediaplayerModel", QVariant::fromValue(m_playlist));
}
-void Mediaplayer::updateMetadata(QVariantMap metadata)
+void Mediaplayer::updateLocalMetadata(QVariantMap metadata)
{
- if (metadata.contains("track")) {
- QVariantMap track = metadata.value("track").toMap();
+ if (!m_bt_connected)
+ updateMetadata(metadata);
+}
- if (track.contains("image")) {
- m_context->setContextProperty("AlbumArt",
- QVariant::fromValue(track.value("image")));
- }
-
- if (!track.contains("artist")) {
- track.insert("artist", "");
- metadata["track"] = track;
- }
-
- if (!track.contains("album")) {
- track.insert("album", "");
- metadata["track"] = track;
+void Mediaplayer::updateBluetoothMetadata(QVariantMap metadata)
+{
+ if (m_bt_connected)
+ updateMetadata(metadata);
+}
+
+void Mediaplayer::updateBluetoothMediaConnected(const bool connected)
+{
+ if (m_bt_connected != connected) {
+ QMutexLocker locker(&m_backend_mutex);
+ if (connected) {
+ qDebug() << "Mediaplayer::updateBluetoothMediaConnected: switching to BlueZ backend";
+ m_backend = m_bluez_backend;
+ m_bt_connected = connected;
+ m_bluez_backend->refresh_metadata();
+ } else {
+ qDebug() << "Mediaplayer::updateBluetoothMediaConnected: switching to MPD backend";
+ m_backend = m_mpd_backend;
+ m_bt_connected = connected;
+ m_mpd_backend->refresh_metadata();
}
}
- emit metadataChanged(metadata);
+ m_bt_operation_mutex.lock();
+ if (m_bt_operation) {
+ m_bt_operation = false;
+ }
+ // else externally driven event
+ m_bt_operation_mutex.unlock();
}
+
// Control methods
// For backwards compatibility
@@ -122,60 +150,124 @@ void Mediaplayer::connect()
void Mediaplayer::disconnectBluetooth()
{
+ m_bt_operation_mutex.lock();
+ if (m_bt_operation) {
+ m_bt_operation_mutex.unlock();
+ qDebug() << "Bluetooth media connection operation in progress, ignoring";
+ return;
+ }
+ m_bt_operation_mutex.unlock();
+
// Disconnect from Bluetooth media
+ if (m_bt_connected) {
+ // Explicitly pausing before disconnecting does not seem to be required
+ m_bluez_backend->disconnect_media();
+ }
}
void Mediaplayer::connectBluetooth()
{
+ m_bt_operation_mutex.lock();
+ if (m_bt_operation) {
+ m_bt_operation_mutex.unlock();
+ qDebug() << "Bluetooth media connection operation in progress, ignoring";
+ return;
+ }
+ m_bt_operation_mutex.unlock();
+
// Connect to Bluetooth media
+ if (!m_bt_connected) {
+ m_mpd_backend->pause();
+ m_bluez_backend->connect_media();
+ }
}
void Mediaplayer::play()
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->play();
}
void Mediaplayer::pause()
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->pause();
}
void Mediaplayer::previous()
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->previous();
}
void Mediaplayer::next()
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->next();
}
void Mediaplayer::seek(int milliseconds)
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->seek(milliseconds);
}
void Mediaplayer::fastforward(int milliseconds)
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->fastforward(milliseconds);
}
void Mediaplayer::rewind(int milliseconds)
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->rewind(milliseconds);
}
void Mediaplayer::picktrack(int track)
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->picktrack(track);
}
void Mediaplayer::volume(int volume)
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->volume(volume);
}
void Mediaplayer::loop(QString state)
{
+ QMutexLocker locker(&m_backend_mutex);
m_backend->loop(state);
}
+
+// Private
+
+// Common metadata helper
+void Mediaplayer::updateMetadata(QVariantMap &metadata)
+{
+ if (metadata.contains("track")) {
+ QVariantMap track = metadata.value("track").toMap();
+
+ if (track.contains("image")) {
+ m_context->setContextProperty("AlbumArt",
+ QVariant::fromValue(track.value("image")));
+ }
+
+ if (!track.contains("artist")) {
+ track.insert("artist", "");
+ metadata["track"] = track;
+ }
+
+ if (!track.contains("album")) {
+ track.insert("album", "");
+ metadata["track"] = track;
+ }
+ }
+
+ // Insert current Bluetooth status
+ metadata["connected"] = m_bt_connected;
+
+ emit metadataChanged(metadata);
+}
diff --git a/mediaplayer/mediaplayer.h b/mediaplayer/mediaplayer.h
index 9afe6c6..e274d75 100644
--- a/mediaplayer/mediaplayer.h
+++ b/mediaplayer/mediaplayer.h
@@ -18,6 +18,7 @@
#define MEDIAPLAYER_H
#include <QObject>
+#include <QMutex>
#include <QtQml/QQmlContext>
#include <QtQml/QQmlListProperty>
@@ -36,71 +37,93 @@ class Playlist : public QObject
Q_PROPERTY(QString genre READ genre NOTIFY genreChanged)
public:
- explicit Playlist(QVariantMap& item);
- virtual ~Playlist();
+ explicit Playlist(QVariantMap& item);
+ virtual ~Playlist();
- bool operator<(Playlist& c) { return ((this->m_index < c.m_index)); };
- int index() { return m_index; };
- int duration() { return m_duration; };
- QString path() { return m_path; };
+ bool operator<(Playlist& c) { return ((this->m_index < c.m_index)); };
+ int index() { return m_index; };
+ int duration() { return m_duration; };
+ QString path() { return m_path; };
- // METADATA FIELDS
- QString title() { return m_title; };
- QString album() { return m_album; };
- QString artist() { return m_artist; };
- QString genre() { return m_genre; };
+ // METADATA FIELDS
+ QString title() { return m_title; };
+ QString album() { return m_album; };
+ QString artist() { return m_artist; };
+ QString genre() { return m_genre; };
signals:
- void indexChanged();
- void durationChanged();
- void pathChanged();
- void titleChanged();
- void albumChanged();
- void artistChanged();
- void genreChanged();
+ void indexChanged();
+ void durationChanged();
+ void pathChanged();
+ void titleChanged();
+ void albumChanged();
+ void artistChanged();
+ void genreChanged();
private:
- int m_index, m_duration;
- QString m_path, m_title, m_album, m_artist, m_genre;
+ int m_index, m_duration;
+ QString m_path, m_title, m_album, m_artist, m_genre;
};
+class MediaplayerBackend;
class MediaplayerMpdBackend;
+class MediaplayerBluezBackend;
class Mediaplayer : public QObject
{
Q_OBJECT
public:
- explicit Mediaplayer(QQmlContext *context, QObject * parent = Q_NULLPTR);
- virtual ~Mediaplayer();
-
- // controls
- Q_INVOKABLE void connect();
- Q_INVOKABLE void disconnect();
- Q_INVOKABLE void connectBluetooth();
- Q_INVOKABLE void disconnectBluetooth();
- Q_INVOKABLE void play();
- Q_INVOKABLE void pause();
- Q_INVOKABLE void previous();
- Q_INVOKABLE void next();
- Q_INVOKABLE void seek(int);
- Q_INVOKABLE void fastforward(int);
- Q_INVOKABLE void rewind(int);
- Q_INVOKABLE void picktrack(int);
- Q_INVOKABLE void volume(int);
- Q_INVOKABLE void loop(QString);
+ explicit Mediaplayer(QQmlContext *context, QObject * parent = Q_NULLPTR);
+ virtual ~Mediaplayer();
+
+ // NOTE: Start in the sense of the user is ready to receive events
+ Q_INVOKABLE void start(void);
+
+ // controls
+ Q_INVOKABLE void connect();
+ Q_INVOKABLE void disconnect();
+ Q_INVOKABLE void connectBluetooth();
+ Q_INVOKABLE void disconnectBluetooth();
+ Q_INVOKABLE void play();
+ Q_INVOKABLE void pause();
+ Q_INVOKABLE void previous();
+ Q_INVOKABLE void next();
+ Q_INVOKABLE void seek(int);
+ Q_INVOKABLE void fastforward(int);
+ Q_INVOKABLE void rewind(int);
+ Q_INVOKABLE void picktrack(int);
+ Q_INVOKABLE void volume(int);
+ Q_INVOKABLE void loop(QString);
public slots:
- void updatePlaylist(QVariantMap playlist);
- void updateMetadata(QVariantMap metadata);
+ void updateLocalPlaylist(QVariantMap playlist);
+ void updateLocalMetadata(QVariantMap metadata);
+ void updateBluetoothMetadata(QVariantMap metadata);
+ void updateBluetoothMediaConnected(const bool connected);
signals:
- void metadataChanged(QVariantMap metadata);
+ void metadataChanged(QVariantMap metadata);
private:
- QQmlContext *m_context;
- QList<QObject *> m_playlist;
- MediaplayerMpdBackend *m_backend;
+ void updateMetadata(QVariantMap &metadata);
+
+ QQmlContext *m_context;
+ QList<QObject *> m_playlist;
+
+ MediaplayerBackend *m_backend;
+ MediaplayerMpdBackend *m_mpd_backend;
+ MediaplayerBluezBackend *m_bluez_backend;
+ QMutex m_backend_mutex;
+ bool m_bt_connected = false;
+
+ // Need to track if a Bluetooth connect/disconnect is in progress
+ // to serialize things. This avoids potential issues from multiple
+ // inputs from the UI, which are easy to trigger with the current
+ // demo app not having debouncing logic wrt the operation not being
+ // instant.
+ bool m_bt_operation = false;
+ QMutex m_bt_operation_mutex;
};
#endif // MEDIAPLAYER_H