diff --git a/CMakeLists.txt b/CMakeLists.txt index 84ed7e7..c97445c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ qt_wrap_cpp(MOC_FILES mpvwidget.h playerwidget.h playercontroller.h + program_arg.h tracksmenu.h autohidewidget.h # From SMPlayer singleinstance.h @@ -75,6 +76,7 @@ set(MAIN_SRC mpvwidget.cpp playerwidget.cpp playercontroller.cpp + program_arg.cpp tracksmenu.cpp autohidewidget.cpp # From SMPlayer config.cpp diff --git a/helper.cpp b/helper.cpp index 9107a17..c0af1f0 100644 --- a/helper.cpp +++ b/helper.cpp @@ -118,11 +118,20 @@ void Helper::searchWithMaxDepth(QStringList &outList, const QStringList &filter, const QList Helper::pathsToUrls(const QStringList &paths) { QList ret; - for (auto &arg : paths) + for (const auto &arg : paths) { - QFileInfo f(arg); - if (f.exists()) - ret.append(QUrl(f.absoluteFilePath())); + // FIXME: URLs with spaces don't work + QUrl url(arg); + //QUrl::isLocalFile() is not reliable if the file doesn't exist + if (url.isValid() && !url.isLocalFile() && !url.host().isEmpty()) + ret.append(url); + else { + QFileInfo f(arg); + if (f.exists()) + ret.append(QUrl(f.absoluteFilePath())); + else + qDebug() << "Path " << f.path() << "does not exist, skipping";; + } } return ret; } diff --git a/kokovp.cpp b/kokovp.cpp index 128bb87..da99c17 100644 --- a/kokovp.cpp +++ b/kokovp.cpp @@ -38,6 +38,7 @@ #include "config.h" #include "cache.h" #include "helper.h" +#include "program_arg.h" #include "prefs/prefdialog.h" #include "persistency/filesettingshash.h" @@ -48,7 +49,7 @@ QString extfolderRewriteRule(const PlayerController::Track &t) if (!t.isExternal) return QString(); - QFileInfo fI(t.filename); + QFileInfo fI(t.mediaUrl); QString fName = fI.fileName(); if (fName==t.title) @@ -77,8 +78,8 @@ KokoVP::KokoVP(QWidget *parent) createPlaylistDock(); connect(player, &PlayerController::tracksUpdated, this, &KokoVP::handleTracks); - connect(player, &PlayerController::fileMetaUpdated, playlist, &Playlist::setCurrentRowMetainfo); - connect(player, &PlayerController::endFile, this, &KokoVP::handleEOF); + connect(player, &PlayerController::mediaMetaUpdated, playlist, &Playlist::setCurrentRowMetainfo); + connect(player, &PlayerController::endMediaRessource, this, &KokoVP::handleEOF); connect(playerWidget, &PlayerWidget::draggedURLS, playlist, &Playlist::addURLs); connect(playlist, &Playlist::playRequest, this, qOverload(&KokoVP::playFile)); @@ -154,18 +155,15 @@ KokoVP::KokoVP(QWidget *parent) KokoVP::~KokoVP() { - fileSettings->saveSettingsFor(player->lastOpenFile(), true); // Always save time-pos on exit + fileSettings->saveSettingsFor(player->lastOpenMediaUrl(), true); // Always save time-pos on exit } -void KokoVP::handleNewMessage(QString msg) +void KokoVP::handleNewMessage(const ProgramArgument &msg) { - int del = msg.indexOf(':'); - QString cmd = msg.left(del); - QStringList args = msg.trimmed().mid(del+1).split(','); - if (cmd=="open") - playlist->addURLs(Helper::pathsToUrls(args)); - else if (cmd=="playlast") - QTimer::singleShot(100, playlist, &Playlist::playLast); // Workaroun to wait until Qt event loop and libmpv will be ready + if (msg.cmd==ProgramCmd::OPEN) + playlist->addURLs(Helper::pathsToUrls(msg.args)); + else if (msg.cmd==ProgramCmd::PLAYLAST) + QTimer::singleShot(100, playlist, &Playlist::playLast); // Workaround to wait until Qt event loop and libmpv will be ready } void KokoVP::toggleFullscreen(bool on) @@ -481,13 +479,13 @@ void KokoVP::handleTracks() }; } if (Config::i().get("play_mode/keep_props", true).toBool()) - fileSettings->loadSettingsFor(player->currentFile(), Config::i().get("play_mode/keep_timepos", true).toBool()); + fileSettings->loadSettingsFor(player->currentMediaUrl(), Config::i().get("play_mode/keep_timepos", true).toBool()); } void KokoVP::handleEOF(bool wasStopped) { setWindowTitle("KokoVP"); - fileSettings->saveSettingsFor(player->lastOpenFile(), wasStopped); // If file is ended, then time-pos shouldn't be saved + fileSettings->saveSettingsFor(player->lastOpenMediaUrl(), wasStopped); // If file is ended, then time-pos shouldn't be saved player->prop("pause")->set(true); if (!wasStopped && Config::i().get("play_mode/next_on_eof", true).toBool()) playlist->next(); @@ -546,7 +544,7 @@ void KokoVP::setAudioDevice(QAction *audioDeviceAction) void KokoVP::tryPlayCurrent() { - if (player->currentFile().isEmpty()) + if (player->currentMediaUrl().isEmpty()) playlist->playCurrent(); } diff --git a/kokovp.desktop b/kokovp.desktop index 6a62ea7..d8a4994 100644 --- a/kokovp.desktop +++ b/kokovp.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Categories=Qt;AudioVideo;Player;Video; Comment=A modern mpv-based player -Exec=kokovp %F +Exec=kokovp %U GenericName=Media Player MimeType=application/ogg;application/x-ogg;application/mxf;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/aiff;audio/x-aiff;audio/m4a;audio/x-m4a;application/x-extension-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/mpeg2;audio/mpeg3;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/x-musepack;audio/ogg;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-wav;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;audio/x-matroska;application/x-matroska;video/webm;audio/webm;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-audio;application/x-ogm-video;application/x-shorten;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/eac3;audio/amr-wb;video/mp2t;audio/flac;audio/mp4;audio/x-pn-au;video/3gp;video/3gpp;video/3gpp2;audio/3gpp;audio/3gpp2;video/dv;audio/dv;audio/opus;audio/vnd.dts;audio/vnd.dts.hd;audio/x-adpcm;audio/vnd.wave;video/vnd.avi; Name=KokoVP diff --git a/kokovp.h b/kokovp.h index 8bfc6d0..0d25046 100644 --- a/kokovp.h +++ b/kokovp.h @@ -27,6 +27,7 @@ class Playlist; class TracksMenu; class AutohideWidget; class FileSettingsHash; +struct ProgramArgument; class QTableView; using QActionMap = QMap; @@ -40,7 +41,7 @@ class KokoVP : public QMainWindow ~KokoVP(); static KokoVP *i() { return inst; } QActionMap actionsMap() const { return p_actionsMap; } - void handleNewMessage(QString msg); + void handleNewMessage(const ProgramArgument &msg); private: void toggleFullscreen(bool on); diff --git a/main.cpp b/main.cpp index 6a66df5..07685a9 100644 --- a/main.cpp +++ b/main.cpp @@ -23,6 +23,7 @@ #include #include +#include "program_arg.h" #include "singleinstance.h" int main(int argc, char *argv[]) @@ -36,6 +37,7 @@ int main(int argc, char *argv[]) parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("media", QCoreApplication::translate("KokoVP", "Media files to play")); + parser.addPositionalArgument("urls", QCoreApplication::translate("KokoVP", "URLs to play")); // A boolean option with a single name (-p) QCommandLineOption newInstanceOption(QStringList() << "n" << "new-instance", QCoreApplication::translate("KokoVP", "Force open in new instance")); @@ -58,7 +60,7 @@ int main(int argc, char *argv[]) { if (inst.connectServer()) { - inst.sendMessage("open:" + parser.positionalArguments().join(',')); + inst.sendMessage(ProgramArgument(ProgramCmd::OPEN, parser.positionalArguments())); inst.closeSocket(); return 0; } @@ -70,8 +72,8 @@ int main(int argc, char *argv[]) KokoVP w; if (parser.positionalArguments().length()>0) { - w.handleNewMessage("open:" + parser.positionalArguments().join(',')); - w.handleNewMessage("playlast"); + w.handleNewMessage(ProgramArgument(ProgramCmd::OPEN, parser.positionalArguments())); + w.handleNewMessage(ProgramArgument(ProgramCmd::PLAYLAST)); } QObject::connect(&inst, &SingleInstance::newMessage, &w, &KokoVP::handleNewMessage); w.showNormal(); diff --git a/playercontroller.cpp b/playercontroller.cpp index 909306a..1ff02a0 100644 --- a/playercontroller.cpp +++ b/playercontroller.cpp @@ -26,9 +26,9 @@ PlayerController::PlayerController(PlayerWidget *parent) prop("volume")->set(50); prop("pause")->set(true); p->setProp("audio-file-auto-exts", Extensions.audio()); - connect(p, &PlayerWidget::fileLoaded, this, &PlayerController::handleFileLoad); - connect(p, &PlayerWidget::endFile, this, &PlayerController::endFile); - connect(p, &PlayerWidget::endFile, this, &PlayerController::handleFileEnd); + connect(p, &PlayerWidget::fileLoaded, this, &PlayerController::handleMediaLoad); + connect(p, &PlayerWidget::endFile, this, &PlayerController::endMediaRessource); + connect(p, &PlayerWidget::endFile, this, &PlayerController::handleMediaEnd); } PropertyObserver *PlayerController::prop(QString name) @@ -51,31 +51,31 @@ void PlayerController::setOption(const QString &name, const QVariant &value) p->setOption(name, value); } -void PlayerController::handleFileEnd() +void PlayerController::handleMediaEnd() { - haveFile = false; - if (!queuedFile.isEmpty()) + haveMediaUrl = false; + if (!queuedMediaUrl.isEmpty()) { - QUrl f = queuedFile; - queuedFile = QUrl(); - open(f); + QUrl url = queuedMediaUrl; + queuedMediaUrl = QUrl(); + open(url); } } -void PlayerController::open(const QUrl &file) +void PlayerController::open(const QUrl &url) { - if (haveFile) + if (haveMediaUrl) { - queuedFile = file; + queuedMediaUrl = url; return stop(); } p_tracks.clear(); // Here we need to scan siblings folder for possible external subtitles and audio //, then set it to sub-file-paths and audio-file-paths OPTIONs (not properties) - if (file.isLocalFile()) + if (url.isLocalFile()) { - QDir mediaDir = QFileInfo(file.toLocalFile()).absoluteDir(); + QDir mediaDir = QFileInfo(url.toLocalFile()).absoluteDir(); if (p_extSubMaxDepth>=0 && p_extSubMode!="no") { @@ -94,7 +94,7 @@ void PlayerController::open(const QUrl &file) } } - p->command(QStringList{"loadfile", file.path()}); + p->command(QStringList{"loadfile", url.toString()}); } void PlayerController::stop() @@ -164,10 +164,10 @@ bool PlayerController::isPlaying() return !getProp("pause").toBool(); } -void PlayerController::handleFileLoad() +void PlayerController::handleMediaLoad() { - lastFile = currentFile(); - haveFile = true; + lastMediaUrl = currentMediaUrl(); + haveMediaUrl = true; bool ok; int tracksCount = getProp("track-list/count").toInt(&ok); assert(ok); @@ -188,7 +188,7 @@ void PlayerController::handleFileLoad() t.type = Track::TRACK_TYPE_SUB; if (t.isExternal) - t.filename = p->getProp(trackAddr + "external-filename").toString(); + t.mediaUrl = p->getProp(trackAddr + "external-mediaUrl").toString(); p_tracks.append(t); } @@ -200,5 +200,5 @@ void PlayerController::handleFileLoad() p->setProp("pause", false); // Previous file can be paused, when new file is loaded, it's time to de-pause player emit tracksUpdated(); - emit fileMetaUpdated(p->getProp("media-title").toString(), prop("duration")->get().toDouble()); + emit mediaMetaUpdated(p->getProp("media-title").toString(), prop("duration")->get().toDouble()); } diff --git a/playercontroller.h b/playercontroller.h index e72574c..3f8967d 100644 --- a/playercontroller.h +++ b/playercontroller.h @@ -40,14 +40,14 @@ class PlayerController : public QObject TrackType type; QString title; QString lang; - QString filename; + QString mediaUrl; bool isExternal; }; explicit PlayerController(PlayerWidget *parent = nullptr); - QString currentFile() { return getProp("path").toString(); } - QString lastOpenFile() { return lastFile; } + QString currentMediaUrl() { return getProp("path").toString(); } + QString lastOpenMediaUrl() { return lastMediaUrl; } PropertyObserver *prop(QString name); void setProp(const QString& name, const QVariant& value); @@ -58,7 +58,7 @@ class PlayerController : public QObject void setExtSubOptions(QString mode, int depth) { p_extSubMode = mode; p_extSubMaxDepth = depth; } void setExtAudioOptions(QString mode, int depth) { p_extAudioMode = mode; p_extAudioMaxDepth = depth; } - void open(const QUrl &file); + void open(const QUrl &url); void stop(); void togglePlayback(); @@ -82,11 +82,11 @@ class PlayerController : public QObject signals: void tracksUpdated(); - void fileMetaUpdated(QString label, double duration); - void endFile(bool wasStopped); + void mediaMetaUpdated(QString label, double duration); + void endMediaRessource(bool wasStopped); private: - void handleFileEnd(); - void handleFileLoad(); + void handleMediaEnd(); + void handleMediaLoad(); PlayerWidget *p; @@ -98,9 +98,9 @@ class PlayerController : public QObject QList p_tracks; - QUrl queuedFile; - QString lastFile; - bool haveFile = false; + QUrl queuedMediaUrl; + QString lastMediaUrl; + bool haveMediaUrl = false; }; #endif // PLAYERCONTROLLER_H diff --git a/program_arg.cpp b/program_arg.cpp new file mode 100644 index 0000000..a5db460 --- /dev/null +++ b/program_arg.cpp @@ -0,0 +1,20 @@ +#include + +#include "program_arg.h" + +ProgramArgument::ProgramArgument(const ProgramCmd &cmd, const QStringList &args) + : cmd{cmd}, args{args} +{ +} + +QDataStream &operator>>(QDataStream &istream, ProgramArgument &pa) +{ + istream >> pa.cmd >> pa.args; + return istream; +} + +QDataStream &operator<<(QDataStream &ostream, const ProgramArgument &pa) +{ + ostream << pa.cmd << pa.args; + return ostream; +} diff --git a/program_arg.h b/program_arg.h new file mode 100644 index 0000000..3cd550f --- /dev/null +++ b/program_arg.h @@ -0,0 +1,27 @@ +#ifndef PROGRAM_ARG_H +#define PROGRAM_ARG_H + +#include +#include + +class QDataStream; + +enum class ProgramCmd +{ + NONE, + OPEN, + PLAYLAST, +}; + +struct ProgramArgument +{ + explicit ProgramArgument(const ProgramCmd &cmd = ProgramCmd::NONE, const QStringList &args = QStringList()); + + ProgramCmd cmd; + QStringList args; + + friend QDataStream &operator>>(QDataStream &istream, ProgramArgument &pa); + friend QDataStream &operator<<(QDataStream &ostream, const ProgramArgument &pa); +}; + +#endif diff --git a/singleinstance.cpp b/singleinstance.cpp index a3d88f3..a5aa0dd 100644 --- a/singleinstance.cpp +++ b/singleinstance.cpp @@ -16,8 +16,13 @@ */ #include "singleinstance.h" +#include +#include #include #include +#include + +#include "program_arg.h" SingleInstance::SingleInstance(QString appName, QObject *parent) : p_appName(appName), QObject{parent} @@ -49,10 +54,12 @@ bool SingleInstance::hostServer() return ret; } -void SingleInstance::sendMessage(QString msg) +void SingleInstance::sendMessage(const ProgramArgument &msg) { - p_socket->write(msg.toUtf8()); - p_socket->putChar('\n'); + QByteArray data; + QDataStream ostream(&data, QIODevice::WriteOnly); + ostream << msg; + p_socket->write(data); p_socket->waitForBytesWritten(100); } @@ -69,6 +76,12 @@ void SingleInstance::handleNewConnection() void SingleInstance::readData() { QLocalSocket *sock = static_cast(sender()); - while (sock->canReadLine()) - emit newMessage(sock->readLine()); + QByteArray bytes = sock->readAll(); + QDataStream istream(&bytes, QIODevice::ReadOnly); + + while (!istream.atEnd()) { + ProgramArgument msg; + istream >> msg; + emit newMessage(msg); + } } diff --git a/singleinstance.h b/singleinstance.h index 6ec7a5c..bfeb7da 100644 --- a/singleinstance.h +++ b/singleinstance.h @@ -19,6 +19,7 @@ #include +struct ProgramArgument; class QLocalSocket; class QLocalServer; @@ -29,11 +30,12 @@ class SingleInstance : public QObject explicit SingleInstance(QString appName, QObject *parent = nullptr); bool connectServer(); void closeSocket(); - void sendMessage(QString msg); + void sendMessage(const ProgramArgument &msg); bool hostServer(); signals: - void newMessage(QString msg); + void newMessage(const ProgramArgument &msg); + void handleNewMessage(const ProgramArgument &msg); private: void handleNewConnection(); void readData();