Skip to content
Extraits de code Groupes Projets

Video playback with mpv

1 file
+ 4
2
Comparer les modifications
  • Côte à côte
  • En ligne
+ 326
0
#include "MpvContainer.hh"
#include <mpv/client.h>
using namespace Vivy;
using namespace std::string_literals;
void
MpvContainer::mpvEventWakeUpCB(void *user) noexcept
{
MpvContainer *container = reinterpret_cast<MpvContainer *>(user);
emit container->mpvEvent();
}
MpvContainer::MpvContainer(QWidget *parent)
: QWidget(parent)
, mpv(mpv_create())
{
if (mpv == nullptr)
throw std::runtime_error("Failed to create the MPV context");
setAttribute(Qt::WA_DontCreateNativeAncestors);
setAttribute(Qt::WA_NativeWindow);
quint64 wid = winId();
mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
mpv_set_option_string(mpv, "idle", "yes");
mpv_set_option_string(mpv, "loop-file", "inf");
mpv_set_option_string(mpv, "no-config", "yes");
mpv_set_option_string(mpv, "sid", "no");
mpv_set_option_string(mpv, "input-default-bindings", "no");
mpv_set_option_string(mpv, "input-vo-keyboard", "no");
mpv_request_log_messages(mpv, "info");
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
connect(this, &MpvContainer::mpvEvent, this, &MpvContainer::onMpvEvent, Qt::QueuedConnection);
mpv_set_wakeup_callback(mpv, &MpvContainer::mpvEventWakeUpCB, this);
if (int rc = mpv_initialize(mpv); rc < 0) {
printMpvError(rc);
throw std::runtime_error("Failed to initialize the mpv context");
}
}
void
MpvContainer::registerMpvTimeCallback(std::function<void(double)> callback) noexcept
{
if (mpvTimeCallback)
qWarning() << "Override a previous mpv callback!";
mpvTimeCallback = callback;
}
void
MpvContainer::registerMpvDurationCallback(std::function<void(double)> callback) noexcept
{
if (mpvDurationCallback)
qWarning() << "Override a previous mpv callback!";
mpvDurationCallback = callback;
}
void
MpvContainer::closeMpv() noexcept
{
if (mpv) {
qDebug() << "Closing the MPV context";
asyncCommand(AsyncCmdType::None, { "quit", nullptr });
registerMpvTimeCallback(nullptr);
registerMpvDurationCallback(nullptr);
mpv_handle *tmp_mpv = mpv;
mpv = nullptr; // Stop all other callbacks here
mpv_terminate_destroy(tmp_mpv);
}
}
MpvContainer::~MpvContainer() noexcept
{
closeMpv();
}
void
MpvContainer::handleMpvEvent(mpv_event *event) noexcept
{
// Declare here variables that can be used in the switch-case statements
qint64 w, h;
double time;
QString msgText;
union {
mpv_event_log_message *msg;
mpv_event_property *prop;
};
auto checkProp = [](mpv_event_property *prop, const std::string &str,
int format) noexcept -> bool {
return (prop->name == str) && (prop->format == format);
};
switch (event->event_id) {
case MPV_EVENT_SHUTDOWN:
closeMpv();
break;
case MPV_EVENT_VIDEO_RECONFIG:
// TODO: Those are sync calls, prefer async calls
if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 &&
mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 && (w > 0 && h > 0)) {
qDebug() << "Reconfigure video to" << w << "x" << h;
}
break;
case MPV_EVENT_LOG_MESSAGE:
msg = reinterpret_cast<mpv_event_log_message *>(event->data);
msgText = msg->text;
msgText.replace('\n', "");
qDebug().nospace().noquote()
<< "MPV - MSG [" << msg->prefix << "] " << msg->level << ": " << msgText;
break;
case MPV_EVENT_PROPERTY_CHANGE:
prop = reinterpret_cast<mpv_event_property *>(event->data);
if (checkProp(prop, "time-pos"s, MPV_FORMAT_DOUBLE) && mpvTimeCallback) {
time = *reinterpret_cast<double *>(prop->data);
mpvTimeCallback(time);
}
else if (checkProp(prop, "duration"s, MPV_FORMAT_DOUBLE) && mpvDurationCallback) {
time = *reinterpret_cast<double *>(prop->data);
mpvDurationCallback(time);
}
else if (checkProp(prop, "pause"s, MPV_FORMAT_FLAG)) {
isPlaybackPaused = *reinterpret_cast<bool *>(prop->data);
emit mpvPlaybackToggled(!isPlaybackPaused);
qDebug() << "MPV -> set to" << (isPlaybackPaused ? "pause" : "play");
}
break;
case MPV_EVENT_PAUSE:
isPlaybackPaused = true;
emit mpvPlaybackToggled(!isPlaybackPaused);
qDebug() << "MPV -> set to pause";
break;
case MPV_EVENT_UNPAUSE:
isPlaybackPaused = false;
emit mpvPlaybackToggled(!isPlaybackPaused);
qDebug() << "MPV -> set to play";
break;
case MPV_EVENT_START_FILE:
qDebug() << "MPV: Begin of file";
break;
case MPV_EVENT_END_FILE:
qDebug() << "MPV: Reached end of file!";
break;
case MPV_EVENT_COMMAND_REPLY:
qDebug() << "Got return of" << event->reply_userdata;
handleMpvEventCommandReply(static_cast<AsyncCmdType>(event->reply_userdata));
break;
// Explicitly ignored
case MPV_EVENT_NONE:
case MPV_EVENT_GET_PROPERTY_REPLY:
case MPV_EVENT_SET_PROPERTY_REPLY:
case MPV_EVENT_FILE_LOADED:
case MPV_EVENT_TRACKS_CHANGED:
case MPV_EVENT_TRACK_SWITCHED:
case MPV_EVENT_IDLE:
case MPV_EVENT_TICK:
case MPV_EVENT_SCRIPT_INPUT_DISPATCH:
case MPV_EVENT_CLIENT_MESSAGE:
case MPV_EVENT_AUDIO_RECONFIG:
case MPV_EVENT_METADATA_UPDATE:
case MPV_EVENT_SEEK:
case MPV_EVENT_PLAYBACK_RESTART:
case MPV_EVENT_CHAPTER_CHANGE:
case MPV_EVENT_QUEUE_OVERFLOW:
case MPV_EVENT_HOOK:
break;
}
}
int
MpvContainer::getAssSid() const noexcept
{
bool conversionOk = false;
const char *result = mpv_get_property_string(mpv, "sid");
const int ret = QString(result).toInt(&conversionOk);
return (result == nullptr || !conversionOk) ? -1 : ret;
}
void
MpvContainer::handleMpvEventCommandReply(const AsyncCmdType type) noexcept
{
switch (type) {
case AsyncCmdType::None:
break;
case AsyncCmdType::LoadAssFile:
case AsyncCmdType::ReloadAss:
sid = getAssSid();
qDebug() << "Load ASS file with id:" << sid;
break;
case AsyncCmdType::UnloadAss:
sid = getAssSid();
qDebug().nospace() << "Unload Ass, rc = " << sid;
if (sid != -1)
unloadAssFile();
break;
case AsyncCmdType::LoadFile:
sid = getAssSid();
qDebug() << "MPV - CMD: File loaded by mpv, sid =" << sid;
isPlaybackPaused = false;
mpvPause();
unloadAssFile();
break;
case AsyncCmdType::SeekTime:
qDebug() << "MPV - CMD: Seeked playback";
break;
case AsyncCmdType::TogglePlayback:
qDebug() << "MPV - CMD: Playback was toggled";
break;
}
}
void
MpvContainer::onMpvEvent() noexcept
{
while (mpv) {
mpv_event *event = mpv_wait_event(mpv, 0);
if (event == nullptr || event->event_id == MPV_EVENT_NONE)
break;
handleMpvEvent(event);
}
if (mpv == nullptr) {
qDebug() << "MPV was closed while in the event loop!";
}
}
void
MpvContainer::loadFile(const QString &filename) noexcept
{
if (filename.isEmpty())
return;
const QByteArray cFileName = filename.toUtf8();
asyncCommand(AsyncCmdType::LoadFile, { "loadfile", cFileName.data(), nullptr });
}
void
MpvContainer::loadAssFile(const QString &ass) noexcept
{
if (ass.isEmpty())
return;
const QByteArray cFileName = ass.toUtf8();
asyncCommand(AsyncCmdType::LoadAssFile, { "sub-add", cFileName.data(), nullptr });
}
void
MpvContainer::reloadAssFile() noexcept
{
asyncCommand(AsyncCmdType::ReloadAss, { "sub-reload", nullptr });
}
void
MpvContainer::printMpvError(int rc) const noexcept
{
if (rc == MPV_ERROR_SUCCESS)
return;
qCritical() << "MPV error:" << mpv_error_string(rc);
}
void
MpvContainer::mpvPlay() noexcept
{
if (isPlaybackPaused)
mpvTogglePlayback();
}
void
MpvContainer::mpvPause() noexcept
{
if (!isPlaybackPaused)
mpvTogglePlayback();
}
void
MpvContainer::mpvTogglePlayback() noexcept
{
qDebug() << "MPV: Toggling the playback";
asyncCommand(AsyncCmdType::TogglePlayback, { "cycle", "pause", "up", nullptr });
}
void
MpvContainer::asyncCommand(const AsyncCmdType cmd,
std::initializer_list<const char *> args) noexcept
{
// NOTE: const_cast here, we have faith in MPV to not change the value of
// the temporary pointers here. Should be OK anyway because the `args` init
// list is a temporary and should be discarded afer the method call.
printMpvError(mpv_command_async(mpv, cmd, const_cast<const char **>(std::data(args))));
}
void
MpvContainer::unloadAssFile() noexcept
{
asyncCommand(AsyncCmdType::UnloadAss, { "sub-remove", nullptr });
}
void
MpvContainer::seekInFile(const chrono::seconds time) noexcept
{
QByteArray seconds = QString::number(time.count()).toUtf8();
asyncCommand(AsyncCmdType::SeekTime, { "seek", seconds.data(), "absolute", nullptr });
}
Chargement en cours