summaryrefslogtreecommitdiffstats
path: root/mediaplayer/MediaplayerMpdBackend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mediaplayer/MediaplayerMpdBackend.cpp')
-rw-r--r--mediaplayer/MediaplayerMpdBackend.cpp284
1 files changed, 284 insertions, 0 deletions
diff --git a/mediaplayer/MediaplayerMpdBackend.cpp b/mediaplayer/MediaplayerMpdBackend.cpp
new file mode 100644
index 0000000..c42d18a
--- /dev/null
+++ b/mediaplayer/MediaplayerMpdBackend.cpp
@@ -0,0 +1,284 @@
+/*
+ * 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 "MediaplayerMpdBackend.h"
+#include "MpdEventHandler.h"
+#include "mediaplayer.h"
+
+// Use a 60s timeout on our MPD connection
+// NOTE: The connection is actively poked at a higher frequency than
+// this to ensure we don't hit the timeout. The alternatives
+// are to either open and close a connection for every command,
+// or try to keep the connection in idle mode when not using it.
+// The latter is deemed too complicated for our purposes for now,
+// due to it likely requiring another thread.
+#define MPD_CONNECTION_TIMEOUT 60000
+
+MediaplayerMpdBackend::MediaplayerMpdBackend(Mediaplayer *player, QQmlContext *context, QObject *parent) :
+ QObject(parent),
+ m_player(player)
+{
+ struct mpd_connection *conn = mpd_connection_new(NULL, 0, MPD_CONNECTION_TIMEOUT);
+ if (!conn) {
+ qFatal("Could not create MPD connection");
+ }
+ if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) {
+ qFatal("%s", mpd_connection_get_error_message(conn));
+ }
+ m_mpd_conn = conn;
+
+ // Set up connection keepalive timer
+ m_mpd_conn_timer = new QTimer(this);
+ connect(m_mpd_conn_timer, &QTimer::timeout, this, &MediaplayerMpdBackend::connectionKeepaliveTimeout);
+ m_mpd_conn_timer->start(MPD_CONNECTION_TIMEOUT / 2);
+
+ m_song_pos_timer = new QTimer(this);
+ connect(m_song_pos_timer, &QTimer::timeout, this, &MediaplayerMpdBackend::songPositionTimeout);
+
+ MpdEventHandler *handler = new MpdEventHandler();
+ handler->moveToThread(&m_handlerThread);
+ connect(&m_handlerThread, &QThread::finished, handler, &QObject::deleteLater);
+ connect(this, &MediaplayerMpdBackend::start, 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.
+ connect(handler,
+ &MpdEventHandler::playlistUpdate,
+ player,
+ &Mediaplayer::updatePlaylist);
+ connect(handler,
+ &MpdEventHandler::metadataUpdate,
+ player,
+ &Mediaplayer::updateMetadata);
+
+ m_handlerThread.start();
+
+ // Start event handler worker loop
+ emit start();
+}
+
+MediaplayerMpdBackend::~MediaplayerMpdBackend()
+{
+ m_handlerThread.quit();
+ m_handlerThread.wait();
+
+ m_mpd_conn_timer->stop();
+ delete m_mpd_conn_timer;
+
+ mpd_connection_free(m_mpd_conn);
+}
+
+void MediaplayerMpdBackend::connectionKeepaliveTimeout(void)
+{
+ m_mpd_conn_mutex.lock();
+
+ // Clear any lingering non-fatal errors
+ if (!mpd_connection_clear_error(m_mpd_conn)) {
+ // NOTE: There should likely be an attempt to reconnect here,
+ // but it definitely would complicate things for all the
+ // other users.
+ qWarning() << "MPD connection in error state!";
+ m_mpd_conn_mutex.unlock();
+ return;
+ }
+
+ struct mpd_status *status = mpd_run_status(m_mpd_conn);
+ if (!status) {
+ qWarning() << "MPD connection status check failed";
+ } else {
+ mpd_status_free(status);
+ }
+
+ m_mpd_conn_mutex.unlock();
+}
+
+void MediaplayerMpdBackend::songPositionTimeout(void)
+{
+ m_state_mutex.lock();
+
+ if (m_playing) {
+ // Instead of the expense of repeatedly calling mpd_run_status,
+ // provide our own elapsed time. In practice this seems
+ // sufficient for reasonable behavior in the application UI, and
+ // it is what seems recommended for MPD client implementations.
+ m_song_pos_ms += 250;
+ QVariantMap metadata;
+ metadata["position"] = m_song_pos_ms;
+ m_player->updateMetadata(metadata);
+ }
+
+ m_state_mutex.unlock();
+}
+
+void MediaplayerMpdBackend::updatePlaybackState(int queue_pos, int song_pos_ms, bool state)
+{
+ m_state_mutex.lock();
+
+ m_queue_pos = queue_pos;
+ m_song_pos_ms = song_pos_ms;
+ if (m_playing != state) {
+ if (state) {
+ // Start position timer
+ m_song_pos_timer->start(250);
+ } else {
+ // Stop position timer
+ m_song_pos_timer->stop();
+ //m_song_pos_ms = 0;
+ }
+ }
+ m_playing = state;
+
+ m_state_mutex.unlock();
+}
+
+// Control methods
+
+void MediaplayerMpdBackend::play()
+{
+ m_mpd_conn_mutex.lock();
+
+ m_state_mutex.lock();
+ if (!m_playing) {
+ mpd_run_play(m_mpd_conn);
+ }
+ m_state_mutex.unlock();
+
+ m_mpd_conn_mutex.unlock();
+}
+
+void MediaplayerMpdBackend::pause()
+{
+ m_mpd_conn_mutex.lock();
+
+ m_state_mutex.lock();
+ if (m_playing) {
+ mpd_run_pause(m_mpd_conn, true);
+ }
+ m_state_mutex.unlock();
+
+ m_mpd_conn_mutex.unlock();
+}
+
+void MediaplayerMpdBackend::previous()
+{
+ m_mpd_conn_mutex.lock();
+
+ // MPD only allows next/previous if playing
+ m_state_mutex.lock();
+ if (m_playing) {
+ mpd_run_previous(m_mpd_conn);
+ }
+ m_state_mutex.unlock();
+
+ m_mpd_conn_mutex.unlock();
+}
+
+void MediaplayerMpdBackend::next()
+{
+ m_mpd_conn_mutex.lock();
+
+ // MPD only allows next/previous if playing
+ m_state_mutex.lock();
+ if (m_playing) {
+ mpd_run_next(m_mpd_conn);
+ }
+ m_state_mutex.unlock();
+
+ m_mpd_conn_mutex.unlock();
+}
+
+void MediaplayerMpdBackend::seek(int milliseconds)
+{
+ m_mpd_conn_mutex.lock();
+
+ float t = milliseconds;
+ t /= 1000.0;
+ mpd_run_seek_current(m_mpd_conn, t, false);
+
+ m_mpd_conn_mutex.unlock();
+}
+
+// Relative to current position
+void MediaplayerMpdBackend::fastforward(int milliseconds)
+{
+ m_mpd_conn_mutex.lock();
+
+ float t = milliseconds;
+ t /= 1000.0;
+ mpd_run_seek_current(m_mpd_conn, t, true);
+
+ m_mpd_conn_mutex.unlock();
+}
+
+// Relative to current position
+void MediaplayerMpdBackend::rewind(int milliseconds)
+{
+ m_mpd_conn_mutex.lock();
+
+ float t = -milliseconds;
+ t /= 1000.0;
+ mpd_run_seek_current(m_mpd_conn, t, true);
+
+ m_mpd_conn_mutex.unlock();
+}
+
+void MediaplayerMpdBackend::picktrack(int track)
+{
+ m_mpd_conn_mutex.lock();
+
+ if (track >= 0) {
+ mpd_run_play_pos(m_mpd_conn, track);
+ }
+
+ m_mpd_conn_mutex.unlock();
+}
+
+void MediaplayerMpdBackend::volume(int volume)
+{
+ // Not implemented
+}
+
+void MediaplayerMpdBackend::loop(QString state)
+{
+ m_mpd_conn_mutex.lock();
+
+ // Song:
+ // mpd_run_single_state(m_mpd_conn, MPD_SINGLE_ON)
+ // mpd_run_repeat(m_mpd_conn, true) to loop
+ //
+ // Playlist:
+ // mpd_run_single_state(m_mpd_conn, MPD_SINGLE_OFF) (default)
+ // mpd_run_repeat(m_mpd_conn, true) to loop
+
+ if (state == "playlist") {
+ mpd_run_repeat(m_mpd_conn, true);
+ } else {
+ mpd_run_repeat(m_mpd_conn, false);
+ }
+
+ m_mpd_conn_mutex.unlock();
+}