summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMavridis Philippe <[email protected]>2023-05-24 16:42:35 +0300
committerMavridis Philippe <[email protected]>2024-09-08 01:41:03 +0300
commit62ee1ff78bb965de66f02dea87fb5821f1b3fd05 (patch)
tree924a36730f5405ec61f333368981b49ed7dbc095
parent2ae5b7210a4654089c902d93232a53d7eccc12f8 (diff)
downloadkaffeine-62ee1ff78bb965de66f02dea87fb5821f1b3fd05.tar.gz
kaffeine-62ee1ff78bb965de66f02dea87fb5821f1b3fd05.zip
mpvpart: lots of improvements
* Stream recording support (experimental in mpv) * Rudimentary error handling mechanism * State-based action control Signed-off-by: Mavridis Philippe <[email protected]>
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp20
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_event.h16
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp108
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_part.h14
-rw-r--r--kaffeine/src/player-parts/libmpv-part/libmpv_part.rc118
5 files changed, 273 insertions, 3 deletions
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp b/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp
index 189b6fe..f6edbec 100644
--- a/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_event.cpp
@@ -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();
+ }
} \ 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 b13bad2..477f191 100644
--- a/kaffeine/src/player-parts/libmpv-part/libmpv_event.h
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_event.h
@@ -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;
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp b/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp
index 7f7406f..27ae01a 100644
--- a/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.cpp
@@ -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" \ 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
index e81429f..ecd6641 100644
--- a/kaffeine/src/player-parts/libmpv-part/libmpv_part.h
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.h
@@ -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__ */
diff --git a/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc b/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc
index 9373445..046f732 100644
--- a/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc
+++ b/kaffeine/src/player-parts/libmpv-part/libmpv_part.rc
@@ -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> \ No newline at end of file