File indexing completed on 2024-12-15 04:23:41

0001 /*
0002  * SPDX-FileCopyrightText: 2020 George Florea Bănuș <georgefb899@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 //#include "_debug.h"
0008 #include "mpvobject.h"
0009 //#include "application.h"
0010 //#include "playbacksettings.h"
0011 //#include "playlistitem.h"
0012 #include "track.h"
0013 
0014 #include <QDir>
0015 #include <QJsonArray>
0016 #include <QJsonDocument>
0017 #include <QJsonObject>
0018 #include <QObject>
0019 #include <QOpenGLContext>
0020 #include <QOpenGLFramebufferObject>
0021 #include <QProcess>
0022 #include <QQuickWindow>
0023 #include <QStandardPaths>
0024 #include <QtGlobal>
0025 #include <cstring>
0026 
0027 void on_mpv_redraw(void *ctx)
0028 {
0029     QMetaObject::invokeMethod(static_cast<MpvObject*>(ctx), "update", Qt::QueuedConnection);
0030 }
0031 
0032 static void *get_proc_address_mpv(void *ctx, const char *name)
0033 {
0034     Q_UNUSED(ctx)
0035 
0036     QOpenGLContext *glctx = QOpenGLContext::currentContext();
0037     if (!glctx) return nullptr;
0038 
0039     return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name)));
0040 }
0041 
0042 MpvRenderer::MpvRenderer(MpvObject *new_obj)
0043     : obj{new_obj}
0044 {}
0045 
0046 void MpvRenderer::render()
0047 {
0048     obj->window()->resetOpenGLState();
0049 
0050     QOpenGLFramebufferObject *fbo = framebufferObject();
0051     mpv_opengl_fbo mpfbo;
0052     mpfbo.fbo = static_cast<int>(fbo->handle());
0053     mpfbo.w = fbo->width();
0054     mpfbo.h = fbo->height();
0055     mpfbo.internal_format = 0;
0056 
0057     mpv_render_param params[] = {
0058         // Specify the default framebuffer (0) as target. This will
0059         // render onto the entire screen. If you want to show the video
0060         // in a smaller rectangle or apply fancy transformations, you'll
0061         // need to render into a separate FBO and draw it manually.
0062         {MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo},
0063         {MPV_RENDER_PARAM_INVALID, nullptr}
0064     };
0065     // See render_gl.h on what OpenGL environment mpv expects, and
0066     // other API details.
0067     mpv_render_context_render(obj->mpv_gl, params);
0068 
0069     obj->window()->resetOpenGLState();
0070 }
0071 
0072 QOpenGLFramebufferObject * MpvRenderer::createFramebufferObject(const QSize &size)
0073 {
0074     // init mpv_gl:
0075     if (!obj->mpv_gl)
0076     {
0077         mpv_opengl_init_params gl_init_params;
0078         gl_init_params.get_proc_address = get_proc_address_mpv;
0079         gl_init_params.get_proc_address_ctx = nullptr;
0080         mpv_render_param params[]{
0081             {MPV_RENDER_PARAM_API_TYPE, const_cast<char *>(MPV_RENDER_API_TYPE_OPENGL)},
0082             {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},
0083             {MPV_RENDER_PARAM_INVALID, nullptr}
0084         };
0085 
0086         if (mpv_render_context_create(&obj->mpv_gl, obj->mpv, params) < 0)
0087             throw std::runtime_error("failed to initialize mpv GL context");
0088         mpv_render_context_set_update_callback(obj->mpv_gl, on_mpv_redraw, obj);
0089         Q_EMIT obj->ready();
0090     }
0091 
0092     return QQuickFramebufferObject::Renderer::createFramebufferObject(size);
0093 }
0094 
0095 MpvObject::MpvObject(QQuickItem * parent)
0096     : QQuickFramebufferObject(parent)
0097     , mpv{mpv_create()}
0098     , mpv_gl(nullptr)
0099     , m_audioTracksModel(new TracksModel)
0100     , m_subtitleTracksModel(new TracksModel)
0101     //    , m_playlistModel(new PlayListModel)
0102 {
0103     if (!mpv)
0104         throw std::runtime_error("could not create mpv context");
0105 
0106     //    setProperty("terminal", "yes");
0107     //    setProperty("msg-level", "all=v");
0108 
0109     if (m_hardwareDecoding) {
0110         setProperty("hwdec", "yes");
0111     } else {
0112         setProperty("hwdec", "no");
0113     }
0114 
0115     setProperty("screenshot-template", "%x/screenshots/%n");
0116     setProperty("sub-auto", "exact");
0117 
0118     mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
0119     mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
0120     mpv_observe_property(mpv, 0, "time-remaining", MPV_FORMAT_DOUBLE);
0121     mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
0122     mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_INT64);
0123     mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
0124     mpv_observe_property(mpv, 0, "chapter", MPV_FORMAT_INT64);
0125     mpv_observe_property(mpv, 0, "aid", MPV_FORMAT_INT64);
0126     mpv_observe_property(mpv, 0, "sid", MPV_FORMAT_INT64);
0127     mpv_observe_property(mpv, 0, "secondary-sid", MPV_FORMAT_INT64);
0128     mpv_observe_property(mpv, 0, "contrast", MPV_FORMAT_INT64);
0129     mpv_observe_property(mpv, 0, "brightness", MPV_FORMAT_INT64);
0130     mpv_observe_property(mpv, 0, "gamma", MPV_FORMAT_INT64);
0131     mpv_observe_property(mpv, 0, "saturation", MPV_FORMAT_INT64);
0132 
0133     QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
0134     QString watchLaterPath = configPath.append("/georgefb/watch-later");
0135     setProperty("watch-later-directory", watchLaterPath);
0136     QDir watchLaterDir(watchLaterPath);
0137     if (!watchLaterDir.exists()) {
0138         QDir().mkdir(watchLaterPath);
0139     }
0140 
0141     if (mpv_initialize(mpv) < 0)
0142         throw std::runtime_error("could not initialize mpv context");
0143 
0144     mpv_set_wakeup_callback(mpv, MpvObject::mpvEvents, this);
0145 
0146     connect(this, &MpvObject::fileLoaded,
0147             this, &MpvObject::loadTracks);
0148 
0149     connect(this, &MpvObject::positionChanged, this, [this]() {
0150         int pos = getProperty("time-pos").toInt();
0151         double duration = getProperty("duration").toDouble();
0152         if (!m_secondsWatched.contains(pos)) {
0153             m_secondsWatched << pos;
0154             setWatchPercentage(m_secondsWatched.count() * 100 / duration);
0155         }
0156     });
0157 
0158     connect(this, &MpvObject::paused, [this]()
0159     {
0160         this->setPlaybackState(QMediaPlayer::PausedState);
0161     });
0162 
0163     connect(this, &MpvObject::playing, [this]()
0164     {
0165         this->setPlaybackState(QMediaPlayer::PlayingState);
0166     });
0167 
0168     connect(this, &MpvObject::stopped, [this]()
0169     {
0170         this->setPlaybackState(QMediaPlayer::StoppedState);
0171     });
0172 }
0173 
0174 MpvObject::~MpvObject()
0175 {
0176     // only initialized if something got drawn
0177     if (mpv_gl) {
0178         mpv_render_context_free(mpv_gl);
0179     }
0180     mpv_terminate_destroy(mpv);
0181 }
0182 
0183 //PlayListModel *MpvObject::playlistModel()
0184 //{
0185 //    return m_playlistModel;
0186 //}
0187 
0188 //void MpvObject::setPlaylistModel(PlayListModel *model)
0189 //{
0190 //    m_playlistModel = model;
0191 //}
0192 
0193 QString MpvObject::mediaTitle()
0194 {
0195     return getProperty("media-title").toString();
0196 }
0197 
0198 double MpvObject::position()
0199 {
0200     return getProperty("time-pos").toDouble()*1000;
0201 }
0202 
0203 void MpvObject::setPosition(double value)
0204 {
0205     if (value == position()) {
0206         return;
0207     }
0208     setProperty("time-pos", value);
0209     Q_EMIT positionChanged();
0210 }
0211 
0212 double MpvObject::remaining()
0213 {
0214     return getProperty("time-remaining").toDouble();
0215 }
0216 
0217 double MpvObject::duration()
0218 {
0219     return getProperty("duration").toDouble()*1000;
0220 }
0221 
0222 int MpvObject::volume()
0223 {
0224     return getProperty("volume").toInt();
0225 }
0226 
0227 void MpvObject::setVolume(int value)
0228 {
0229     if (value == volume()) {
0230         return;
0231     }
0232     setProperty("volume", value);
0233     Q_EMIT volumeChanged();
0234 }
0235 
0236 int MpvObject::chapter()
0237 {
0238     return getProperty("chapter").toInt();
0239 }
0240 
0241 void MpvObject::setChapter(int value)
0242 {
0243     if (value == chapter()) {
0244         return;
0245     }
0246     setProperty("chapter", value);
0247     Q_EMIT chapterChanged();
0248 }
0249 
0250 int MpvObject::audioId()
0251 {
0252     return getProperty("aid").toInt();
0253 }
0254 
0255 void MpvObject::setAudioId(int value)
0256 {
0257     if (value == audioId()) {
0258         return;
0259     }
0260     setProperty("aid", value);
0261     Q_EMIT audioIdChanged();
0262 }
0263 
0264 int MpvObject::subtitleId()
0265 {
0266     return getProperty("sid").toInt();
0267 }
0268 
0269 void MpvObject::setSubtitleId(int value)
0270 {
0271     if (value == subtitleId()) {
0272         return;
0273     }
0274     setProperty("sid", value);
0275     Q_EMIT subtitleIdChanged();
0276 }
0277 
0278 int MpvObject::secondarySubtitleId()
0279 {
0280     return getProperty("secondary-sid").toInt();
0281 }
0282 
0283 void MpvObject::setSecondarySubtitleId(int value)
0284 {
0285     if (value == secondarySubtitleId()) {
0286         return;
0287     }
0288     setProperty("secondary-sid", value);
0289     Q_EMIT secondarySubtitleIdChanged();
0290 }
0291 
0292 int MpvObject::contrast()
0293 {
0294     return getProperty("contrast").toInt();
0295 }
0296 
0297 void MpvObject::setContrast(int value)
0298 {
0299     if (value == contrast()) {
0300         return;
0301     }
0302     setProperty("contrast", value);
0303     Q_EMIT contrastChanged();
0304 }
0305 
0306 int MpvObject::brightness()
0307 {
0308     return getProperty("brightness").toInt();
0309 }
0310 
0311 void MpvObject::setBrightness(int value)
0312 {
0313     if (value == brightness()) {
0314         return;
0315     }
0316     setProperty("brightness", value);
0317     Q_EMIT brightnessChanged();
0318 }
0319 
0320 int MpvObject::gamma()
0321 {
0322     return getProperty("gamma").toInt();
0323 }
0324 
0325 void MpvObject::setGamma(int value)
0326 {
0327     if (value == gamma()) {
0328         return;
0329     }
0330     setProperty("gamma", value);
0331     Q_EMIT gammaChanged();
0332 }
0333 
0334 int MpvObject::saturation()
0335 {
0336     return getProperty("saturation").toInt();
0337 }
0338 
0339 void MpvObject::setSaturation(int value)
0340 {
0341     if (value == saturation()) {
0342         return;
0343     }
0344     setProperty("saturation", value);
0345     Q_EMIT saturationChanged();
0346 }
0347 
0348 double MpvObject::watchPercentage()
0349 {
0350     return m_watchPercentage;
0351 }
0352 
0353 void MpvObject::setWatchPercentage(double value)
0354 {
0355     if (m_watchPercentage == value) {
0356         return;
0357     }
0358     m_watchPercentage = value;
0359     Q_EMIT watchPercentageChanged();
0360 }
0361 
0362 bool MpvObject::hwDecoding()
0363 {
0364     if (getProperty("hwdec") == "yes") {
0365         return true;
0366     } else {
0367         return false;
0368     }
0369 }
0370 
0371 void MpvObject::setHWDecoding(bool value)
0372 {
0373     if (value) {
0374         setProperty("hwdec", "yes");
0375     } else  {
0376         setProperty("hwdec", "no");
0377     }
0378     Q_EMIT hwDecodingChanged();
0379 }
0380 
0381 QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const
0382 {
0383     window()->setPersistentOpenGLContext(true);
0384     window()->setPersistentSceneGraph(true);
0385     return new MpvRenderer(const_cast<MpvObject *>(this));
0386 }
0387 
0388 void MpvObject::mpvEvents(void *ctx)
0389 {
0390     QMetaObject::invokeMethod(static_cast<MpvObject*>(ctx), "eventHandler", Qt::QueuedConnection);
0391 }
0392 
0393 void MpvObject::eventHandler()
0394 {
0395     while (mpv) {
0396         mpv_event *event = mpv_wait_event(mpv, 0);
0397         if (event->event_id == MPV_EVENT_NONE) {
0398             break;
0399         }
0400         switch (event->event_id) {
0401 
0402         case MPV_EVENT_START_FILE:
0403             //               clearTrackState();
0404             //               Q_EMIT sourceChanged();
0405             setStatus(QMediaPlayer::LoadingMedia);
0406             break;
0407 
0408         case MPV_EVENT_SEEK:
0409             setStatus(QMediaPlayer::BufferingMedia);
0410             break;
0411 
0412         case MPV_EVENT_PLAYBACK_RESTART: {
0413             bool paused = this->getProperty("pause").toBool();
0414             if (paused)
0415                 Q_EMIT this->paused();
0416             else
0417                 Q_EMIT this->playing();
0418             break;
0419         }
0420 
0421         case MPV_EVENT_FILE_LOADED: {
0422             Q_EMIT fileLoaded();
0423             setStatus(QMediaPlayer::LoadedMedia);
0424             Q_EMIT this->playing();
0425             break;
0426         }
0427 
0428         case MPV_EVENT_END_FILE: {
0429             auto prop = (mpv_event_end_file *)event->data;
0430             if (prop->reason == MPV_END_FILE_REASON_EOF ||
0431                     prop ->reason == MPV_END_FILE_REASON_ERROR) {
0432                 Q_EMIT endOfFile();
0433                 setStatus(QMediaPlayer::EndOfMedia);
0434                 Q_EMIT this->stopped();
0435             }
0436             break;
0437         }
0438         case MPV_EVENT_PROPERTY_CHANGE: {
0439             mpv_event_property *prop = (mpv_event_property *)event->data;
0440 
0441             if (strcmp(prop->name, "time-pos") == 0) {
0442                 if (prop->format == MPV_FORMAT_DOUBLE) {
0443                     Q_EMIT positionChanged();
0444                 }
0445             } else if (strcmp(prop->name, "media-title") == 0) {
0446                 if (prop->format == MPV_FORMAT_STRING) {
0447                     Q_EMIT mediaTitleChanged();
0448                 }
0449             } else if (strcmp(prop->name, "time-remaining") == 0) {
0450                 if (prop->format == MPV_FORMAT_DOUBLE) {
0451                     Q_EMIT remainingChanged();
0452                 }
0453             } else if (strcmp(prop->name, "duration") == 0) {
0454                 if (prop->format == MPV_FORMAT_DOUBLE) {
0455                     Q_EMIT durationChanged();
0456                 }
0457             } else if (strcmp(prop->name, "volume") == 0) {
0458                 if (prop->format == MPV_FORMAT_INT64) {
0459                     Q_EMIT volumeChanged();
0460                 }
0461             } else if (strcmp(prop->name, "pause") == 0) {
0462 
0463                 if (prop->format == MPV_FORMAT_FLAG) {
0464                     int pause = *(int *)prop->data;
0465                     bool paused = pause == 1;
0466                     if (paused)
0467                         Q_EMIT this->paused();
0468                     else {
0469                         if(this->getProperty("core-idle").toBool())
0470                         {
0471                             Q_EMIT this->stopped();
0472                             setStatus(QMediaPlayer::NoMedia);
0473                         }
0474                         else
0475                         {
0476                             Q_EMIT this->playing();
0477                             Q_EMIT this->stopped();
0478                             setStatus(QMediaPlayer::LoadedMedia);
0479                         }
0480                     }
0481                 }
0482 
0483             } else if (strcmp(prop->name, "chapter") == 0) {
0484                 if (prop->format == MPV_FORMAT_INT64) {
0485                     Q_EMIT chapterChanged();
0486                 }
0487             } else if (strcmp(prop->name, "aid") == 0) {
0488                 if (prop->format == MPV_FORMAT_INT64) {
0489                     Q_EMIT audioIdChanged();
0490                 }
0491             } else if (strcmp(prop->name, "sid") == 0) {
0492                 if (prop->format == MPV_FORMAT_INT64) {
0493                     Q_EMIT subtitleIdChanged();
0494                 } else {
0495                     Q_EMIT subtitleIdChanged();
0496                 }
0497             } else if (strcmp(prop->name, "secondary-sid") == 0) {
0498                 if (prop->format == MPV_FORMAT_INT64) {
0499                     Q_EMIT secondarySubtitleIdChanged();
0500                 } else {
0501                     Q_EMIT secondarySubtitleIdChanged();
0502                 }
0503             } else if (strcmp(prop->name, "contrast") == 0) {
0504                 if (prop->format == MPV_FORMAT_INT64) {
0505                     Q_EMIT contrastChanged();
0506                 }
0507             } else if (strcmp(prop->name, "brightness") == 0) {
0508                 if (prop->format == MPV_FORMAT_INT64) {
0509                     Q_EMIT brightnessChanged();
0510                 }
0511             } else if (strcmp(prop->name, "gamma") == 0) {
0512                 if (prop->format == MPV_FORMAT_INT64) {
0513                     Q_EMIT gammaChanged();
0514                 }
0515             } else if (strcmp(prop->name, "saturation") == 0) {
0516                 if (prop->format == MPV_FORMAT_INT64) {
0517                     Q_EMIT saturationChanged();
0518                 }
0519             }
0520             break;
0521         }
0522 
0523         case MPV_EVENT_LOG_MESSAGE: {
0524             struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
0525             qDebug() << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
0526 
0527             if (msg->log_level == MPV_LOG_LEVEL_ERROR) {
0528                 //                    lastErrorString = QString::fromUtf8(msg->text);
0529                 Q_EMIT error(QString::fromUtf8(msg->text));
0530                 setStatus(QMediaPlayer::InvalidMedia);
0531 
0532             }
0533 
0534             break;
0535         }
0536 
0537         default: ;
0538             // Ignore uninteresting or unknown events.
0539         }
0540     }
0541 }
0542 
0543 void MpvObject::loadTracks()
0544 {
0545     m_subtitleTracks.clear();
0546     m_audioTracks.clear();
0547 
0548     auto none = new Track();
0549     none->setId(0);
0550     none->setTitle("None");
0551     m_subtitleTracks.insert(0, none);
0552 
0553     const QList<QVariant> tracks = getProperty("track-list").toList();
0554     int subIndex = 1;
0555     int audioIndex = 0;
0556     for (const auto &track : tracks) {
0557         const auto t = track.toMap();
0558         if (track.toMap()["type"] == "sub") {
0559             auto track = new Track();
0560             track->setCodec(t["codec"].toString());
0561             track->setType(t["type"].toString());
0562             track->setDefaut(t["default"].toBool());
0563             track->setDependent(t["dependent"].toBool());
0564             track->setForced(t["forced"].toBool());
0565             track->setId(t["id"].toLongLong());
0566             track->setSrcId(t["src-id"].toLongLong());
0567             track->setFfIndex(t["ff-index"].toLongLong());
0568             track->setLang(t["lang"].toString());
0569             track->setTitle(t["title"].toString());
0570             track->setIndex(subIndex);
0571 
0572             m_subtitleTracks.insert(subIndex, track);
0573             subIndex++;
0574         }
0575         if (track.toMap()["type"] == "audio") {
0576             auto track = new Track();
0577             track->setCodec(t["codec"].toString());
0578             track->setType(t["type"].toString());
0579             track->setDefaut(t["default"].toBool());
0580             track->setDependent(t["dependent"].toBool());
0581             track->setForced(t["forced"].toBool());
0582             track->setId(t["id"].toLongLong());
0583             track->setSrcId(t["src-id"].toLongLong());
0584             track->setFfIndex(t["ff-index"].toLongLong());
0585             track->setLang(t["lang"].toString());
0586             track->setTitle(t["title"].toString());
0587             track->setIndex(audioIndex);
0588 
0589             m_audioTracks.insert(audioIndex, track);
0590             audioIndex++;
0591         }
0592     }
0593     m_subtitleTracksModel->setTracks(m_subtitleTracks);
0594 
0595     qDebug() << "Audio tracks"  << m_audioTracks << m_audioTracks.size();
0596 
0597     m_audioTracksModel->setTracks(m_audioTracks);
0598 
0599     Q_EMIT audioTracksModelChanged();
0600     Q_EMIT subtitleTracksModelChanged();
0601 }
0602 
0603 void MpvObject::play()
0604 {
0605     this->setProperty("pause", false);
0606 }
0607 
0608 void MpvObject::stop()
0609 {
0610     this->command(QStringList () << "stop" << "");
0611     Q_EMIT this->stopped();
0612 }
0613 
0614 void MpvObject::pause()
0615 {
0616     this->setProperty("pause", true);
0617 }
0618 
0619 void MpvObject::seek(const double &value)
0620 {
0621     command(QStringList() << "seek" << QString::number(value/1000) << "absolute");
0622 }
0623 
0624 void MpvObject::setHardwareDecoding(bool hardwareDecoding)
0625 {
0626     if (m_hardwareDecoding == hardwareDecoding)
0627         return;
0628 
0629     m_hardwareDecoding = hardwareDecoding;
0630 
0631     if (m_hardwareDecoding) {
0632         setProperty("hwdec", "yes");
0633     } else {
0634         setProperty("hwdec", "no");
0635     }
0636 
0637     Q_EMIT hardwareDecodingChanged(m_hardwareDecoding);
0638 }
0639 
0640 TracksModel *MpvObject::subtitleTracksModel() const
0641 {
0642     return m_subtitleTracksModel;
0643 }
0644 
0645 TracksModel *MpvObject::audioTracksModel() const
0646 {
0647     return m_audioTracksModel;
0648 }
0649 
0650 int MpvObject::setProperty(const QString &name, const QVariant &value)
0651 {
0652     return mpv::qt::set_property(mpv, name, value);
0653 }
0654 
0655 void MpvObject::setSource(QUrl url)
0656 {
0657     if (m_source == url)
0658         return;
0659 
0660     m_source = url;
0661 
0662     if(m_autoPlay)
0663     {
0664         this->playUrl();
0665     }
0666 
0667     Q_EMIT sourceChanged(m_source);
0668 }
0669 
0670 void MpvObject::setAutoPlay(bool autoPlay)
0671 {
0672     if (m_autoPlay == autoPlay)
0673         return;
0674 
0675     m_autoPlay = autoPlay;
0676     Q_EMIT autoPlayChanged(m_autoPlay);
0677 }
0678 
0679 QVariant MpvObject::getProperty(const QString &name)
0680 {
0681     auto value = mpv::qt::get_property(mpv, name);
0682     return value;
0683 }
0684 
0685 QVariant MpvObject::command(const QVariant &params)
0686 {
0687     return mpv::qt::command(mpv, params);
0688 }
0689 
0690 QUrl MpvObject::source() const
0691 {
0692     return m_source;
0693 }
0694 
0695 bool MpvObject::autoPlay() const
0696 {
0697     return m_autoPlay;
0698 }
0699 
0700 void MpvObject::setPlaybackState(const QMediaPlayer::State &state)
0701 {
0702     m_playbackState = state;
0703     Q_EMIT this->playbackStateChanged(m_playbackState);
0704 }
0705 
0706 void MpvObject::setStatus(const QMediaPlayer::MediaStatus  &status)
0707 {
0708     m_status = status;
0709     Q_EMIT this->statusChanged(m_status);
0710 }
0711 
0712 QMediaPlayer::State MpvObject::getPlaybackState() const
0713 {
0714     return m_playbackState;
0715 }
0716 
0717 QMediaPlayer::MediaStatus MpvObject::getStatus() const
0718 {
0719     return m_status;
0720 }
0721 
0722 bool MpvObject::hardwareDecoding() const
0723 {
0724     return m_hardwareDecoding;
0725 }
0726 
0727 void MpvObject::playUrl()
0728 {
0729     if(!m_source.isEmpty() && m_source.isValid())
0730     {
0731         qDebug() << "request play file" << m_source;
0732 
0733         if(m_source.isLocalFile())
0734             command(QStringList{"loadfile", m_source.toLocalFile()});
0735         else
0736             command(QStringList{"loadfile", m_source.toString()});
0737     }
0738 
0739     if (m_playbackState == QMediaPlayer::PausedState)
0740         play();
0741 }