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 +#include #include #include #include @@ -30,6 +31,8 @@ #include #include #include +#include +#include #include #include #include @@ -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 +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 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 @@ + + + Playback Controls Toolbar @@ -27,4 +30,119 @@ +Stream Recorder Toolbar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file