Skip to content

Instantly share code, notes, and snippets.

@underdoeg
Last active May 4, 2023 14:53
Show Gist options
  • Save underdoeg/2e6d0e606a962458e3126ecff7f76815 to your computer and use it in GitHub Desktop.
Save underdoeg/2e6d0e606a962458e3126ecff7f76815 to your computer and use it in GitHub Desktop.
mpv integration into raylib
#pragma once
#include <GLFW/glfw3.h>
#include <raylib.h>
#include <mpv/client.h>
#include <mpv/render_gl.h>
#include <string>
#include <atomic>
#include <cstring>
#include "utils.h"
#include "rlgl.h"
static void *get_proc_address_mpv(void *fn_ctx, const char *name) {
return (void *) glfwGetProcAddress(name);
}
class VideoPlayer {
mpv_handle *mpv = nullptr;
mpv_render_context *mpv_gl;
std::atomic_bool pending_events = false;
std::atomic_bool need_render = false;
int width = -1;
int height = -1;
double percent_pos = 0.0;
RenderTexture2D render_texture;
static void on_mpv_events(void *ctx) {
auto player = reinterpret_cast<VideoPlayer *>(ctx);
player->pending_events = true;
}
static void on_mpv_render_update(void *ctx) {
auto player = reinterpret_cast<VideoPlayer *>(ctx);
player->need_render = true;
}
void check_size() {
if (width <= 0 || height <= 0) {
return;
}
if (render_texture.texture.width == width && render_texture.texture.height == height) {
return;
}
printf("create video render texture %d %d\n", width, height);
render_texture = LoadRenderTexture(width, height);
}
public:
~VideoPlayer() {
if (mpv_gl)
mpv_render_context_free(mpv_gl);
if (mpv)
mpv_terminate_destroy(mpv);
}
void request_redraw() {
need_render = true;
}
void load(const std::string &path) {
mpv = mpv_create();
if (!mpv)
die("context init failed");
// Some minor options can only be set before mpv_initialize().
if (mpv_initialize(mpv) < 0)
die("mpv init failed");
mpv_request_log_messages(mpv, "debug");
mpv_opengl_init_params gl_init_params{get_proc_address_mpv, this};
int advanced_control = 1;
mpv_render_param params[] = {
{MPV_RENDER_PARAM_API_TYPE, const_cast<char *>(MPV_RENDER_API_TYPE_OPENGL)},
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},
// Tell libmpv that you will call mpv_render_context_update() on render
// context update callbacks, and that you will _not_ block on the core
// ever (see <libmpv/render.h> "Threading" section for what libmpv
// functions you can call at all when this is active).
// In particular, this means you must call e.g. mpv_command_async()
// instead of mpv_command().
// If you want to use synchronous calls, either make them on a separate
// thread, or remove the option below (this will disable features like
// DR and is not recommended anyway).
// {MPV_RENDER_PARAM_ADVANCED_CONTROL, &advanced_control},
{MPV_RENDER_PARAM_INVALID, nullptr}
};
// This makes mpv use the currently set GL context. It will use the callback
// (passed via params) to resolve GL builtin functions, as well as extensions.
if (mpv_render_context_create(&mpv_gl, mpv, params) < 0)
die("failed to initialize mpv GL context");
mpv_observe_property(mpv, 0, "dwidth", MPV_FORMAT_INT64);
mpv_observe_property(mpv, 0, "dheight", MPV_FORMAT_INT64);
//
// mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
// mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "percent-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
mpv_set_option_string(mpv, "loop", "inf");
// When normal mpv events are available.
mpv_set_wakeup_callback(mpv, on_mpv_events, this);
// When there is a need to call mpv_render_context_update(), which can
// request a new frame to be rendered.
// (Separate from the normal event handling mechanism for the sake of
// users which run OpenGL on a different thread.)
mpv_render_context_set_update_callback(mpv_gl, on_mpv_render_update, this);
// Play this file.
const char *cmd[] = {"loadfile", path.c_str(), nullptr};
mpv_command_async(mpv, 0, cmd);
mpv_set_option_string(mpv, "loop", "inf");
}
void restart() {
const char *cmd[] = {"seek", "0", "absolute", nullptr};
mpv_command_async(mpv, 0, cmd);
}
void stop() {
const char *cmd[] = {"stop", nullptr};
mpv_command_async(mpv, 0, cmd);
}
void process() {
if (!mpv) return;
// if (pending_events) {
while (true) {
mpv_event *mp_event = mpv_wait_event(mpv, 0);
if (mp_event->event_id == MPV_EVENT_NONE)
break;
mpv_event_log_message *msg;
mpv_event_property *prop;
switch (mp_event->event_id) {
case MPV_EVENT_NONE:
break;
case MPV_EVENT_LOG_MESSAGE:
// msg = reinterpret_cast<mpv_event_log_message *>(mp_event->data);
// printf("[MPV] %s\n", msg->text);
break;
case MPV_EVENT_SHUTDOWN:
break;
case MPV_EVENT_GET_PROPERTY_REPLY:
break;
case MPV_EVENT_SET_PROPERTY_REPLY:
break;
case MPV_EVENT_COMMAND_REPLY:
break;
case MPV_EVENT_START_FILE:
break;
case MPV_EVENT_END_FILE:
printf("end file\n");
break;
case MPV_EVENT_FILE_LOADED:
break;
case MPV_EVENT_IDLE:
break;
case MPV_EVENT_TICK:
break;
case MPV_EVENT_CLIENT_MESSAGE:
break;
case MPV_EVENT_VIDEO_RECONFIG:
break;
case MPV_EVENT_AUDIO_RECONFIG:
break;
case MPV_EVENT_SEEK:
break;
case MPV_EVENT_PLAYBACK_RESTART:
break;
case MPV_EVENT_PROPERTY_CHANGE:
prop = reinterpret_cast<mpv_event_property *>(mp_event->data);
if (!prop->data) break;
// printf("property %s\n", prop->name);
if (strcmp("dwidth", prop->name) == 0) {
auto w = *reinterpret_cast<int64_t *>(prop->data);
width = w;
check_size();
} else if (strcmp("dheight", prop->name) == 0) {
auto h = *reinterpret_cast<int64_t *>(prop->data);
height = h;
check_size();
} else if (prop->data && strcmp("percent-pos", prop->name) == 0) {
percent_pos = *reinterpret_cast<double *>(prop->data);
// printf("percent %f\n", percent_pos);
}
break;
case MPV_EVENT_QUEUE_OVERFLOW:
break;
case MPV_EVENT_HOOK:
break;
}
// (mp_event->event_id == MPV_EVENT_LOG_MESSAGE)
// {
// auto *msg = reinterpret_cast<mpv_event_log_message *>(mp_event->data);
// // Print log messages about DR allocations, just to
// // test whether it works. If there is more than 1 of
// // these, it works. (The log message can actually change
// // any time, so it's possible this logging stops working
// // in the future.)
//// if (strstr(msg->text, "DR image"))
//
// continue;
// }
// printf("event: %s\n", mpv_event_name(mp_event->event_id));
}
if (need_render) {
if (!IsRenderTextureReady(render_texture)) {
check_size();
}
BeginTextureMode(render_texture);
int flip = 1;
int fbo_id = render_texture.id;
mpv_opengl_fbo fbo = {
fbo_id,
render_texture.texture.width,
render_texture.texture.height,
GL_RGBA8
};
mpv_render_param params[] = {
// Specify the default framebuffer (0) as target. This will
// render onto the entire screen. If you want to show the video
// in a smaller rectangle or apply fancy transformations, you'll
// need to render into a separate FBO and draw it manually.
{MPV_RENDER_PARAM_OPENGL_FBO, &fbo},
// Flip rendering (needed due to flipped GL coordinate system).
{MPV_RENDER_PARAM_FLIP_Y, &flip},
};
// See render_gl.h on what OpenGL environment mpv expects, and
// other API details.
mpv_render_context_render(mpv_gl, params);
EndTextureMode();
}
}
Texture get_texture() {
if (!IsRenderTextureReady(render_texture)) {
check_size();
}
return render_texture.texture;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment