summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMavridis Philippe <mavridisf@gmail.com>2023-05-17 15:33:02 +0300
committerMavridis Philippe <mavridisf@gmail.com>2024-09-08 01:41:03 +0300
commitc6007f32d633e03d32548a170590ac3d1691f8fc (patch)
tree35e2a52b01d3469b04be592bc0e6c4a1ec98d394
parentf0b65f432b2e602bd9a515bcc460bcd6790a4925 (diff)
downloadkaffeine-c6007f32d633e03d32548a170590ac3d1691f8fc.tar.gz
kaffeine-c6007f32d633e03d32548a170590ac3d1691f8fc.zip
Add libmpv backend
This commit adds the basic functionality including: - local/remote video playback - subtitles support - Kaffeine playlist integration What is missing: - sound controls Signed-off-by: Mavridis Philippe <mavridisf@gmail.com>
-rw-r--r--CMakeLists.txt1
-rw-r--r--ConfigureChecks.cmake10
-rw-r--r--kaffeine/src/player-parts/CMakeLists.txt4
-rw-r--r--kaffeine/src/player-parts/libmpv-part/CMakeLists.txt50
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp91
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_event.h81
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp401
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_part.desktop10
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_part.h116
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_part.rc26
10 files changed, 790 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 61b273e..80e38a8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -62,6 +62,7 @@ option( WITH_XTEST "Enable support for XTest"
option( WITH_XINERAMA "Enable support for xinerama" ${WITH_ALL_OPTIONS} )
option( WITH_XCB "Enable support for xcb" ${WITH_ALL_OPTIONS} )
option( WITH_GSTREAMER "Enable support for gstreamer backend" ${WITH_ALL_OPTIONS} )
+option( WITH_LIBMPV "Enable support for libmpv backend" ${WITH_ALL_OPTIONS} )
option( WITH_OGGVORBIS "Enable support for oggvorbis" ${WITH_ALL_OPTIONS} )
option( WITH_LAME "Enable support for lame" ${WITH_ALL_OPTIONS} )
option( WITH_DVB "Enable support for dvb" ${WITH_ALL_OPTIONS} )
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index b3becf2..fce49d2 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -135,6 +135,16 @@ if( WITH_GSTREAMER )
message( STATUS "gstreamer plugins version: ${GSTREAMER_PLUGIN_VERSION}" )
endif( WITH_GSTREAMER )
+##### check for libmpv
+if( WITH_LIBMPV )
+ pkg_search_module( MPV mpv )
+ if( NOT MPV_FOUND )
+ tde_message_fatal( "libmpv support has been requested but mpv headers were not found on your system." )
+ endif()
+
+ message( STATUS "libmpv version: ${MPV_VERSION}" )
+endif( WITH_LIBMPV )
+
##### check for lame
diff --git a/kaffeine/src/player-parts/CMakeLists.txt b/kaffeine/src/player-parts/CMakeLists.txt
index 56791b1..248d4bc 100644
--- a/kaffeine/src/player-parts/CMakeLists.txt
+++ b/kaffeine/src/player-parts/CMakeLists.txt
@@ -4,3 +4,7 @@ add_subdirectory( xine-part )
if( WITH_GSTREAMER )
add_subdirectory( gstreamer-part )
endif()
+
+if( WITH_LIBMPV )
+add_subdirectory( libmpv-part )
+endif()
diff --git a/kaffeine/src/player-parts/libmpv-part/CMakeLists.txt b/kaffeine/src/player-parts/libmpv-part/CMakeLists.txt
new file mode 100644
index 0000000..bfc0f0f
--- /dev/null
+++ b/kaffeine/src/player-parts/libmpv-part/CMakeLists.txt
@@ -0,0 +1,50 @@
+include_directories(
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${TDE_INCLUDE_DIR}
+ ${TQT_INCLUDE_DIRS}
+ ${GSTREAMER_INCLUDE_DIRS}
+ ${GSTREAMER_PLUGIN_INCLUDE_DIRS}
+ ${GSTREAMER_VIDEO_INCLUDE_DIRS}
+ ${CMAKE_SOURCE_DIR}/kaffeine/src/player-parts/kaffeine-part
+ ${CMAKE_SOURCE_DIR}/kaffeine/src
+)
+
+link_directories(
+ ${TQT_LIBRARY_DIRS}
+ ${TDE_LIB_DIR}
+ #${MPV_LIBRARY_DIRS}
+)
+
+
+##### libmpvpart (kpart)
+
+tde_add_kpart( libmpvpart AUTOMOC
+
+ SOURCES
+ libmpv_part.cpp libmpv_event.cpp
+ LINK
+ tdecore-shared
+ tdeui-shared
+ tdeio-shared
+ tdeparts-shared
+ kaffeinepart-shared
+ ${MPV_LIBRARIES}
+
+ DESTINATION ${PLUGIN_INSTALL_DIR}
+)
+
+
+##### other data
+
+tde_create_translated_desktop(
+ SOURCE libmpv_part.desktop
+ DESTINATION ${SERVICES_INSTALL_DIR}
+ PO_DIR kaffeine-desktops
+)
+
+install(
+ FILES libmpv_part.rc
+ DESTINATION ${DATA_INSTALL_DIR}/libmpvpart
+)
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp b/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp
new file mode 100644
index 0000000..189b6fe
--- /dev/null
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp
@@ -0,0 +1,91 @@
+/*
+ * Kaffeine libmpv part
+ * Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
+ *
+ * Based on Kaffeine dummy part
+ * Copyright (C) 2004-2005 Jürgen Kofler <kaffeine@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+// TQt
+#include <tqapplication.h>
+
+// TDE
+#include <tdelocale.h>
+#include <kdebug.h>
+
+// Part
+#include "libmpv_event.h"
+#include "libmpv_part.h"
+
+MpvEventThread::MpvEventThread(MpvPart *part) {
+ m_part = part;
+ initPropertyObservers();
+}
+
+void MpvEventThread::initPropertyObservers() {
+ mpv_observe_property(m_part->m_mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(m_part->m_mpv, 0, "duration", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(m_part->m_mpv, 0, "media-title", MPV_FORMAT_STRING);
+
+ // "The advantage over using this instead of calculating it out of other
+ // properties is that it properly falls back to estimating the playback
+ // position from the byte position, if the file duration is not known."
+ mpv_observe_property(m_part->m_mpv, 0, "percent-pos", MPV_FORMAT_DOUBLE);
+
+ //mpv_observe_property(m_mpv, 0, "track-list", MPV_FORMAT_NODE);
+ //mpv_observe_property(m_mpv, 0, "chapter-list", MPV_FORMAT_NODE);
+}
+
+void MpvEventThread::run() {
+ while (m_part->m_mpv) {
+ mpv_event *event = mpv_wait_event(m_part->m_mpv, 0);
+ if (event->event_id != MPV_EVENT_NONE) {
+ processEvent(event);
+ }
+ }
+}
+
+void MpvEventThread::processEvent(mpv_event *event) {
+ switch (event->event_id) {
+ case MPV_EVENT_PROPERTY_CHANGE: {
+ mpv_event_property *prop = (mpv_event_property *)event->data;
+ MpvPropertyChangeEvent *pe = new MpvPropertyChangeEvent(
+ prop->name, prop->format, prop->data);
+ TQApplication::postEvent(m_part, pe);
+ break;
+ }
+
+ case MPV_EVENT_LOG_MESSAGE: {
+ struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
+ kdDebug() << "[mpv " << msg->prefix << "] " << msg->level << ": "
+ << msg->text << endl;
+ break;
+ }
+
+ case MPV_EVENT_END_FILE: {
+ struct mpv_event_end_file *end = (struct mpv_event_end_file *)event->data;
+ TQString error;
+ if (end->reason == MPV_END_FILE_REASON_ERROR) {
+ error = TQString(mpv_error_string(end->error));
+ }
+ MpvEOFEvent *eofe = new MpvEOFEvent(end->reason, error);
+ TQApplication::postEvent(m_part, eofe);
+ }
+
+ default: break; // ignore other events
+ }
+} \ No newline at end of file
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_event.h b/kaffeine/src/player-parts/libmpv-part/libmpv_event.h
new file mode 100644
index 0000000..b13bad2
--- /dev/null
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_event.h
@@ -0,0 +1,81 @@
+/*
+ * Kaffeine libmpv part
+ * Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
+ *
+ * Based on Kaffeine dummy part
+ * Copyright (C) 2004-2005 Jürgen Kofler <kaffeine@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+// TQt
+#include <tqthread.h>
+#include <tqevent.h>
+#include <tqdatetime.h>
+
+// libmpv
+#include <mpv/client.h>
+
+#define MPVPART_EVENT_PROPERTY_CHANGE 65890
+#define MPVPART_EVENT_EOF 65891
+
+class MpvPart;
+
+class MpvPropertyChangeEvent : public TQCustomEvent {
+ public:
+ MpvPropertyChangeEvent(TQCString property, mpv_format format, void *data)
+ : TQCustomEvent(MPVPART_EVENT_PROPERTY_CHANGE),
+ _property(property), _format(format), _data(data)
+ {}
+
+ TQCString property() { return _property; }
+ mpv_format format() { return _format; }
+ void *data() { return _data; }
+
+ double toDouble() { return *(double *)_data; }
+ TQTime toTime() { return TQTime().addMSecs(toDouble() * 1000); }
+
+ private:
+ TQCString _property;
+ mpv_format _format;
+ void *_data;
+};
+
+class MpvEOFEvent : public TQCustomEvent {
+ public:
+ MpvEOFEvent(mpv_end_file_reason reason, TQString error = TQString::null)
+ : TQCustomEvent(MPVPART_EVENT_EOF), _reason(reason), _error(error)
+ {}
+
+ mpv_end_file_reason reason() { return _reason; }
+ TQString error() { return _error; }
+
+ private:
+ mpv_end_file_reason _reason;
+ TQString _error;
+};
+
+class MpvEventThread : public TQThread {
+ public:
+ MpvEventThread(MpvPart *part);
+ virtual void run();
+
+ private:
+ void initPropertyObservers();
+ void processEvent(mpv_event *event);
+
+ private:
+ MpvPart *m_part;
+}; \ No newline at end of file
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp b/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp
new file mode 100644
index 0000000..a42b3c7
--- /dev/null
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp
@@ -0,0 +1,401 @@
+/*
+ * Kaffeine libmpv part
+ * Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
+ *
+ * Based on Kaffeine dummy part
+ * Copyright (C) 2004-2005 Jürgen Kofler <kaffeine@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+// TQt
+#include <tqtooltip.h>
+#include <tqslider.h>
+#include <tqpushbutton.h>
+#include <tqfile.h>
+
+// TDE
+#include <tdeparts/genericfactory.h>
+#include <tdeglobalsettings.h>
+#include <tdeio/netaccess.h>
+#include <tdeaction.h>
+#include <kmimetype.h>
+#include <tdemessagebox.h>
+#include <kxmlguifactory.h>
+#include <tdetoolbar.h>
+#include <kdebug.h>
+
+// Kaffeine
+#include "playlistimport.h"
+
+// Part
+#include "libmpv_part.h"
+#include "libmpv_event.h"
+
+typedef KParts::GenericFactory<MpvPart> MpvPartFactory;
+K_EXPORT_COMPONENT_FACTORY (libmpvpart, MpvPartFactory);
+
+MpvPart::MpvPart(TQWidget* parentWidget, const char* widgetName, TQObject* parent, const char* name, const TQStringList& /*args*/)
+ : KaffeinePart(parent, name ? name : "MpvPart"),
+ m_current(0),
+ m_seeking(false)
+{
+ // Create an instance of this class
+ setInstance(MpvPartFactory::instance());
+
+ // Create container widget
+ m_player = new TQWidget(parentWidget);
+ m_player->setBackgroundColor(TQt::black);
+ m_player->setFocusPolicy(TQ_ClickFocus);
+ setWidget(m_player);
+
+ // Initialize and embed mpv
+ if (!initMpv()) {
+ kdError() << "libmpvpart: initialization of mpv failed!" << endl;
+ emit canceled(i18n("MPV initialization failed!"));
+ return;
+ }
+
+ // Initialize GUI
+ setXMLFile("libmpv_part.rc");
+ initActions();
+ emit setStatusBarText(i18n("Ready"));
+}
+
+MpvPart::~MpvPart()
+{
+ if (m_mpv) {
+ mpv_terminate_destroy(m_mpv);
+ m_mpv = nullptr;
+ }
+
+ if (m_eventThread) {
+ // the event thread doesn't do I/O, so we just kill it
+ m_eventThread->terminate();
+ }
+}
+
+TDEAboutData *MpvPart::createAboutData() {
+ TDEAboutData* aboutData = new TDEAboutData(
+ "libmpvpart", I18N_NOOP("MpvPart"),
+ "0.1", "A Kaffeine player part based on libmpv",
+ TDEAboutData::License_GPL,
+ "(c) 2023 Trinity Desktop Project", 0);
+ aboutData->addAuthor("Mavridis Philippe", I18N_NOOP("Developer"), "mavridisf@gmail.com");
+ aboutData->addAuthor("Jürgen Kofler", I18N_NOOP("Original Kaffeine developer"), "kaffeine@gmx.net");
+ return aboutData;
+}
+
+bool MpvPart::initMpv() {
+ // Create mpv instance
+ m_mpv = mpv_create();
+ if (!m_mpv) {
+ return false;
+ }
+
+ // Embed mpv into container widget
+ int64_t wid = static_cast<int64_t>(m_player->winId());
+ mpv_set_option(m_mpv, "wid", MPV_FORMAT_INT64, &wid);
+
+ // Get log messages with minimal level "info"
+ mpv_request_log_messages(m_mpv, "info");
+
+ // Start mpv event thread
+ m_eventThread = new MpvEventThread(this);
+ m_eventThread->start(TQThread::LowPriority);
+
+ return (mpv_initialize(m_mpv) >= 0);
+}
+
+void MpvPart::initActions()
+{
+ new TDEAction(i18n("Play"), "media-playback-start", 0, this, SLOT(slotPlay()), actionCollection(), "player_play");
+ new TDEAction(i18n("Pause"), "media-playback-pause", Key_Space, this, SLOT(slotTogglePause()), actionCollection(), "player_pause");
+ new TDEAction(i18n("Stop"), "media-playback-stop", Key_Backspace, this, SLOT(slotStop()), actionCollection(), "player_stop");
+ new TDEAction(i18n("&Previous"), "media-skip-backward", Key_PageUp, this, SLOT(slotPrevious()), actionCollection(), "player_previous");
+ new TDEAction(i18n("&Next"), "media-skip-forward", Key_PageDown, this, SLOT(slotNext()), actionCollection(), "player_next");
+
+ // Important: we have a max of 1000 instead of 100 for better precision; multiply/divide your percentages by 10
+ m_position = new TQSlider(0, 1000, 10, 0, TQt::Horizontal, 0);
+ TQToolTip::add(m_position, i18n("Position"));
+ m_position->setTracking(false);
+ m_position->setFocusPolicy(TQ_NoFocus);
+ m_position->setMinimumWidth(100);
+ connect(m_position, SIGNAL(sliderPressed()), this, SLOT(slotStartSeeking()));
+ connect(m_position, SIGNAL(sliderMoved(int)), this, SLOT(slotSetSeekingPos(int)));
+ connect(m_position, SIGNAL(sliderReleased()), this, SLOT(slotStopSeeking()));
+ KWidgetAction *posAction = new KWidgetAction(m_position, i18n("Position"), 0, 0, 0, actionCollection(), "player_position");
+ posAction->setAutoSized(true);
+
+ m_playtime = new TQPushButton(0);
+ TQFontMetrics met(TDEGlobalSettings::generalFont());
+ m_playtime->setFixedWidth(met.width("-88:88:88") + 6);
+ m_playtime->setSizePolicy(TQSizePolicy (TQSizePolicy::Fixed, TQSizePolicy::Fixed));
+ m_playtime->setFocusPolicy(TQ_NoFocus);
+ new KWidgetAction(m_playtime, i18n("Playtime"), 0, 0, 0, actionCollection(), "player_playtime");
+
+ resetTime();
+}
+
+// Custom events dispatched from mpv event thread are handled here
+void MpvPart::customEvent(TQCustomEvent *event) {
+ if (event->type() == MPVPART_EVENT_PROPERTY_CHANGE) {
+ MpvPropertyChangeEvent *pe = (MpvPropertyChangeEvent *)event;
+ if (pe->property() == "time-pos" && pe->format() == MPV_FORMAT_DOUBLE) {
+ m_time = pe->toTime();
+ m_playtime->setText(m_time.toString());
+ }
+
+ else if (pe->property() == "percent-pos" && pe->format() == MPV_FORMAT_DOUBLE) {
+ if (!m_seeking) {
+ m_percent = pe->toDouble();
+ m_position->setValue(m_percent * 10);
+ }
+ }
+
+ else if (pe->property() == "duration" && pe->format() == MPV_FORMAT_DOUBLE) {
+ MRL mrl = m_playlist[m_current];
+ TQTime length = TQTime().addMSecs(pe->toDouble() * 1000);
+ if (!length.isNull()) {
+ mrl.setLength(length);
+ emit signalNewMeta(m_mrl);
+ }
+ }
+ }
+
+ else if (event->type() == MPVPART_EVENT_EOF) {
+ resetTime();
+
+ MpvEOFEvent *eofe = (MpvEOFEvent *)event;
+ if (eofe->reason() == MPV_END_FILE_REASON_ERROR) {
+ KMessageBox::detailedError(nullptr, i18n("Cannot play file."), eofe->error());
+ }
+ }
+}
+
+bool MpvPart::openURL(const MRL& mrl) {
+ /* Note: we do not use the internal playlist feature of mpv.
+ * Instead we rely on the playlist feature of Kaffeine.
+ */
+
+ if (!m_mpv) return false;
+
+ m_mrl = mrl;
+ m_playlist.clear();
+ m_current = 0;
+
+ /* Playlist handling code taken from gstreamer_part.cpp */
+ bool playlist = false;
+ TQString ext = m_mrl.kurl().fileName();
+ ext = ext.remove( 0 , ext.findRev('.')+1 ).lower();
+
+ if ( m_mrl.mime().isNull() ) {
+ KMimeType::Ptr mime = KMimeType::findByURL( m_mrl.kurl().path() );
+ m_mrl.setMime( mime->name() );
+ }
+
+ if ( (m_mrl.mime() == "text/plain") || (m_mrl.mime() == "text/xml") || (m_mrl.mime() == "application/x-kaffeine")
+ || (m_mrl.mime() == "audio/x-scpls") || (m_mrl.mime() == "audio/x-mpegurl") || (m_mrl.mime() == "audio/mpegurl")
+ || (ext == "asx") || (ext == "asf") || (ext == "wvx") || (ext == "wax") ) /* windows meta files */
+ {
+ kdDebug() << "libmpvpart: Check for kaffeine/noatun/m3u/pls/asx playlist\n";
+ TQString localFile;
+ if ( TDEIO::NetAccess::download(m_mrl.kurl(), localFile, widget()) ) {
+ TQFile file( localFile );
+ file.open( IO_ReadOnly );
+ TQTextStream stream( &file );
+ TQString firstLine = stream.readLine();
+ TQString secondLine = stream.readLine();
+ file.close();
+
+ if ( secondLine.contains("kaffeine", false) ) {
+ kdDebug() << "libmpvpart: Try loading kaffeine playlist\n";
+ playlist = PlaylistImport::kaffeine( localFile, m_playlist );
+ }
+ if ( secondLine.contains("noatun", false) ) {
+ kdDebug() << "libmpvpart: Try loading noatun playlist\n";
+ playlist = PlaylistImport::noatun( localFile, m_playlist);
+ }
+ if ( firstLine.contains("asx", false) ) {
+ kdDebug() << "libmpvpart: Try loading asx playlist\n";
+ playlist = PlaylistImport::asx( localFile, m_playlist );
+ }
+ if ( firstLine.contains("[playlist]", false) ) {
+ kdDebug() << "libmpvpart: Try loading pls playlist\n";
+ playlist = PlaylistImport::pls( localFile, m_playlist );
+ }
+ if (ext == "m3u" || ext == "m3u8") { //indentify by extension
+ kdDebug() << "libmpvpart: Try loading m3u playlist\n";
+ playlist = PlaylistImport::m3u( localFile, m_playlist );
+ }
+ }
+ else
+ kdError() << "libmpvpart: " << TDEIO::NetAccess::lastErrorString() << endl;
+ }
+ // check for ram playlist
+ if ( (ext == "ra") || (ext == "rm") || (ext == "ram") || (ext == "lsc") || (ext == "pl") ) {
+ kdDebug() << "libmpvpart: Try loading ram playlist\n";
+ playlist = PlaylistImport::ram( m_mrl, m_playlist, widget() );
+ }
+
+ if (!playlist)
+ {
+ kdDebug() << "libmpvpart: Got single track" << endl;
+ m_playlist.append( m_mrl );
+ }
+
+ slotPlay();
+ return true;
+}
+
+bool MpvPart::closeURL() {
+ if (!m_mpv) return false;
+
+ const char *args[] = {"playlist-remove", "current", nullptr};
+ mpv_command_async(m_mpv, 0, args);
+ return true;
+}
+
+void MpvPart::slotPlay() {
+ if (!m_mpv) return;
+
+ if (isPaused()) {
+ int value = 0;
+ mpv_set_property(m_mpv, "pause", MPV_FORMAT_FLAG, &value);
+ return;
+ }
+
+ if (m_playlist.count() > 0) {
+ emit setStatusBarText( i18n("Opening...") );
+ MRL curMRL = m_playlist[m_current];
+
+ const char *args[] = {"loadfile", curMRL.url().local8Bit(), nullptr};
+ mpv_command_async(m_mpv, 0, args);
+
+ // Add subtitles
+ if ((!curMRL.subtitleFiles().isEmpty()) && (curMRL.currentSubtitle() > -1)) {
+ kdDebug() << "adding subs" << endl;
+ const char *args[] = {"sub-add", curMRL.subtitleFiles()[curMRL.currentSubtitle()].local8Bit(), nullptr};
+ mpv_command_async(m_mpv, 0, args);
+ }
+
+ }
+ else {
+ emit signalRequestCurrentTrack();
+ }
+}
+
+void MpvPart::slotPause(bool pause) {
+ int value = pause ? 1 : 0;
+ mpv_set_property(m_mpv, "pause", MPV_FORMAT_FLAG, &value);
+}
+
+void MpvPart::slotTogglePause() {
+ if (!m_mpv) return;
+ slotPause(!isPaused());
+}
+
+bool MpvPart::isPlaying()
+{
+ if (!m_mpv) return false;
+
+ int result;
+ if (mpv_get_property(m_mpv, "core-idle", MPV_FORMAT_FLAG, &result) < 0) {
+ return false;
+ }
+ return !(bool)result;
+}
+
+bool MpvPart::isPaused() {
+ if (!m_mpv) return false;
+
+ int result;
+ if (mpv_get_property(m_mpv, "pause", MPV_FORMAT_FLAG, &result) < 0) {
+ return false;
+ }
+ return (bool)result;
+}
+
+void MpvPart::resetTime() {
+ m_playtime->setText("0:00:00");
+ m_position->setValue(0);
+}
+
+void MpvPart::slotStartSeeking() {
+ m_seeking = true;
+ slotPause(true);
+}
+
+void MpvPart::slotStopSeeking() {
+ slotSetPosition(m_percent);
+ slotPause(false);
+ m_seeking = false;
+}
+
+void MpvPart::slotSetSeekingPos(int pos) {
+ m_percent = (uint)pos / 10;
+}
+
+uint MpvPart::volume() const {
+ return 0;
+}
+
+uint MpvPart::position() const {
+ return m_percent;
+}
+
+void MpvPart::slotSetVolume(uint) {
+
+}
+
+void MpvPart::slotSetPosition(uint position) {
+ if (!m_mpv) return;
+
+ int64_t value = (int64_t)position;
+ mpv_set_property(m_mpv, "percent-pos", MPV_FORMAT_INT64, &value);
+}
+
+void MpvPart::slotPrevious() {
+ if ( m_current > 0 ) {
+ m_current--;
+ slotPlay();
+ }
+ else {
+ emit signalRequestPreviousTrack();
+ }
+}
+
+void MpvPart::slotNext() {
+ if ((m_playlist.count() > 0) && (m_current < m_playlist.count()-1)) {
+ m_current++;
+ slotPlay();
+ }
+ else {
+ emit signalRequestNextTrack();
+ }
+}
+
+void MpvPart::slotStop() {
+ if (isPlaying()) {
+ const char *args[] = {"stop", nullptr};
+ mpv_command_async(m_mpv, 0, args);
+ }
+}
+
+void MpvPart::slotMute() {
+
+}
+
+#include "libmpv_part.moc" \ No newline at end of file
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_part.desktop b/kaffeine/src/player-parts/libmpv-part/libmpv_part.desktop
new file mode 100644
index 0000000..9e89407
--- /dev/null
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Encoding=UTF-8
+Icon=mpv
+Name=Kaffeine-MPV
+Description=A Kaffeine engine based on libmpv
+MimeType=application/x-ogg;audio/basic;audio/vnd.rn-realaudio;audio/x-aiff;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-ogg;audio/x-pn-realaudio;audio/x-pn-realaudio-plugin;audio/x-scpls;audio/x-wav;audio/x-flac;video/x-matroska;audio/x-matroska;video/mpeg;video/msvideo;video/quicktime;video/vnd.rn-realvideo;video/x-avi;video/x-fli;video/x-flic;video/x-ms-asf;video/x-ms-wmv;video/x-msvideo;application/x-mplayer2;application/smil;application/x-kaffeine;audio/x-musepack;
+X-TDE-ServiceTypes=KParts/ReadOnlyPart,KaffeinePart
+Type=Service
+X-TDE-Library=libmpvpart
+X-TDE-InitialPreference=10 \ No newline at end of file
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_part.h b/kaffeine/src/player-parts/libmpv-part/libmpv_part.h
new file mode 100644
index 0000000..cb771f5
--- /dev/null
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.h
@@ -0,0 +1,116 @@
+/*
+ * Kaffeine libmpv part
+ * Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
+ *
+ * Based on Kaffeine dummy part
+ * Copyright (C) 2004-2005 Jürgen Kofler <kaffeine@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#ifndef __LIBMPVPART_H__
+#define __LIBMPVPART_H__
+
+// TQt
+#include <tqwidget.h>
+
+// TDE
+#include <tdeparts/factory.h>
+
+// Kaffeine
+#include "kaffeinepart.h"
+
+// libmpv
+#include <mpv/client.h>
+
+class TQSlider;
+class TQPushButton;
+class MpvEventThread;
+
+/**
+ * MpvPart -- A KaffeinePart which uses libmpv as backend
+ * @author Mavridis Philippe <mavridisf@gmail.com>
+ *
+ */
+
+class MpvPart : public KaffeinePart
+{
+ TQ_OBJECT
+
+ friend class MpvEventThread;
+
+ public:
+ MpvPart(TQWidget*, const char*, TQObject*, const char*, const TQStringList&);
+ virtual ~MpvPart();
+
+ /*
+ * Reimplement from KaffeinePart
+ */
+ uint volume() const; /* percent */
+ uint position() const; /* percent */
+ bool isPlaying();
+ bool isPaused();
+
+ bool closeURL();
+ static TDEAboutData* createAboutData();
+
+ public slots:
+ /*
+ * Reimplement from KaffeinePart
+ */
+ bool openURL(const MRL& mrl);
+ void slotPlay();
+ void slotPause(bool pause);
+ void slotTogglePause();
+ void slotSetVolume(uint); /* percent */
+ void slotSetPosition(uint); /* percent */
+ void slotStop();
+ void slotMute();
+ void slotPrevious();
+ void slotNext();
+
+ signals:
+ void mpvEvents();
+
+ protected:
+ mpv_handle *m_mpv;
+
+ private slots:
+ void slotStartSeeking();
+ void slotStopSeeking();
+ void slotSetSeekingPos(int pos);
+
+ private:
+ bool initMpv();
+ void initActions();
+ void resetTime();
+ void customEvent(TQCustomEvent *event);
+
+ private:
+ TQWidget *m_player;
+ MpvEventThread *m_eventThread;
+ TQSlider *m_position;
+ TQPushButton *m_playtime;
+
+ MRL m_mrl;
+ TQValueList<MRL> m_playlist;
+ uint m_current;
+
+ TQTime m_time;
+ uint m_percent;
+ bool m_seeking;
+};
+
+#endif /* __LIBMPVPART_H__ */
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc b/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc
new file mode 100644
index 0000000..21e765c
--- /dev/null
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc
@@ -0,0 +1,26 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="libmpv_part" version="1">
+<MenuBar>
+ <Menu name="player"><text>&amp;Player</text>
+ <Action name="player_play"/>
+ <Action name="player_pause"/>
+ <Action name="player_stop"/>
+ <Separator/>
+ <Action name="player_previous"/>
+ <Action name="player_next"/>
+ </Menu>
+</MenuBar>
+<ToolBar name="controls" position="Bottom"><text>Controls Toolbar</text>
+ <Action name="player_previous"/>
+ <Action name="player_play"/>
+ <Action name="player_pause"/>
+ <Action name="player_stop"/>
+ <Action name="player_next"/>
+</ToolBar>
+<ToolBar name="position" fullWidth="true" position="Bottom"><text>Position Toolbar</text>
+ <Action name="player_position"/>
+ <Separator/>
+ <Action name="player_playtime"/>
+</ToolBar>
+<StatusBar/>
+</kpartgui> \ No newline at end of file