mpvpart: lots of improvements

* Stream recording support (experimental in mpv)
* Rudimentary error handling mechanism
* State-based action control

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

@ -71,8 +71,14 @@ void MpvEventThread::processEvent(mpv_event *event) {
case MPV_EVENT_LOG_MESSAGE: {
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
kdDebug() << "[mpv " << msg->prefix << "] " << msg->level << ": "
kdDebug() << "[mpv " << msg->prefix << "] <" << msg->level << ">: "
<< msg->text << endl;
m_part->setStatusBarText(TQString("MPV %1 (%2): %3").arg(msg->prefix, msg->level, msg->text));
if (TQString(msg->level) == "error") {
processErrorMessage(msg->prefix, msg->level, msg->text);
}
break;
}
@ -88,4 +94,16 @@ void MpvEventThread::processEvent(mpv_event *event) {
default: break; // ignore other events
}
}
void MpvEventThread::processErrorMessage(TQString prefix, TQString level, TQString text) {
if (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();
}
}

@ -30,6 +30,7 @@
#define MPVPART_EVENT_PROPERTY_CHANGE 65890
#define MPVPART_EVENT_EOF 65891
#define MPVPART_EVENT_ERROR 65892
class MpvPart;
@ -67,6 +68,20 @@ class MpvEOFEvent : public TQCustomEvent {
TQString _error;
};
class MpvErrorEvent : public TQCustomEvent {
public:
MpvErrorEvent(TQString text, TQString caption, TQString details = TQString::null)
: TQCustomEvent(MPVPART_EVENT_ERROR), _text(text), _caption(caption), _details(details)
{}
TQString text() { return _text; }
TQString caption() { return _caption; }
TQString details() { return _details; }
private:
TQString _text, _caption, _details;
};
class MpvEventThread : public TQThread {
public:
MpvEventThread(MpvPart *part);
@ -75,6 +90,7 @@ class MpvEventThread : public TQThread {
private:
void initPropertyObservers();
void processEvent(mpv_event *event);
void processErrorMessage(TQString prefix, TQString level, TQString text);
private:
MpvPart *m_part;

@ -22,6 +22,7 @@
// TQt
#include <tqtooltip.h>
#include <tqlabel.h>
#include <tqslider.h>
#include <tqpushbutton.h>
#include <tqfile.h>
@ -30,6 +31,8 @@
#include <tdeparts/genericfactory.h>
#include <tdeglobalsettings.h>
#include <tdeio/netaccess.h>
#include <tdemessagebox.h>
#include <tdefiledialog.h>
#include <tdeaction.h>
#include <kmimetype.h>
#include <tdemessagebox.h>
@ -50,7 +53,8 @@ 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)
m_seeking(false),
m_recordFilePath()
{
// Create an instance of this class
setInstance(MpvPartFactory::instance());
@ -153,9 +157,25 @@ void MpvPart::initActions()
m_volume = new TQSlider(0, 100, 10, 100, TQt::Horizontal, 0);
connect(m_volume, SIGNAL(sliderMoved(int)), this, SLOT(slotSetVolume(int)));
KWidgetAction *volAction = new KWidgetAction(m_volume, i18n("Volume"), 0, 0, 0, actionCollection(), "player_volume");
new KWidgetAction(m_volume, i18n("Volume"), 0, 0, 0, actionCollection(), "player_volume");
/*** Stream recording toolbar ***/
m_recordAction = new TDEToggleAction(i18n("&Record stream"), "player_record", Key_R, this, SLOT(slotToggleRecording()), actionCollection(), "record_toggle");
new TDEAction(i18n("Set recording file"), "document-open", 0, this, SLOT(slotSetRecordingFile()), actionCollection(), "record_open");
m_recordFile = new TQLabel(0);
new KWidgetAction(m_recordFile, i18n("Recording file"), 0, 0, 0, actionCollection(), "record_file");
updateRecordFileLabel();
resetTime();
stateChanged("disable_all");
}
void MpvPart::showError(TQString text, TQString caption) {
KMessageBox::sorry(0, text, caption);
}
void MpvPart::showDetailedError(TQString text, TQString details, TQString caption) {
KMessageBox::detailedSorry(0, text, details, caption);
}
// Custom events dispatched from mpv event thread are handled here
@ -192,6 +212,18 @@ void MpvPart::customEvent(TQCustomEvent *event) {
KMessageBox::detailedError(nullptr, i18n("Cannot play file."), eofe->error());
}
}
else if (event->type() == MPVPART_EVENT_ERROR) {
slotPause(true);
MpvErrorEvent *ee = (MpvErrorEvent *)event;
if (ee->details().isEmpty()) {
showError(ee->text(), ee->caption());
}
else {
showDetailedError(ee->text(), ee->details(), ee->caption());
}
}
}
bool MpvPart::openURL(const MRL& mrl) {
@ -304,11 +336,14 @@ void MpvPart::slotPlay() {
else {
emit signalRequestCurrentTrack();
}
stateChanged(isStream() ? "playing_stream" : "playing_file");
}
void MpvPart::slotPause(bool pause) {
int value = pause ? 1 : 0;
mpv_set_property(m_mpv, "pause", MPV_FORMAT_FLAG, &value);
stateChanged(pause ? "paused" : (isStream() ? "playing_stream" : "playing_file"));
}
void MpvPart::slotTogglePause() {
@ -347,6 +382,14 @@ bool MpvPart::isMute() {
return (bool)result;
}
bool MpvPart::isStream() {
TQString proto = m_mrl.kurl().protocol();
if (proto.startsWith("http") || proto.startsWith("rtsp")) {
return true;
}
return false;
}
void MpvPart::resetTime() {
m_playtime->setText("0:00:00");
m_position->setValue(0);
@ -415,6 +458,7 @@ void MpvPart::slotStop() {
if (isPlaying()) {
const char *args[] = {"stop", nullptr};
mpv_command_async(m_mpv, 0, args);
stateChanged("not_playing");
}
}
@ -423,4 +467,64 @@ void MpvPart::slotMute() {
mpv_set_property(m_mpv, "ao-mute", MPV_FORMAT_FLAG, &value);
}
void MpvPart::slotToggleRecording() {
if (m_recordAction->isChecked()) {
startRecording();
}
else {
stopRecording();
}
}
void MpvPart::startRecording() {
// Ensure we have an out file and start recording
if (m_recordFilePath.isEmpty()) {
slotSetRecordingFile();
}
if (m_recordFilePath.isEmpty()) {
m_recordAction->setChecked(false);
return;
}
mpv_set_property_string(m_mpv, "stream-record", m_recordFilePath.local8Bit());
m_recordAction->setChecked(true);
}
void MpvPart::stopRecording() {
mpv_set_property_string(m_mpv, "stream-record", "");
m_recordAction->setChecked(false);
}
void MpvPart::slotSetRecordingFile() {
if (m_recordAction->isChecked() && !m_recordFilePath.isEmpty()) {
if (!KMessageBox::warningContinueCancel(0,
i18n("Changing the recording file will stop the recording process."),
i18n("Recording in progress")) == KMessageBox::Continue)
{
return;
}
}
m_recordFilePath = KFileDialog::getSaveFileName(
TQString::null,
"*.mkv|" + i18n("Matroska file (*.mkv)") + "\n" +
"*.ogg|" + i18n("OGG media (*.ogg)") + "\n" +
"*.mp4|" + i18n("MPEG-4 video (*.mp4)") + "\n" +
"*.mpeg|" + i18n("MPEG video (*.mpeg)") + "\n" +
"*.avi|" + i18n("Microsoft AVI video (*.avi)") + "\n" +
"*.mp3|" + i18n("MPEG Layer 3 audio (*.mp3)") + "\n" +
"*.mp2|" + i18n("MPEG Layer 2 audio (*.mp2)") + "\n" +
"*.flac|" + i18n("FLAC audio (*.flac)") + "\n",
0, i18n("Select file name for saved recording"));
updateRecordFileLabel();
}
void MpvPart::updateRecordFileLabel() {
m_recordFile->setText(
m_recordFilePath.isEmpty()
? i18n("No output file")
: m_recordFilePath
);
}
#include "libmpv_part.moc"

@ -35,8 +35,10 @@
// libmpv
#include <mpv/client.h>
class TQLabel;
class TQSlider;
class TQPushButton;
class TDEToggleAction;
class MpvEventThread;
/**
@ -63,10 +65,14 @@ class MpvPart : public KaffeinePart
bool isPlaying();
bool isPaused();
bool isMute();
bool isStream();
bool closeURL();
static TDEAboutData* createAboutData();
void showError(TQString text, TQString caption);
void showDetailedError(TQString text, TQString details, TQString caption);
public slots:
/*
* Reimplement from KaffeinePart
@ -81,6 +87,10 @@ class MpvPart : public KaffeinePart
void slotMute();
void slotPrevious();
void slotNext();
void slotToggleRecording();
void startRecording();
void stopRecording();
void slotSetRecordingFile();
signals:
void mpvEvents();
@ -102,13 +112,16 @@ class MpvPart : public KaffeinePart
void initActions();
void resetTime();
void customEvent(TQCustomEvent *event);
void updateRecordFileLabel();
private:
TDEToggleAction *m_recordAction;
TQWidget *m_player;
MpvEventThread *m_eventThread;
TQSlider *m_position;
TQSlider *m_volume;
TQPushButton *m_playtime;
TQLabel *m_recordFile;
MRL m_mrl;
TQValueList<MRL> m_playlist;
@ -117,6 +130,7 @@ class MpvPart : public KaffeinePart
TQTime m_time;
uint m_percent;
bool m_seeking;
TQString m_recordFilePath;
};
#endif /* __LIBMPVPART_H__ */

@ -8,8 +8,11 @@
<Separator/>
<Action name="player_previous"/>
<Action name="player_next"/>
<Separator/>
<Action name="record_toggle"/>
</Menu>
</MenuBar>
<ToolBar name="controls" position="Bottom"><text>Playback Controls Toolbar</text>
<Action name="player_previous"/>
<Action name="player_play"/>
@ -27,4 +30,119 @@
<Action name="player_volume"/>
<Separator/>
</ToolBar>
<ToolBar name="record"><text>Stream Recorder Toolbar</text>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
</ToolBar>
<State name="disable_all">
<disable>
<Action name="player_previous"/>
<Action name="player_pause"/>
<Action name="player_stop"/>
<Action name="player_next"/>
<Action name="player_position"/>
<Action name="player_playtime"/>
<Action name="player_mute"/>
<Action name="player_volume"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
</disable>
<enable>
<Action name="player_play"/>
</enable>
</State>
<State name="not_playing">
<disable>
<Action name="player_pause"/>
<Action name="player_stop"/>
<Action name="player_position"/>
<Action name="player_playtime"/>
<Action name="player_mute"/>
<Action name="player_volume"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
</disable>
<enable>
<Action name="player_play"/>
<Action name="player_previous"/>
<Action name="player_next"/>
</enable>
</State>
<State name="paused">
<disable>
<Action name="player_pause"/>
</disable>
<enable>
<Action name="player_previous"/>
<Action name="player_play"/>
<Action name="player_stop"/>
<Action name="player_next"/>
<Action name="player_position"/>
<Action name="player_playtime"/>
<Action name="player_mute"/>
<Action name="player_volume"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
</enable>
</State>
<State name="playing_file">
<disable>
<Action name="player_play"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
</disable>
<enable>
<Action name="player_previous"/>
<Action name="player_pause"/>
<Action name="player_stop"/>
<Action name="player_next"/>
<Action name="player_position"/>
<Action name="player_playtime"/>
<Action name="player_mute"/>
<Action name="player_volume"/>
</enable>
</State>
<State name="playing_stream">
<disable>
<Action name="player_play"/>
</disable>
<enable>
<Action name="player_previous"/>
<Action name="player_pause"/>
<Action name="player_stop"/>
<Action name="player_next"/>
<Action name="player_position"/>
<Action name="player_playtime"/>
<Action name="player_mute"/>
<Action name="player_volume"/>
<Action name="record_toggle"/>
<Action name="record_open"/>
<Action name="record_file"/>
</enable>
</State>
</kpartgui>
Loading…
Cancel
Save