root/src/mpv.cpp

#include "mpv.h"
#ifdef USE_MPV

Mpv::Mpv(Library &library, const BaseConfig &baseConfig, QObject *parent) :
    VideoPlayer(library, baseConfig, parent),
    handle(NULL)
{
    connect(this, &VideoPlayer::playbackEnded, this, &Mpv::resetMpv);
    connect(this, &Mpv::mpv_events, this, &Mpv::on_mpv_events,
                Qt::QueuedConnection);
}

Mpv::~Mpv() {
    if (this->handle) {
        mpv_terminate_destroy(this->handle);
        this->handle = NULL;
    }
}

void Mpv::resetMpv() {
    if (this->handle) {
        mpv_terminate_destroy(this->handle);
        this->handle = NULL;
    }
    this->pauseStatus = true;
}

void Mpv::stop() {
    emit playbackCanceled();
}

void Mpv::backwards(const int seconds) {
    simpleCommand2("seek", QString::number(-seconds).toUtf8().constData());
}
void Mpv::forwards(const int seconds) {
    simpleCommand2("seek", QString::number(seconds).toUtf8().constData());
}

float Mpv::incrementVolume() {
    return -1;
}

float Mpv::decrementVolume() {
    return -1;
}


static void mpvWakeup(void* data) {
    Mpv* mpvPlayer = static_cast<Mpv*>(data);
    emit mpvPlayer->mpv_events();
}

bool Mpv::playFileImpl(QString filepath, const TvShowPlayerSettings &settings) {
    bool noFailure = true;
    // TODO use child handles to allow partial shutdown while keeping the core initalized
    if (NULL == this->handle) {
        qDebug() << "initalizing mpv";
        this->handle = mpv_create();
        if (!this->handle) {
            qDebug() << "failed creating mpv context";
            return false;
        }

        noFailure *= checkError(mpv_set_option_string(this->handle, "input-default-bindings", "yes"));
        mpv_set_option_string(this->handle, "input-vo-keyboard", "yes");
        noFailure *= checkError(mpv_set_option_string(this->handle, "config", "yes"));

        noFailure *= checkError(mpv_observe_property(this->handle, 0, "time-pos", MPV_FORMAT_DOUBLE));
        noFailure *= checkError(mpv_observe_property(this->handle, 0, "pause", MPV_FORMAT_FLAG));

        mpv_set_wakeup_callback(this->handle, mpvWakeup, this);

        noFailure *= checkError(mpv_initialize(this->handle));
        qDebug() << "mpv ready";
    }

    // toUtf8 is freed after the ';', but mpv commands need 2 lines of code;
    noFailure *= simpleCommand2("loadfile", filepath.toUtf8().constData());

    if (settings.audioTrack != 0) {
        double atrack = settings.audioTrack;
        if (atrack > 0) {
            checkError(mpv_set_property(this->handle, "aid", MPV_FORMAT_DOUBLE, &atrack));
        }

    }
    if (settings.subtileTrack != 0) {
        double strack = settings.subtileTrack;
        if (strack < 0) {
            checkError(mpv_set_property_string(this->handle, "sub-visibility", "no"));
        } else {
            checkError(mpv_set_property(this->handle, "sid", MPV_FORMAT_DOUBLE, &strack));
        }
    }

    pauseStatus = false;
    if (!noFailure) {
        qDebug() << "mpv setup failed: exit";
        exit(1);
    }
    qDebug() << "mpv playback started: " << filepath;

    return noFailure;
}

// this thing is a fix that avoids QString::toUtf8() pointers to free because
// the temporary QByteArray's destructor is called on the ';' of the args[] line
// Let's hope no compiler will fuckup the inline of something like this.
bool Mpv::simpleCommand2(const char* commandName, const char* commandValue) {
    if (!this->handle) {
        return false;
    }
    const char* args[] = {commandName, commandValue, NULL};
    return checkError(mpv_command(this->handle, args));
}

void Mpv::pauseImpl() {
    if (this->handle && !this->pauseStatus) {
        int value = 1;
        checkError(mpv_set_property(this->handle, "pause", MPV_FORMAT_FLAG, &value));
    }
}

void Mpv::unPauseImpl() {
    if (this->handle && this->pauseStatus) {
        int value = 0;
        checkError(mpv_set_property(this->handle, "pause", MPV_FORMAT_FLAG, &value));
    }
}

void Mpv::getExactProgress() {
    if (!this->handle) {
        return;
    }
    double second = -1;
    int error = mpv_get_property(this->handle, "time-pos", MPV_FORMAT_DOUBLE, &second);
    if (!error) {
        emit exactProgressReady(second);
    }
}
bool Mpv::checkError(int errorCode) {
    if (errorCode != 0) {
        qDebug() << "mpv API error:" << mpv_error_string(errorCode);
        return false;
    }
    return true;
}

void Mpv::handle_mpv_event(mpv_event *event) {
    switch (event->event_id) {
    case MPV_EVENT_PROPERTY_CHANGE: {
        mpv_event_property* prop = (mpv_event_property*)event->data;
        // qDebug() << "got event" << prop->name;
        if (0 == strcmp(prop->name, "time-pos")) {
            if (prop->format == MPV_FORMAT_DOUBLE) {
                double time = *(double *)prop->data;
                double oldTime = this->nowPlaying.seconds;
                double diff = time - oldTime;
                diff = diff < 0.0 ? -diff : diff;
                // qDebug() << "playing-t:" << time << "diff" << diff << "sec" << oldTime;
                this->nowPlaying.seconds = time;
                if (diff > 2.f) {
                    emit jumped(time);
                }
            } else if (prop->format == MPV_FORMAT_NONE) {
                // The property is unavailable, which probably means playback
                // was stopped.
                // emit jumped(0);
            }
        } else if (0 == strcmp(prop->name, "pause")) {
            if (prop->format == MPV_FORMAT_FLAG) {
                qDebug() << "pause:" << *(int*)prop->data;
                bool newPauseStatus = *(int*)prop->data;
                bool changed = newPauseStatus != this->pauseStatus;
                this->pauseStatus = newPauseStatus;
                if (changed) {
                    if (pauseStatus) {
                        emit paused();
                    } else {
                        emit unpaused();
                    }
                }
            }
        }
        break;
    }
    case MPV_EVENT_IDLE: {
        qDebug() << "mpv idle";
        // emit playbackEndedNormally();
        break;
    }
    case MPV_EVENT_START_FILE: {
        char* startFilePath = mpv_get_property_string(this->handle, "path");
        qDebug() << "mpv starting file:" << startFilePath;
        mpv_free(startFilePath);
        break;
    }
    case MPV_EVENT_FILE_LOADED: {
        qDebug() << "mpv file-loaded";
        break;
    }
    case MPV_EVENT_END_FILE: {
        qDebug() << "mpv end-file";
        mpv_event_end_file* endFile = (mpv_event_end_file*)event->data;
        switch (endFile->reason) {
        case MPV_END_FILE_REASON_EOF: {
            qDebug() << "mpv MPV_END_FILE_REASON_EOF";
            emit playbackEndedNormally();
            break;
        }
        case MPV_END_FILE_REASON_STOP: {
            qDebug() << "mpv MPV_END_FILE_REASON_STOP";
            emit playbackEndedNormally();
            break;
        }
        case MPV_END_FILE_REASON_QUIT: {
            qDebug() << "mpv MPV_END_FILE_REASON_QUIT";
            emit playbackEndedNormally();
            break;
        }
        case MPV_END_FILE_REASON_ERROR: {
            qDebug() << "mpv MPV_END_FILE_REASON_ERROR";
            qDebug() << "mpv error:" <<  mpv_error_string(endFile->error);
            emit playbackCanceled();
            break;
        }
        case MPV_END_FILE_REASON_REDIRECT: {
            qDebug() << "mpv MPV_END_FILE_REASON_REDIRECT";
            break;
        }
        default: emit playbackEndedNormally();
        }
        break;
    }
    case MPV_EVENT_SHUTDOWN: {
        qDebug() << "mpv shutdown";
        emit playbackEndedNormally();
        break;
    }
    default: qDebug() << "mpv-event:" << mpv_event_name(event->event_id);
    }
}

void Mpv::on_mpv_events() {
    while (this->handle) {
        mpv_event* event = mpv_wait_event(this->handle, 0);
        if (event->event_id == MPV_EVENT_NONE) {
            break;
        }
        handle_mpv_event(event);
    }
}

#endif // USE_MPV