From 54545a0913972d357d3c2dfcb5e446bac674faf1 Mon Sep 17 00:00:00 2001 From: Mavridis Philippe Date: Wed, 17 May 2023 15:33:02 +0300 Subject: [PATCH] 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 --- CMakeLists.txt | 1 + ConfigureChecks.cmake | 10 + kaffeine/src/player-parts/CMakeLists.txt | 4 + .../player-parts/libmpv-part/CMakeLists.txt | 50 +++ .../player-parts/libmpv-part/libmpv_event.cpp | 91 ++++ .../player-parts/libmpv-part/libmpv_event.h | 81 ++++ .../player-parts/libmpv-part/libmpv_part.cpp | 401 ++++++++++++++++++ .../libmpv-part/libmpv_part.desktop | 10 + .../player-parts/libmpv-part/libmpv_part.h | 116 +++++ .../player-parts/libmpv-part/libmpv_part.rc | 26 ++ 10 files changed, 790 insertions(+) create mode 100644 kaffeine/src/player-parts/libmpv-part/CMakeLists.txt create mode 100644 kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp create mode 100644 kaffeine/src/player-parts/libmpv-part/libmpv_event.h create mode 100644 kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp create mode 100644 kaffeine/src/player-parts/libmpv-part/libmpv_part.desktop create mode 100644 kaffeine/src/player-parts/libmpv-part/libmpv_part.h create mode 100644 kaffeine/src/player-parts/libmpv-part/libmpv_part.rc diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e07784..49056b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,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 + * + * Based on Kaffeine dummy part + * Copyright (C) 2004-2005 Jürgen Kofler + * + * 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 + +// TDE +#include +#include + +// 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 + * + * Based on Kaffeine dummy part + * Copyright (C) 2004-2005 Jürgen Kofler + * + * 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 +#include +#include + +// libmpv +#include + +#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 + * + * Based on Kaffeine dummy part + * Copyright (C) 2004-2005 Jürgen Kofler + * + * 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 +#include +#include +#include + +// TDE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Kaffeine +#include "playlistimport.h" + +// Part +#include "libmpv_part.h" +#include "libmpv_event.h" + +typedef KParts::GenericFactory 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(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 + * + * Based on Kaffeine dummy part + * Copyright (C) 2004-2005 Jürgen Kofler + * + * 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 + +// TDE +#include + +// Kaffeine +#include "kaffeinepart.h" + +// libmpv +#include + +class TQSlider; +class TQPushButton; +class MpvEventThread; + +/** + * MpvPart -- A KaffeinePart which uses libmpv as backend + * @author Mavridis Philippe + * + */ + +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 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 @@ + + + + &Player + + + + + + + + +Controls Toolbar + + + + + + +Position Toolbar + + + + + + \ No newline at end of file