mpvpart: added log viewer and improved error handling

Signed-off-by: Mavridis Philippe <mavridisf@gmail.com>
feat/libmpv-backend
Mavridis Philippe 12 months ago
parent a629f68164
commit f9aeaa2575
No known key found for this signature in database
GPG Key ID: 93F66F98F906147D

@ -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

@ -0,0 +1,113 @@
/*
* Kaffeine libmpv part
* Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
*
* 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 <tqhbox.h>
#include <tqlabel.h>
// TDE
#include <kiconloader.h>
#include <tdeglobal.h>
#include <tdelocale.h>
// 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
}

@ -0,0 +1,55 @@
/*
* Kaffeine libmpv part
* Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
*
* 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 <kdialogbase.h>
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__

@ -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();
}
}
}
}

@ -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 {

@ -0,0 +1,145 @@
/*
* Kaffeine libmpv part
* Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
*
* 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>
#include <tqclipboard.h>
#include <tqlistview.h>
#include <tqtextstream.h>
// TDE
#include <tdemessagebox.h>
#include <kiconloader.h>
#include <tdeglobal.h>
#include <tdelocale.h>
// 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<struct MpvEventData>::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);
}

@ -0,0 +1,47 @@
/*
* Kaffeine libmpv part
* Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
*
* 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 <kdialogbase.h>
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__

@ -29,6 +29,7 @@
#include <tqstringlist.h>
#include <tqpopupmenu.h>
#include <tqcursor.h>
#include <tqhbox.h>
// TDE
#include <tdeparts/genericfactory.h>
@ -42,6 +43,8 @@
#include <tdemessagebox.h>
#include <kxmlguifactory.h>
#include <tdetoolbar.h>
#include <tdeglobal.h>
#include <kiconloader.h>
#include <kdebug.h>
// 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<MpvPart> 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");
}

@ -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<struct MpvEventData> m_eventLog;
KDialogBase *m_error;
KDialogBase *m_criticalError;
TQValueList<MRL> m_playlist;
uint m_current;

@ -1,6 +1,9 @@
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="libmpv_part" version="1">
<MenuBar>
<Menu name="file">
<Action name="view_log"/>
</Menu>
<Menu name="player"><text>&amp;Player</text>
<Action name="player_play"/>
<Action name="player_pause"/>

Loading…
Cancel
Save