MpvAudioOutput::MpvAudioOutput() : state_(AudioState::Stopped), seek_offset_(-1), volumeNeverSet_(true) { setlocale(LC_NUMERIC, "C"); handle_ = mpv::qt::Handle::FromRawHandle(mpv_create()); if (static_cast<mpv_handle *>(handle_) == nullptr) qDebug() << "Cannot mpv_create()"; set_option("video", "no"); set_option("softvol", "yes"); set_option("ytdl", "yes"); set_property("audio-client-name", "fubar"); int r = mpv_initialize(handle_); if (r < 0) { qDebug() << "Failed to initialize mpv backend: " << mpv_error_string(r); // raise Exception(""); } thread_.reset(new std::thread([this] { event_loop(); })); observe_property("playback-time"); observe_property("idle", MPV_FORMAT_FLAG); observe_property("pause", MPV_FORMAT_FLAG); observe_property("duration", MPV_FORMAT_DOUBLE); observe_property("metadata", MPV_FORMAT_NODE); r = mpv_request_log_messages(handle_, "warn"); if (r < 0) qDebug() << "mpv_request_log_messages failed: " << mpv_error_string(r); }
static inline void check_error(int status) { if (status < 0) { printf("mpv API error: %s\n", mpv_error_string(status)); exit(1); } }
void MpvHandler::HandleErrorCode(int error_code) { if(error_code >= 0) return; QString error = mpv_error_string(error_code); if(error != QString()) emit messageSignal(error+"\n"); }
bool MpvAudioOutput::set_property(const QString &name, const QVariant &v) { int r = set_property_variant(handle_, name, v); if (r < 0) { qDebug() << "Failed to set property: " << name << " to " << v << ": " << mpv_error_string(r); return false; } return true; }
void mpv_check_error(int status) { void *array[10]; size_t size; if(status < 0) { size = (size_t)backtrace(array, 10); g_critical("MPV API error: %s\n", mpv_error_string(status)); backtrace_symbols_fd(array, (int)size, STDERR_FILENO); exit(EXIT_FAILURE); } }
gint gmpv_mpv_obj_command_string(GmpvMpvObj *mpv, const gchar *cmd) { gint rc = MPV_ERROR_UNINITIALIZED; if(mpv->mpv_ctx) { rc = mpv_command_string(mpv->mpv_ctx, cmd); } if(rc < 0) { g_warning( "Failed to run mpv command string \"%s\". " "Reason: %s.", cmd, mpv_error_string(rc) ); } return rc; }
gint gmpv_mpv_obj_set_property_string( GmpvMpvObj *mpv, const gchar *name, const char *data ) { gint rc = MPV_ERROR_UNINITIALIZED; if(mpv->mpv_ctx) { rc = mpv_set_property_string(mpv->mpv_ctx, name, data); } if(rc < 0) { g_info( "Failed to set property \"%s\" as string. Reason: %s.", name, mpv_error_string(rc) ); } return rc; }
gint gmpv_mpv_obj_set_property_flag( GmpvMpvObj *mpv, const gchar *name, gboolean value ) { gint rc = MPV_ERROR_UNINITIALIZED; if(mpv->mpv_ctx) { rc = mpv_set_property (mpv->mpv_ctx, name, MPV_FORMAT_FLAG, &value); } if(rc < 0) { g_info( "Failed to set property \"%s\" as flag. Reason: %s.", name, mpv_error_string(rc) ); } return rc; }
gint gmpv_mpv_obj_command(GmpvMpvObj *mpv, const gchar **cmd) { gint rc = MPV_ERROR_UNINITIALIZED; if(mpv->mpv_ctx) { rc = mpv_command(mpv->mpv_ctx, cmd); } if(rc < 0) { gchar *cmd_str = g_strjoinv(" ", (gchar **)cmd); g_warning( "Failed to run mpv command \"%s\". Reason: %s.", cmd_str, mpv_error_string(rc) ); g_free(cmd_str); } return rc; }
gint gmpv_mpv_obj_set_property( GmpvMpvObj *mpv, const gchar *name, mpv_format format, void *data ) { gint rc = MPV_ERROR_UNINITIALIZED; if(mpv->mpv_ctx) { rc = mpv_set_property(mpv->mpv_ctx, name, format, data); } if(rc < 0) { g_info( "Failed to set property \"%s\" using mpv format %d. " "Reason: %s.", name, format, mpv_error_string(rc) ); } return rc; }
static gboolean mpv_event_handler(gpointer data) { GmpvMpvObj *mpv = data; gboolean done = !mpv; while(!done) { mpv_event *event = mpv->mpv_ctx? mpv_wait_event(mpv->mpv_ctx, 0): NULL; if(!event) { done = TRUE; } else if(event->event_id == MPV_EVENT_PROPERTY_CHANGE) { mpv_event_property *prop = event->data; mpv_prop_change_handler(mpv, prop); g_signal_emit_by_name( mpv, "mpv-prop-change", prop->name ); } else if(event->event_id == MPV_EVENT_IDLE) { if(mpv->state.loaded) { mpv->state.loaded = FALSE; gmpv_mpv_obj_set_property_flag (mpv, "pause", TRUE); gmpv_playlist_reset(mpv->playlist); } mpv->state.init_load = FALSE; } else if(event->event_id == MPV_EVENT_FILE_LOADED) { mpv->state.loaded = TRUE; mpv->state.init_load = FALSE; mpv_obj_update_playlist(mpv); } else if(event->event_id == MPV_EVENT_END_FILE) { mpv_event_end_file *ef_event = event->data; mpv->state.init_load = FALSE; if(mpv->state.loaded) { mpv->state.new_file = FALSE; } if(ef_event->reason == MPV_END_FILE_REASON_ERROR) { const gchar *err; gchar *msg; err = mpv_error_string(ef_event->error); msg = g_strdup_printf ( _("Playback was terminated " "abnormally. Reason: %s."), err ); gmpv_mpv_obj_set_property_flag (mpv, "pause", TRUE); g_signal_emit_by_name(mpv, "mpv-error", msg); g_free(msg); } } else if(event->event_id == MPV_EVENT_VIDEO_RECONFIG) { if(mpv->state.new_file) { gmpv_mpv_opt_handle_autofit(mpv); } } else if(event->event_id == MPV_EVENT_PLAYBACK_RESTART) { g_signal_emit_by_name(mpv, "mpv-playback-restart"); } else if(event->event_id == MPV_EVENT_LOG_MESSAGE) { mpv_obj_log_handler(mpv, event->data); } else if(event->event_id == MPV_EVENT_SHUTDOWN || event->event_id == MPV_EVENT_NONE) { done = TRUE; } if(event) { if(mpv->event_callback) { mpv->event_callback (event, mpv->event_callback_data); } if(mpv->mpv_ctx) { g_signal_emit_by_name (mpv, "mpv-event", event->event_id); } else { done = TRUE; } } } return FALSE; }
bool MpvHandler::event(QEvent *event) { if(event->type() == QEvent::User) { while(mpv) { mpv_event *event = mpv_wait_event(mpv, 0); if(event == nullptr || event->event_id == MPV_EVENT_NONE) { break; } if(event->error < 0) { ShowText(mpv_error_string(event->error)); emit messageSignal(mpv_error_string(event->error)); } switch (event->event_id) { case MPV_EVENT_PROPERTY_CHANGE: { mpv_event_property *prop = (mpv_event_property*)event->data; if(QString(prop->name) == "playback-time") // playback-time does the same thing as time-pos but works for streaming media { if(prop->format == MPV_FORMAT_DOUBLE) { setTime((int)*(double*)prop->data); lastTime = time; } } else if(QString(prop->name) == "volume") { if(prop->format == MPV_FORMAT_DOUBLE) setVolume((int)*(double*)prop->data); } else if(QString(prop->name) == "sid") { if(prop->format == MPV_FORMAT_INT64) setSid(*(int*)prop->data); } else if(QString(prop->name) == "aid") { if(prop->format == MPV_FORMAT_INT64) setAid(*(int*)prop->data); } else if(QString(prop->name) == "sub-visibility") { if(prop->format == MPV_FORMAT_FLAG) setSubtitleVisibility((bool)*(unsigned*)prop->data); } else if(QString(prop->name) == "mute") { if(prop->format == MPV_FORMAT_FLAG) setMute((bool)*(unsigned*)prop->data); } break; } case MPV_EVENT_IDLE: fileInfo.length = 0; setTime(0); setPlayState(Mpv::Idle); break; // these two look like they're reversed but they aren't. the names are misleading. case MPV_EVENT_START_FILE: setPlayState(Mpv::Loaded); break; case MPV_EVENT_FILE_LOADED: setPlayState(Mpv::Started); LoadFileInfo(); SetProperties(); case MPV_EVENT_UNPAUSE: setPlayState(Mpv::Playing); break; case MPV_EVENT_PAUSE: setPlayState(Mpv::Paused); break; case MPV_EVENT_END_FILE: setPlayState(Mpv::Stopped); break; case MPV_EVENT_SHUTDOWN: QCoreApplication::quit(); break; case MPV_EVENT_LOG_MESSAGE: { mpv_event_log_message *message = static_cast<mpv_event_log_message*>(event->data); if(message != nullptr) emit messageSignal(message->text); break; } default: // unhandled events break; } } return true; } return QObject::event(event); }
void MpvAudioOutput::set_option(const QString &name, const QVariant &value) { auto r = mpv::qt::set_option_variant(handle_, name, value); if (r < 0) qDebug() << "Failed mpv_set_option " << name << ": " << mpv_error_string(r); }
void MpvAudioOutput::observe_property(const std::string &name, mpv_format format) { auto r = mpv_observe_property(handle_, 0, name.c_str(), format); if (r < 0) qDebug() << "Failed mpv_observe_property " << name.c_str() << ": " << mpv_error_string(r); }
void MpvAudioOutput::command(const QVariant &args) { int r = mpv::qt::command_variant2(handle_, args); if (r < 0) qDebug() << "Command failed: " << args << " " << mpv_error_string(r); }
void MpvAudioOutput::event_loop() { while (true) { auto event = mpv_wait_event(handle_, -1); // qDebug() << "mpv event " << mpv_event_name(event->event_id); switch (event->event_id) { case MPV_EVENT_SHUTDOWN: return; case MPV_EVENT_QUEUE_OVERFLOW: qWarning() << "mpv queue overflow"; break; case MPV_EVENT_START_FILE: setState(AudioState::Buffering); break; case MPV_EVENT_FILE_LOADED: setState(AudioState::Playing); emit currentSourceChanged(); setVolume(); if (seek_offset_ != -1) { seek(seek_offset_); seek_offset_ = -1; } break; case MPV_EVENT_END_FILE: { auto end_ev = reinterpret_cast<mpv_event_end_file *>(event->data); if (end_ev->reason == MPV_END_FILE_REASON_ERROR) qWarning() << "Ended file: " << mpv_error_string(end_ev->error); break; } case MPV_EVENT_LOG_MESSAGE: { auto log = reinterpret_cast<mpv_event_log_message *>(event->data); qDebug() << "mpv [" << log->prefix << "] " << log->text; break; } case MPV_EVENT_PROPERTY_CHANGE: { auto prop = reinterpret_cast<mpv_event_property *>(event->data); if (prop->format != MPV_FORMAT_NONE && prop->data) { if (std::string(prop->name) == "playback-time") { std::string pos(*(reinterpret_cast<char **>(prop->data))); emit tick(pos_to_qint64(pos)); if (volumeNeverSet_) setVolume(); } else if (std::string(prop->name) == "idle") { int idle = *reinterpret_cast<int *>(prop->data); if (idle) { setState(AudioState::Stopped); emit finished(); } else setState(AudioState::Playing); } else if (std::string(prop->name) == "pause") { int pause = *reinterpret_cast<int *>(prop->data); if (pause) setState(AudioState::Paused); else if (state_ == AudioState::Paused) setState(AudioState::Playing); } else if (std::string(prop->name) == "duration") { double v = *reinterpret_cast<double *>(prop->data); emit durationChanged(v); } else if (std::string(prop->name) == "metadata") { emit metadataChanged(get_property("media-title").toString(), get_property("audio-format").toString(), get_property("audio-params/samplerate").toInt()); } } break; } default: break; } } }
static inline void check_mpv_error(int status) { if (status < 0) ERRORF("Mpv error %s\n", mpv_error_string(status)); }
void PlayerComponent::handleMpvEvent(mpv_event *event) { switch (event->event_id) { case MPV_EVENT_START_FILE: { m_CurrentUrl = mpv::qt::get_property_variant(m_mpv, "path").toString(); m_playbackStartSent = false; break; } case MPV_EVENT_FILE_LOADED: { emit playing(m_CurrentUrl); break; } case MPV_EVENT_END_FILE: { mpv_event_end_file *end_file = (mpv_event_end_file *)event->data; switch (end_file->reason) { case MPV_END_FILE_REASON_EOF: emit finished(m_CurrentUrl); break; case MPV_END_FILE_REASON_ERROR: emit error(end_file->error, mpv_error_string(end_file->error)); break; default: emit stopped(m_CurrentUrl); break; } emit playbackEnded(m_CurrentUrl); m_CurrentUrl = ""; m_restoreDisplayTimer.start(0); break; } case MPV_EVENT_IDLE: { emit playbackAllDone(); break; } case MPV_EVENT_PLAYBACK_RESTART: { // it's also sent after seeks are completed if (!m_playbackStartSent) emit playbackStarting(); m_playbackStartSent = true; break; } case MPV_EVENT_PROPERTY_CHANGE: { mpv_event_property *prop = (mpv_event_property *)event->data; if (strcmp(prop->name, "pause") == 0 && prop->format == MPV_FORMAT_FLAG) { int state = *(int *)prop->data; emit paused(state); } else if (strcmp(prop->name, "cache-buffering-state") == 0 && prop->format == MPV_FORMAT_INT64) { int64_t percentage = *(int64_t *)prop->data; emit buffering(percentage); } else if (strcmp(prop->name, "playback-time") == 0 && prop->format == MPV_FORMAT_DOUBLE) { double pos = *(double*)prop->data; if (fabs(pos - m_lastPositionUpdate) > 0.25) { quint64 ms = (quint64)(qMax(pos * 1000.0, 0.0)); emit positionUpdate(ms); m_lastPositionUpdate = pos; } } else if (strcmp(prop->name, "vo-configured") == 0) { int state = prop->format == MPV_FORMAT_FLAG ? *(int *)prop->data : 0; emit windowVisible(state); } else if (strcmp(prop->name, "duration") == 0) { if (prop->format == MPV_FORMAT_DOUBLE) emit updateDuration(*(double *)prop->data * 1000.0); } else if (strcmp(prop->name, "audio-device-list") == 0) { updateAudioDeviceList(); } break; } case MPV_EVENT_LOG_MESSAGE: { mpv_event_log_message *msg = (mpv_event_log_message *)event->data; // Strip the trailing '\n' size_t len = strlen(msg->text); if (len > 0 && msg->text[len - 1] == '\n') len -= 1; QString logline = QString::fromUtf8(msg->prefix) + ": " + QString::fromUtf8(msg->text, len); if (msg->log_level >= MPV_LOG_LEVEL_V) QLOG_DEBUG() << qPrintable(logline); else if (msg->log_level >= MPV_LOG_LEVEL_INFO) QLOG_INFO() << qPrintable(logline); else if (msg->log_level >= MPV_LOG_LEVEL_WARN) QLOG_WARN() << qPrintable(logline); else QLOG_ERROR() << qPrintable(logline); break; } case MPV_EVENT_CLIENT_MESSAGE: { mpv_event_client_message *msg = (mpv_event_client_message *)event->data; // This happens when the player is about to load the file, but no actual loading has taken part yet. // We use this to block loading until we explicitly tell it to continue. if (msg->num_args >= 3 && !strcmp(msg->args[0], "hook_run") && !strcmp(msg->args[1], "1")) { QString resume_id = QString::fromUtf8(msg->args[2]); // Calling this lambda will instruct mpv to continue loading the file. auto resume = [=] { QLOG_INFO() << "resuming loading"; mpv::qt::command_variant(m_mpv, QStringList() << "hook-ack" << resume_id); }; if (switchDisplayFrameRate()) { // Now wait for some time for mode change - this is needed because mode changing can take some // time, during which the screen is black, and initializing hardware decoding could fail due // to various strange OS-related reasons. // (Better hope the user doesn't try to exit Konvergo during mode change.) int pause = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "refreshrate.delay").toInt() * 1000; QLOG_INFO() << "waiting" << pause << "msec after rate switch before loading"; QTimer::singleShot(pause, resume); } else { resume(); } break; } } default:; /* ignore */ } }
static inline void check_mpv_error(int status) { if (status < 0) g_error("{GtPlayerMpv} Mpv error %s\n", mpv_error_string(status)); }