Last active
May 4, 2023 14:53
-
-
Save underdoeg/2e6d0e606a962458e3126ecff7f76815 to your computer and use it in GitHub Desktop.
mpv integration into raylib
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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