You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
756 lines
23 KiB
756 lines
23 KiB
/*
|
|
* Kaffeine libmpv part
|
|
* Copyright (C) 2023 Mavridis Philippe <mavridisf@gmail.com>
|
|
*
|
|
* Based on Kaffeine dummy part
|
|
* Copyright (C) 2004-2005 Jürgen Kofler <kaffeine@gmx.net>
|
|
*
|
|
* 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 <tqtooltip.h>
|
|
#include <tqlabel.h>
|
|
#include <tqslider.h>
|
|
#include <tqpushbutton.h>
|
|
#include <tqfile.h>
|
|
#include <tqstringlist.h>
|
|
#include <tqpopupmenu.h>
|
|
#include <tqcursor.h>
|
|
#include <tqhbox.h>
|
|
|
|
// TDE
|
|
#include <tdeparts/genericfactory.h>
|
|
#include <tdeglobalsettings.h>
|
|
#include <tdeio/netaccess.h>
|
|
#include <kstandarddirs.h>
|
|
#include <tdemessagebox.h>
|
|
#include <tdefiledialog.h>
|
|
#include <tdeaction.h>
|
|
#include <kmimetype.h>
|
|
#include <tdemessagebox.h>
|
|
#include <kxmlguifactory.h>
|
|
#include <tdetoolbar.h>
|
|
#include <tdeglobal.h>
|
|
#include <kiconloader.h>
|
|
#include <kdebug.h>
|
|
|
|
// Kaffeine
|
|
#include "playlistimport.h"
|
|
|
|
// Part
|
|
#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);
|
|
|
|
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_seekpos(0),
|
|
m_recordFilePath(),
|
|
m_context(nullptr),
|
|
m_error(nullptr),
|
|
m_criticalError(nullptr),
|
|
m_playtimeMode(CurrentTime)
|
|
{
|
|
// Create an instance of this class
|
|
setInstance(MpvPartFactory::instance());
|
|
|
|
// Initialize GUI
|
|
setXMLFile("libmpv_part.rc");
|
|
initActions();
|
|
|
|
// Create container widget
|
|
m_player = new MpvContainerWidget(this, parentWidget);
|
|
setWidget(m_player);
|
|
|
|
// Initialize and embed mpv
|
|
if (!initMpv()) {
|
|
kdError() << "libmpvpart: initialization of mpv failed!" << endl;
|
|
emit canceled(i18n("MPV initialization failed!"));
|
|
return;
|
|
}
|
|
|
|
m_logoPath = locate( "data", "kaffeine/logo" );
|
|
kdDebug() << "libmpvpart: Found logo animation: " << m_logoPath << endl;
|
|
|
|
closeURL(); // displays logo
|
|
emit setStatusBarText(i18n("Ready"));
|
|
}
|
|
|
|
MpvPart::~MpvPart()
|
|
{
|
|
if (m_mpv) {
|
|
mpv_terminate_destroy(m_mpv);
|
|
m_mpv = nullptr;
|
|
}
|
|
|
|
if (m_eventThread) {
|
|
// the event thread doesn't do I/O, so we just kill it
|
|
m_eventThread->terminate();
|
|
}
|
|
}
|
|
|
|
TDEAboutData *MpvPart::createAboutData() {
|
|
TDEAboutData* aboutData = new TDEAboutData(
|
|
"libmpvpart", I18N_NOOP("MpvPart"),
|
|
"0.1", "A Kaffeine player part based on libmpv",
|
|
TDEAboutData::License_GPL,
|
|
"(c) 2023 Trinity Desktop Project", 0);
|
|
aboutData->addAuthor("Mavridis Philippe", I18N_NOOP("Developer"), "mavridisf@gmail.com");
|
|
aboutData->addAuthor("Jürgen Kofler", I18N_NOOP("Original Kaffeine developer"), "kaffeine@gmx.net");
|
|
return aboutData;
|
|
}
|
|
|
|
bool MpvPart::initMpv() {
|
|
// Create mpv instance
|
|
m_mpv = mpv_create();
|
|
if (!m_mpv) {
|
|
return false;
|
|
}
|
|
|
|
// Embed mpv into container widget
|
|
int64_t wid = static_cast<int64_t>(m_player->winId());
|
|
mpv_set_option(m_mpv, "wid", MPV_FORMAT_INT64, &wid);
|
|
|
|
// Get log messages with minimal level "info"
|
|
mpv_request_log_messages(m_mpv, "info");
|
|
|
|
// Start mpv event thread
|
|
m_eventThread = new MpvEventThread(this);
|
|
m_eventThread->start(TQThread::LowPriority);
|
|
|
|
return (mpv_initialize(m_mpv) >= 0);
|
|
}
|
|
|
|
void MpvPart::initActions()
|
|
{
|
|
/** Playback controls ***/
|
|
new TDEAction(i18n("Play"), "media-playback-start", 0, this, SLOT(slotPlay()), actionCollection(), "player_play");
|
|
new TDEAction(i18n("Pause"), "media-playback-pause", Key_Space, this, SLOT(slotTogglePause()), actionCollection(), "player_pause");
|
|
new TDEAction(i18n("Stop"), "media-playback-stop", Key_Backspace, this, SLOT(slotStop()), actionCollection(), "player_stop");
|
|
new TDEAction(i18n("&Previous"), "media-skip-backward", Key_PageUp, this, SLOT(slotPrevious()), actionCollection(), "player_previous");
|
|
new TDEAction(i18n("&Next"), "media-skip-forward", Key_PageDown, this, SLOT(slotNext()), actionCollection(), "player_next");
|
|
|
|
/*** Position toolbar ***/
|
|
// Important: we have a max of 1000 instead of 100 for better precision; multiply/divide your percentages by 10
|
|
m_position = new TQSlider(0, 100, 10, 0, TQt::Horizontal, 0);
|
|
TQToolTip::add(m_position, i18n("Position"));
|
|
m_position->setTracking(false);
|
|
m_position->setFocusPolicy(TQWidget::NoFocus);
|
|
m_position->setMinimumWidth(100);
|
|
connect(m_position, SIGNAL(sliderPressed()), this, SLOT(slotStartSeeking()));
|
|
connect(m_position, SIGNAL(sliderMoved(int)), this, SLOT(slotSetSeekingPos(int)));
|
|
connect(m_position, SIGNAL(sliderReleased()), this, SLOT(slotStopSeeking()));
|
|
KWidgetAction *posAction = new KWidgetAction(m_position, i18n("Position"), 0, 0, 0, actionCollection(), "player_position");
|
|
posAction->setAutoSized(true);
|
|
|
|
m_playtime = new TQPushButton(0);
|
|
TQToolTip::add(m_playtime, i18n("Press to change playtime display mode."));
|
|
TQFontMetrics met(TDEGlobalSettings::generalFont());
|
|
m_playtime->setFixedWidth(met.width(" -88:88:88 "));
|
|
m_playtime->setSizePolicy(TQSizePolicy (TQSizePolicy::Fixed, TQSizePolicy::Fixed));
|
|
m_playtime->setFocusPolicy(TQWidget::NoFocus);
|
|
connect(m_playtime, SIGNAL(pressed()), this, SLOT(slotTogglePlaytimeMode()));
|
|
new KWidgetAction(m_playtime, i18n("Playtime"), 0, 0, 0, actionCollection(), "player_playtime");
|
|
|
|
/*** Volume toolbar ***/
|
|
new TDEAction(i18n("&Mute"), "player_mute", Key_U, this, SLOT(slotMute()), actionCollection(), "player_mute");
|
|
|
|
m_volume = new TQSlider(0, 100, 10, 100, TQt::Horizontal, 0);
|
|
connect(m_volume, SIGNAL(sliderMoved(int)), this, SLOT(slotSetVolume(int)));
|
|
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();
|
|
|
|
/*** 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::logEvent(struct MpvEventData eventData) {
|
|
m_eventLog.append(eventData);
|
|
}
|
|
|
|
void MpvPart::slotViewLog() {
|
|
MpvLogViewerDlg *logViewer = new MpvLogViewerDlg(this);
|
|
logViewer->exec();
|
|
}
|
|
|
|
void MpvPart::slotTogglePlaytimeMode() {
|
|
++m_playtimeMode;
|
|
if (m_playtimeMode == PLAYTIME_MODE_LAST) {
|
|
m_playtimeMode = CurrentTime;
|
|
}
|
|
updatePlaytime(m_time);
|
|
}
|
|
|
|
void MpvPart::updatePlaytime(TQTime time) {
|
|
TQString label;
|
|
switch (m_playtimeMode) {
|
|
case CurrentTime: {
|
|
label = time.toString();
|
|
break;
|
|
}
|
|
|
|
case RemainingTime: {
|
|
TQTime duration = m_mrl.length();
|
|
int h = duration.hour() - time.hour();
|
|
int m = duration.minute() - time.minute();
|
|
int s = duration.second() - time.second();
|
|
label = TQString("-%1:%2:%3").arg(TQString::number(h).rightJustify(2, '0'),
|
|
TQString::number(m).rightJustify(2, '0'),
|
|
TQString::number(s).rightJustify(2, '0'));
|
|
break;
|
|
}
|
|
}
|
|
m_playtime->setText(label);
|
|
}
|
|
|
|
// Custom events dispatched from mpv event thread are handled here
|
|
void MpvPart::customEvent(TQCustomEvent *event) {
|
|
if (event->type() == MPVPART_EVENT_PROPERTY_CHANGE) {
|
|
MpvPropertyChangeEvent *pe = (MpvPropertyChangeEvent *)event;
|
|
if (pe->property() == "time-pos" && pe->format() == MPV_FORMAT_DOUBLE) {
|
|
m_time = pe->toTime();
|
|
updatePlaytime(m_time);
|
|
}
|
|
|
|
else if (pe->property() == "percent-pos" && pe->format() == MPV_FORMAT_DOUBLE) {
|
|
if (!m_seeking) {
|
|
m_position->setValue(pe->toDouble());
|
|
}
|
|
}
|
|
|
|
else if (pe->property() == "duration" && pe->format() == MPV_FORMAT_DOUBLE) {
|
|
TQTime length = TQTime().addMSecs(pe->toDouble() * 1000);
|
|
if (!length.isNull()) {
|
|
m_mrl.setLength(length);
|
|
emit signalNewMeta(m_mrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (event->type() == MPVPART_EVENT_EOF) {
|
|
resetTime();
|
|
|
|
if (!m_mrl.isEmpty()) {
|
|
closeURL();
|
|
stateChanged("not_playing");
|
|
}
|
|
|
|
MpvEOFEvent *eofe = (MpvEOFEvent *)event;
|
|
if (eofe->reason() == MPV_END_FILE_REASON_ERROR) {
|
|
KMessageBox::detailedError(nullptr, i18n("Cannot play file."), eofe->error());
|
|
}
|
|
}
|
|
|
|
else if (event->type() == MPVPART_EVENT_ERROR) {
|
|
MpvErrorEvent *ee = (MpvErrorEvent *)event;
|
|
|
|
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 {
|
|
m_error = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MpvPart::openURL(const MRL& mrl) {
|
|
/* Note: we do not use the internal playlist feature of mpv.
|
|
* Instead we rely on the playlist feature of Kaffeine.
|
|
*/
|
|
|
|
if (!m_mpv) return false;
|
|
|
|
m_mrl = mrl;
|
|
m_playlist.clear();
|
|
m_current = 0;
|
|
|
|
/* Playlist handling code taken from gstreamer_part.cpp */
|
|
bool playlist = false;
|
|
TQString ext = m_mrl.kurl().fileName();
|
|
ext = ext.remove( 0 , ext.findRev('.')+1 ).lower();
|
|
|
|
if ( m_mrl.mime().isNull() ) {
|
|
KMimeType::Ptr mime = KMimeType::findByURL( m_mrl.kurl().path() );
|
|
m_mrl.setMime( mime->name() );
|
|
}
|
|
|
|
if ( (m_mrl.mime() == "text/plain") || (m_mrl.mime() == "text/xml") || (m_mrl.mime() == "application/x-kaffeine")
|
|
|| (m_mrl.mime() == "audio/x-scpls") || (m_mrl.mime() == "audio/x-mpegurl") || (m_mrl.mime() == "audio/mpegurl")
|
|
|| (ext == "asx") || (ext == "asf") || (ext == "wvx") || (ext == "wax") ) /* windows meta files */
|
|
{
|
|
kdDebug() << "libmpvpart: Check for kaffeine/noatun/m3u/pls/asx playlist\n";
|
|
TQString localFile;
|
|
if ( TDEIO::NetAccess::download(m_mrl.kurl(), localFile, widget()) ) {
|
|
TQFile file( localFile );
|
|
file.open( IO_ReadOnly );
|
|
TQTextStream stream( &file );
|
|
TQString firstLine = stream.readLine();
|
|
TQString secondLine = stream.readLine();
|
|
file.close();
|
|
|
|
if ( secondLine.contains("kaffeine", false) ) {
|
|
kdDebug() << "libmpvpart: Try loading kaffeine playlist\n";
|
|
playlist = PlaylistImport::kaffeine( localFile, m_playlist );
|
|
}
|
|
if ( secondLine.contains("noatun", false) ) {
|
|
kdDebug() << "libmpvpart: Try loading noatun playlist\n";
|
|
playlist = PlaylistImport::noatun( localFile, m_playlist);
|
|
}
|
|
if ( firstLine.contains("asx", false) ) {
|
|
kdDebug() << "libmpvpart: Try loading asx playlist\n";
|
|
playlist = PlaylistImport::asx( localFile, m_playlist );
|
|
}
|
|
if ( firstLine.contains("[playlist]", false) ) {
|
|
kdDebug() << "libmpvpart: Try loading pls playlist\n";
|
|
playlist = PlaylistImport::pls( localFile, m_playlist );
|
|
}
|
|
if (ext == "m3u" || ext == "m3u8") { //indentify by extension
|
|
kdDebug() << "libmpvpart: Try loading m3u playlist\n";
|
|
playlist = PlaylistImport::m3u( localFile, m_playlist );
|
|
}
|
|
}
|
|
else
|
|
kdError() << "libmpvpart: " << TDEIO::NetAccess::lastErrorString() << endl;
|
|
}
|
|
// check for ram playlist
|
|
if ( (ext == "ra") || (ext == "rm") || (ext == "ram") || (ext == "lsc") || (ext == "pl") ) {
|
|
kdDebug() << "libmpvpart: Try loading ram playlist\n";
|
|
playlist = PlaylistImport::ram( m_mrl, m_playlist, widget() );
|
|
}
|
|
|
|
if (!playlist)
|
|
{
|
|
kdDebug() << "libmpvpart: Got single track" << endl;
|
|
m_playlist.append( m_mrl );
|
|
}
|
|
|
|
m_mrl.reset();
|
|
slotPlay();
|
|
return true;
|
|
}
|
|
|
|
bool MpvPart::closeURL() {
|
|
if (!m_mpv) return false;
|
|
|
|
m_mrl.reset();
|
|
if (!m_logoPath.isNull()) {
|
|
const char *logo_args[] = {"loadfile", m_logoPath.local8Bit(), nullptr };
|
|
mpv_command_async(m_mpv, 0, logo_args);
|
|
slotPause(true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void MpvPart::slotPlay() {
|
|
if (!m_mpv) return;
|
|
|
|
slotPause(false);
|
|
if (!m_mrl.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
if (m_playlist.count() > 0) {
|
|
emit setStatusBarText( i18n("Opening...") );
|
|
m_mrl = m_playlist[m_current];
|
|
|
|
const char *args[] = {"loadfile", m_mrl.url().local8Bit(), nullptr};
|
|
mpv_command_async(m_mpv, 0, args);
|
|
|
|
slotReloadSubtitles();
|
|
}
|
|
else {
|
|
emit signalRequestCurrentTrack();
|
|
}
|
|
|
|
slotReloadSubtitles();
|
|
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() {
|
|
if (!m_mpv) return;
|
|
slotPause(!isPaused());
|
|
}
|
|
|
|
bool MpvPart::isPlaying()
|
|
{
|
|
if (!m_mpv) return false;
|
|
|
|
int result;
|
|
if (mpv_get_property(m_mpv, "core-idle", MPV_FORMAT_FLAG, &result) < 0) {
|
|
return false;
|
|
}
|
|
return !(bool)result;
|
|
}
|
|
|
|
bool MpvPart::isPaused() {
|
|
if (!m_mpv) return false;
|
|
|
|
int result;
|
|
if (mpv_get_property(m_mpv, "pause", MPV_FORMAT_FLAG, &result) < 0) {
|
|
return false;
|
|
}
|
|
return (bool)result;
|
|
}
|
|
|
|
bool MpvPart::isMute() {
|
|
if (!m_mpv) return false;
|
|
|
|
int result;
|
|
if (mpv_get_property(m_mpv, "ao-mute", MPV_FORMAT_FLAG, &result) < 0) {
|
|
return false;
|
|
}
|
|
return (bool)result;
|
|
}
|
|
|
|
bool MpvPart::isStream() {
|
|
TQString proto = m_mrl.kurl().protocol();
|
|
if (proto.startsWith("http") || proto.startsWith("rtsp")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MpvPart::isSeekable() {
|
|
if (!m_mpv) return false;
|
|
|
|
int result;
|
|
if (mpv_get_property(m_mpv, "seekable", MPV_FORMAT_FLAG, &result) < 0) {
|
|
return false;
|
|
}
|
|
return (bool)result;
|
|
}
|
|
|
|
void MpvPart::resetTime() {
|
|
m_playtime->setText("00:00:00");
|
|
m_position->setValue(0);
|
|
}
|
|
|
|
void MpvPart::slotStartSeeking() {
|
|
if (!isSeekable()) {
|
|
kdWarning() << "current file not seekable!" << endl;
|
|
emit setStatusBarText( i18n("Cannot seek current file!") );
|
|
return;
|
|
}
|
|
m_seeking = true;
|
|
slotPause(true);
|
|
}
|
|
|
|
void MpvPart::slotStopSeeking() {
|
|
if (!m_seeking) return;
|
|
|
|
slotSetPosition(m_seekpos);
|
|
slotPause(false);
|
|
m_seeking = false;
|
|
}
|
|
|
|
void MpvPart::slotSetSeekingPos(int percent) {
|
|
if (!m_seeking) return;
|
|
|
|
// Compute current position and update playtime indicator
|
|
TQTime duration = m_mrl.length();
|
|
uint secs = (duration.hour() * 60 * 60) +
|
|
(duration.minute() * 60) +
|
|
duration.second();
|
|
|
|
m_seekpos = secs * percent / 100;
|
|
|
|
TQTime pos; pos = pos.addSecs(m_seekpos);
|
|
updatePlaytime(pos);
|
|
}
|
|
|
|
uint MpvPart::volume() const {
|
|
int64_t value = 0;
|
|
mpv_get_property(m_mpv, "ao-volume", MPV_FORMAT_INT64, &value);
|
|
return value;
|
|
}
|
|
|
|
uint MpvPart::position() const {
|
|
return m_position->value();
|
|
}
|
|
|
|
void MpvPart::slotSetVolume(uint volume) {
|
|
if (!m_mpv) return;
|
|
|
|
int64_t value = (int64_t)volume;
|
|
mpv_set_property(m_mpv, "ao-volume", MPV_FORMAT_INT64, &value);
|
|
}
|
|
|
|
void MpvPart::slotSetPosition(uint secs) {
|
|
if (!m_mpv) return;
|
|
|
|
int64_t value = (int64_t)secs;
|
|
mpv_set_property(m_mpv, "time-pos", MPV_FORMAT_INT64, &value);
|
|
}
|
|
|
|
void MpvPart::slotPrevious() {
|
|
if ( m_current > 0 ) {
|
|
m_current--;
|
|
slotPlay();
|
|
}
|
|
else {
|
|
emit signalRequestPreviousTrack();
|
|
}
|
|
}
|
|
|
|
void MpvPart::slotNext() {
|
|
if ((m_playlist.count() > 0) && (m_current < m_playlist.count()-1)) {
|
|
m_current++;
|
|
slotPlay();
|
|
}
|
|
else {
|
|
emit signalRequestNextTrack();
|
|
}
|
|
}
|
|
|
|
void MpvPart::slotStop() {
|
|
if (!isPlaying()) {
|
|
return;
|
|
}
|
|
|
|
const char *stop_args[] = {"stop", nullptr};
|
|
mpv_command(m_mpv, stop_args);
|
|
|
|
closeURL();
|
|
stateChanged("not_playing");
|
|
}
|
|
|
|
void MpvPart::slotMute() {
|
|
int value = isMute() ? 0 : 1;
|
|
mpv_set_property(m_mpv, "ao-mute", MPV_FORMAT_FLAG, &value);
|
|
}
|
|
|
|
void MpvPart::slotReloadSubtitles() {
|
|
TQStringList subtitles = m_mrl.subtitleFiles();
|
|
int subId = m_mrl.currentSubtitle();
|
|
|
|
// Remove existing subtitles synchronously
|
|
const char *rm_args[] = {"sub-remove", nullptr};
|
|
mpv_command(m_mpv, rm_args);
|
|
|
|
if ((!subtitles.isEmpty()) && (subId > -1)) {
|
|
kdDebug() << "adding subtitle file " << subtitles[subId] << endl;
|
|
|
|
// Add requested subtitles
|
|
const char *add_args[] = {"sub-add", subtitles[subId].local8Bit(), nullptr};
|
|
mpv_command_async(m_mpv, 0, add_args);
|
|
}
|
|
}
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
void MpvPart::slotAddSubtitles() {
|
|
TQString openURL = m_mrl.url();
|
|
TQString subtitleURL = KFileDialog::getOpenURL(openURL,
|
|
i18n("*.smi *.srt *.sub *.txt *.ssa *.asc|Subtitle Files\n*.*|All Files"),
|
|
0, i18n("Select Subtitle File")).path();
|
|
|
|
kdDebug()<<"slotAddSubtitles: "<<subtitleURL<<endl;
|
|
kdDebug()<<" exists: "<<TQFile::exists(subtitleURL)<<endl;
|
|
|
|
if (subtitleURL.isEmpty() || !TQFile::exists(subtitleURL)) {
|
|
return;
|
|
}
|
|
|
|
m_mrl.addSubtitleFile(subtitleURL);
|
|
emit signalNewMeta(m_mrl);
|
|
}
|
|
|
|
void MpvPart::slotContextSetSubtitles(int choice) {
|
|
int subId = m_subs->itemParameter(choice);
|
|
if (subId == choice) {
|
|
// If itemParameter() returns the menu item id, then we know that
|
|
// the item has no item parameter, hence it is not an option that
|
|
// we handle through this slot.
|
|
return;
|
|
}
|
|
|
|
kdDebug() << "switching to subtitle: " << subId
|
|
<< " (" << m_subs->text(choice) << ")" << endl;
|
|
m_mrl.setCurrentSubtitle(subId);
|
|
emit signalNewMeta(m_mrl);
|
|
slotReloadSubtitles();
|
|
}
|
|
|
|
|
|
void MpvPart::showContextMenu() {
|
|
/*** Context menu ***/
|
|
m_context = new TQPopupMenu(0);
|
|
actionCollection()->action("player_play")->plug(m_context);
|
|
actionCollection()->action("player_pause")->plug(m_context);
|
|
actionCollection()->action("player_stop")->plug(m_context);
|
|
m_context->insertSeparator();
|
|
actionCollection()->action("player_previous")->plug(m_context);
|
|
actionCollection()->action("player_next")->plug(m_context);
|
|
m_context->insertSeparator();
|
|
|
|
/*** Subtitles submenu ***/
|
|
m_subs = new TQPopupMenu(0);
|
|
actionCollection()->action("subtitles_add")->plug(m_subs);
|
|
TQStringList subtitles = m_mrl.subtitleFiles();
|
|
|
|
if (m_mrl.isEmpty()) {
|
|
m_subs->setEnabled(false);
|
|
}
|
|
|
|
m_subs->insertSeparator();
|
|
int noSubsItem = m_subs->insertItem(i18n("None"));
|
|
m_subs->setItemParameter(noSubsItem, -1);
|
|
if (!subtitles.empty()) {
|
|
int index;
|
|
for (TQStringList::Iterator it = subtitles.begin(); it != subtitles.end(); ++it) {
|
|
int id = m_subs->insertItem((*it));
|
|
m_subs->setItemParameter(id, index);
|
|
|
|
// Comparing an index is cheaper than doing a string comparison
|
|
if (index == m_mrl.currentSubtitle()) {
|
|
m_subs->setItemChecked(id, true);
|
|
}
|
|
index++;
|
|
}
|
|
connect(m_subs, SIGNAL(activated(int)), this, SLOT(slotContextSetSubtitles(int)));
|
|
}
|
|
|
|
if (subtitles.empty() || m_mrl.currentSubtitle() < 0) {
|
|
m_subs->setItemChecked(noSubsItem, true);
|
|
}
|
|
|
|
m_context->insertItem(i18n("Sub&titles"), m_subs);
|
|
m_context->exec(TQCursor::pos());
|
|
|
|
hideContextMenu();
|
|
}
|
|
|
|
void MpvPart::hideContextMenu() {
|
|
if (m_context) {
|
|
delete m_context;
|
|
m_context = nullptr;
|
|
}
|
|
|
|
if (!m_subs) {
|
|
delete m_subs;
|
|
m_subs = nullptr;
|
|
}
|
|
}
|
|
|
|
#include "libmpv_part.moc" |