diff --git a/kaffeine/src/player-parts/libmpv-part/CMakeLists.txt b/kaffeine/src/player-parts/libmpv-part/CMakeLists.txt index 4a903ae..d38eb89 100644 --- a/kaffeine/src/player-parts/libmpv-part/CMakeLists.txt +++ b/kaffeine/src/player-parts/libmpv-part/CMakeLists.txt @@ -23,6 +23,7 @@ link_directories( tde_add_kpart( libmpvpart AUTOMOC SOURCES libmpv_part.cpp libmpv_event.cpp libmpv_widget.cpp + libmpv_log.cpp libmpv_errordlg.cpp LINK tdecore-shared tdeui-shared diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_errordlg.cpp b/kaffeine/src/player-parts/libmpv-part/libmpv_errordlg.cpp new file mode 100644 index 0000000..7f620b0 --- /dev/null +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_errordlg.cpp @@ -0,0 +1,113 @@ +/* + * Kaffeine libmpv part + * Copyright (C) 2023 Mavridis Philippe + * + * 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 + +// TDE +#include +#include +#include + +// Part +#include "libmpv_part.h" +#include "libmpv_errordlg.h" + +/******************************/ +/***** Error Dialog *****/ +/******************************/ +MpvErrorDlg::MpvErrorDlg(MpvPart *part) + : KDialogBase(0, 0, true, i18n("Error"), + KDialogBase::User1 | KDialogBase::User2 | KDialogBase::User3, + KDialogBase::User1, false, + KGuiItem(i18n("Ignore"), "media-playback-start", i18n("Ignore error and continue playback")), + KGuiItem(i18n("Stop"), "media-playback-stop", i18n("Stop playback")), + KGuiItem(i18n("View Log"), "text-x-log", i18n("Pause playback and view event log"))), + m_part(part) +{ + TQHBox *hbox = makeHBoxMainWidget(); + + TQLabel *errorPix = new TQLabel(hbox); + errorPix->setPixmap(TDEGlobal::iconLoader()->loadIcon("messagebox_warning", + TDEIcon::NoGroup, TDEIcon::SizeMedium, TDEIcon::DefaultState)); + + TQLabel *errorMsg = new TQLabel(i18n("An error has occurred, but Kaffeine can continue playing the current file.\n" + "You can view the event log to check for details."), hbox); + errorMsg->setAlignment(TQt::AlignLeft | TQt::AlignTop); + + m_part->slotPause(true); +} + +MpvErrorDlg::~MpvErrorDlg() { +} + +/*** Ignore error and continue playback ***/ +void MpvErrorDlg::slotUser1() { + m_part->slotPlay(); + accept(); +} + +/*** Stop playback ***/ +void MpvErrorDlg::slotUser2() { + m_part->slotStop(); + accept(); +} + +/*** Pause playback and view event log ***/ +void MpvErrorDlg::slotUser3() { + m_part->slotViewLog(); + accept(); +} +/******************************/ +/***** Fatal Error Dialog *****/ +/******************************/ +MpvFatalErrorDlg::MpvFatalErrorDlg(MpvPart *part) + : KDialogBase(0, 0, true, i18n("Fatal Error"), + KDialogBase::User1 | KDialogBase::User2, + KDialogBase::User1, false, + KGuiItem(i18n("Stop"), "media-playback-stop", i18n("Stop playback")), + KGuiItem(i18n("View Log"), "text-x-log", i18n("Stop playback and view event log"))), + m_part(part) +{ + TQHBox *hbox = makeHBoxMainWidget(); + + TQLabel *errorPix = new TQLabel(hbox); + errorPix->setPixmap(TDEGlobal::iconLoader()->loadIcon("messagebox_critical", + TDEIcon::NoGroup, TDEIcon::SizeMedium, TDEIcon::DefaultState)); + + TQLabel *errorMsg = new TQLabel(i18n("A fatal error has occurred and Kaffeine cannot continue playback."), hbox); + errorMsg->setAlignment(TQt::AlignLeft | TQt::AlignTop); + + m_part->slotStop(); +} + +MpvFatalErrorDlg::~MpvFatalErrorDlg() { +} + +/*** Stop playback ***/ +void MpvFatalErrorDlg::slotUser1() { + accept(); // already stopped +} + +/*** Stop playback and view event log ***/ +void MpvFatalErrorDlg::slotUser2() { + m_part->slotViewLog(); + accept(); // already stopped +} \ No newline at end of file diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_errordlg.h b/kaffeine/src/player-parts/libmpv-part/libmpv_errordlg.h new file mode 100644 index 0000000..f16189f --- /dev/null +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_errordlg.h @@ -0,0 +1,55 @@ +/* + * Kaffeine libmpv part + * Copyright (C) 2023 Mavridis Philippe + * + * 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 __LIBMPVERRORDLG_H__ +#define __LIBMPVERRORDLG_H__ + +// TDE +#include + +class MpvPart; + +class MpvErrorDlg : public KDialogBase { + public: + MpvErrorDlg(MpvPart *part); + ~MpvErrorDlg(); + + protected slots: + void slotUser1(); // ignore + void slotUser2(); // stop + void slotUser3(); // view log + + private: + MpvPart *m_part; +}; + +class MpvFatalErrorDlg : public KDialogBase { + public: + MpvFatalErrorDlg(MpvPart *part); + ~MpvFatalErrorDlg(); + + protected slots: + void slotUser1(); // stop + void slotUser2(); // view log + + private: + MpvPart *m_part; +}; + +#endif // __LIBMPVERRORDLG_H__ \ No newline at end of file diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp b/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp index f328eac..04b6fdb 100644 --- a/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp @@ -96,23 +96,12 @@ void MpvEventThread::processEvent(mpv_event *event) { } void MpvEventThread::processMessage(TQString prefix, TQString level, TQString text) { - // Stream recorder errors - if (level == "error" && prefix == "recorder") { - kdDebug() << "recorder error:" << text << endl; - MpvErrorEvent *ee = new MpvErrorEvent( - i18n("An error occurred in the stream recorder."), - i18n("Recording error"), text - ); - TQApplication::postEvent(m_part, ee); - m_part->stopRecording(); - } - // HACK: click-through for the context menu // MPV does not yet natively support click-through and thus eats up all mouse // events on its embedded window (https://github.com/mpv-player/mpv/issues/8938). // Moreover, there seems to be no way to natively listen for keyboard/mouse events. // This is a nasty workaround. - else if (level == "warn" && prefix == "input" && text.contains("MBTN_")) { + if (level == "warn" && prefix == "input" && text.contains("MBTN_")) { int button = TQt::NoButton; if (text.contains("MBTN_LEFT")) { button = TQt::LeftButton; @@ -144,4 +133,25 @@ void MpvEventThread::processMessage(TQString prefix, TQString level, TQString te TQApplication::postEvent(m_part->m_player, cme); } } + + else { + // Ignore X11 errors + if (level == "error" && prefix == "vo/gpu/x11") { + return; + } + + // Log event + struct MpvEventData eventData = { TQDateTime::currentDateTime(), prefix, level, text }; + m_part->logEvent(eventData); + + // If it's an error, inform the user and optionally do stuff + if (level == "error" || level == "fatal") { + MpvErrorEvent *ee = new MpvErrorEvent(level == "fatal"); + TQApplication::postEvent(m_part, ee); + + if (prefix == "recorder") { + m_part->stopRecording(); + } + } + } } \ 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 index 1df5629..3c63198 100644 --- a/kaffeine/src/player-parts/libmpv-part/libmpv_event.h +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_event.h @@ -35,6 +35,13 @@ #define MPVPART_EVENT_EOF 65891 #define MPVPART_EVENT_ERROR 65892 +struct MpvEventData { + TQDateTime timestamp; + TQString prefix; + TQString level; + TQString text; +}; + class MpvPart; class MpvPropertyChangeEvent : public TQCustomEvent { @@ -73,16 +80,13 @@ class MpvEOFEvent : public TQCustomEvent { class MpvErrorEvent : public TQCustomEvent { public: - MpvErrorEvent(TQString text, TQString caption, TQString details = TQString::null) - : TQCustomEvent(MPVPART_EVENT_ERROR), _text(text), _caption(caption), _details(details) - {} + MpvErrorEvent(bool fatal) : TQCustomEvent(MPVPART_EVENT_ERROR), + _fatal(fatal) {} - TQString text() { return _text; } - TQString caption() { return _caption; } - TQString details() { return _details; } + bool fatal() { return _fatal; } private: - TQString _text, _caption, _details; + bool _fatal; }; class MpvEventThread : public TQThread { diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_log.cpp b/kaffeine/src/player-parts/libmpv-part/libmpv_log.cpp new file mode 100644 index 0000000..263058f --- /dev/null +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_log.cpp @@ -0,0 +1,145 @@ +/* + * Kaffeine libmpv part + * Copyright (C) 2023 Mavridis Philippe + * + * 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 + +// Part +#include "libmpv_event.h" +#include "libmpv_part.h" +#include "libmpv_log.h" + +MpvLogViewerDlg::MpvLogViewerDlg(MpvPart *part) + : KDialogBase(0, 0, false, i18n("MPV log"), + KDialogBase::User1 | KDialogBase::User2 | KDialogBase::User3 | KDialogBase::Close, + KDialogBase::Close, false, + KGuiItem(i18n("Clear"), "clear_left", "Clear MPV event log"), + KGuiItem(i18n("Refresh"), "reload", "Refresh MPV event log"), + KGuiItem(i18n("Copy"), "edit-copy", "Copy selected line")), + m_part(part) +{ + m_logView = new TQListView(this); + m_logView->addColumn("Level"); + m_logView->addColumn("Timestamp"); + m_logView->addColumn("Prefix"); + m_logView->addColumn("Message"); + m_logView->setResizeMode(TQListView::LastColumn); + m_logView->setSorting(1); + m_logView->setAllColumnsShowFocus(true); + setMainWidget(m_logView); + resize(500, 250); + + refreshLog(); +} + +MpvLogViewerDlg::~MpvLogViewerDlg() { + delete m_logView; +} + +void MpvLogViewerDlg::refreshLog() { + TDEIconLoader *icoLoad = TDEGlobal::iconLoader(); + + m_logView->clear(); + + TQValueList::iterator it; + for (it = m_part->m_eventLog.begin(); + it != m_part->m_eventLog.end(); + ++it) + { + TQString timestamp ((*it).timestamp.toString()), + level ((*it).level), + prefix ((*it).prefix), + message ((*it).text.replace("\n", "")); + + TQListViewItem *li = new TQListViewItem(m_logView, level, timestamp, + prefix, message); + + TQString icon; + if (level == "error" || level == "fatal") { + icon = "messagebox_critical"; + } + + else if (level == "warn") { + icon = "messagebox_warning"; + } + + else { + icon = "messagebox_info"; + } + + li->setPixmap(0, icoLoad->loadIcon(icon, TDEIcon::Small)); + } +} + +/*** Clear ***/ +void MpvLogViewerDlg::slotUser1() { + int ret = KMessageBox::questionYesNo(this, + i18n("Would you like to clear the MPV log?"), + i18n("Clear log?")); + + if (ret == KMessageBox::Yes) { + m_part->m_eventLog.clear(); + refreshLog(); + } +} + +/*** Refresh ***/ +void MpvLogViewerDlg::slotUser2() { + refreshLog(); +} + +/*** Copy selection ***/ +void MpvLogViewerDlg::slotUser3() { + TQClipboard *clip = TQApplication::clipboard(); + TQString logLineTemplate = TQString("[%1] (%2 %3) %4"); + + TQListViewItem *sel = m_logView->selectedItem(); + if (!sel) { + int ret = KMessageBox::warningYesNo(this, + i18n("Would you like to copy the whole MPV log?"), + i18n("Copy whole log?")); + + if (ret != KMessageBox::Yes) { + return; + } + } + + TQString out; + TQTextStream ts(&out, IO_WriteOnly); + TQListViewItemIterator it(m_logView); + while (it.current()) { + if (!sel || it.current()->isSelected()) { + ts << logLineTemplate.arg( + (*it)->text(1), (*it)->text(0), + (*it)->text(2), (*it)->text(3)) + << endl; + } + ++it; + } + clip->setText(out); +} \ No newline at end of file diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_log.h b/kaffeine/src/player-parts/libmpv-part/libmpv_log.h new file mode 100644 index 0000000..8fe8f4a --- /dev/null +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_log.h @@ -0,0 +1,47 @@ +/* + * Kaffeine libmpv part + * Copyright (C) 2023 Mavridis Philippe + * + * 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 __LIBMPVLOG_H__ +#define __LIBMPVLOG_H__ + +// TDE +#include + +class MpvPart; +class TQListView; + +class MpvLogViewerDlg : public KDialogBase { + public: + MpvLogViewerDlg(MpvPart *part); + ~MpvLogViewerDlg(); + + protected slots: + void slotUser1(); // clear + void slotUser2(); // refresh + void slotUser3(); // copy selection + + private: + void refreshLog(); + + private: + MpvPart *m_part; + TQListView *m_logView; +}; + +#endif // __LIBMPVLOG_H__ \ 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 index 137497b..a2fdc16 100644 --- a/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp @@ -29,6 +29,7 @@ #include #include #include +#include // TDE #include @@ -42,6 +43,8 @@ #include #include #include +#include +#include #include // Kaffeine @@ -51,6 +54,8 @@ #include "libmpv_part.h" #include "libmpv_event.h" #include "libmpv_widget.h" +#include "libmpv_log.h" +#include "libmpv_errordlg.h" typedef KParts::GenericFactory MpvPartFactory; K_EXPORT_COMPONENT_FACTORY (libmpvpart, MpvPartFactory); @@ -60,7 +65,9 @@ MpvPart::MpvPart(TQWidget* parentWidget, const char* widgetName, TQObject* paren m_current(0), m_seeking(false), m_recordFilePath(), - m_context(nullptr) + m_context(nullptr), + m_error(nullptr), + m_criticalError(nullptr) { // Create an instance of this class setInstance(MpvPartFactory::instance()); @@ -178,16 +185,20 @@ void MpvPart::initActions() /*** Context menu ***/ new TDEAction(i18n("Add subtitles"), "text-x-generic", 0, this, SLOT(slotAddSubtitles()), actionCollection(), "subtitles_add"); + /*** Other ***/ + new TDEAction(i18n("View MPV log"), "text-x-log", 0, this, SLOT(slotViewLog()), actionCollection(), "view_log"); + resetTime(); stateChanged("disable_all"); } -void MpvPart::showError(TQString text, TQString caption) { - KMessageBox::sorry(0, text, caption); +void MpvPart::logEvent(struct MpvEventData eventData) { + m_eventLog.append(eventData); } -void MpvPart::showDetailedError(TQString text, TQString details, TQString caption) { - KMessageBox::detailedSorry(0, text, details, caption); +void MpvPart::slotViewLog() { + MpvLogViewerDlg *logViewer = new MpvLogViewerDlg(this); + logViewer->exec(); } // Custom events dispatched from mpv event thread are handled here @@ -226,14 +237,38 @@ void MpvPart::customEvent(TQCustomEvent *event) { } else if (event->type() == MPVPART_EVENT_ERROR) { - slotPause(true); - MpvErrorEvent *ee = (MpvErrorEvent *)event; - if (ee->details().isEmpty()) { - showError(ee->text(), ee->caption()); + + KDialogBase *edlg; + if (ee->fatal()) { + if (m_criticalError) { + return; + } + + if (m_error) { + m_error->close(); + } + + edlg = new MpvFatalErrorDlg(this); + m_criticalError = edlg; + } + else { + if (m_error || m_criticalError) { + return; + } + + edlg = new MpvErrorDlg(this); + m_error = edlg; + } + + edlg->exec(); + + delete edlg; + if (ee->fatal()) { + m_criticalError = nullptr; } else { - showDetailedError(ee->text(), ee->details(), ee->caption()); + m_error = nullptr; } } } @@ -328,12 +363,16 @@ bool MpvPart::closeURL() { void MpvPart::slotPlay() { if (!m_mpv) return; - kdDebug() << "m_playlist count: " << m_playlist.count() << endl; + if (isPaused()) { + slotPause(false); + if (!m_mrl.url().isEmpty()) { + return; + } + } if (m_playlist.count() > 0) { emit setStatusBarText( i18n("Opening...") ); MRL curMRL = m_playlist[m_current]; - kdDebug() << "current: " << curMRL.url() << endl; const char *args[] = {"loadfile", curMRL.url().local8Bit(), nullptr}; mpv_command_async(m_mpv, 0, args); @@ -344,10 +383,6 @@ void MpvPart::slotPlay() { emit signalRequestCurrentTrack(); } - if (isPaused()) { - slotPause(false); - } - stateChanged(isStream() ? "playing_stream" : "playing_file"); } diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_part.h b/kaffeine/src/player-parts/libmpv-part/libmpv_part.h index cc6f496..76463f5 100644 --- a/kaffeine/src/player-parts/libmpv-part/libmpv_part.h +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.h @@ -40,6 +40,7 @@ class TQSlider; class TQPopupMenu; class TQPushButton; class TDEToggleAction; +class KDialogBase; class MpvEventThread; class MpvContainerWidget; @@ -55,6 +56,7 @@ class MpvPart : public KaffeinePart friend class MpvEventThread; friend class MpvContainerWidget; + friend class MpvLogViewerDlg; public: MpvPart(TQWidget*, const char*, TQObject*, const char*, const TQStringList&); @@ -105,6 +107,8 @@ class MpvPart : public KaffeinePart void showContextMenu(); void hideContextMenu(); + void slotViewLog(); + signals: void mpvEvents(); @@ -128,6 +132,7 @@ class MpvPart : public KaffeinePart void resetTime(); void customEvent(TQCustomEvent *event); void updateRecordFileLabel(); + void logEvent(struct MpvEventData eventData); private: TDEToggleAction *m_recordAction; @@ -140,6 +145,9 @@ class MpvPart : public KaffeinePart TQPopupMenu *m_context; TQPopupMenu *m_subs; TQString m_logoPath; + TQValueList m_eventLog; + KDialogBase *m_error; + KDialogBase *m_criticalError; TQValueList m_playlist; uint m_current; diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc b/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc index 046f732..9d75952 100644 --- a/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc +++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc @@ -1,6 +1,9 @@ + + + &Player