1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
// Build with: gcc -o main main.c `pkg-config --libs --cflags mpv sdl2`
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include <mpv/client.h>
#include <mpv/opengl_cb.h>
static Uint32 wakeup_on_mpv_redraw, wakeup_on_mpv_events;
static void die(const char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
static void *get_proc_address_mpv(void *fn_ctx, const char *name)
{
return SDL_GL_GetProcAddress(name);
}
static void on_mpv_events(void *ctx)
{
SDL_Event event = {.type = wakeup_on_mpv_events};
SDL_PushEvent(&event);
}
static void on_mpv_redraw(void *ctx)
{
SDL_Event event = {.type = wakeup_on_mpv_redraw};
SDL_PushEvent(&event);
}
int main(int argc, char *argv[])
{
if (argc != 2)
die("pass a single media file as argument");
mpv_handle *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");
if (SDL_Init(SDL_INIT_VIDEO) < 0)
die("SDL init failed");
SDL_Window *window =
SDL_CreateWindow("hi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
1000, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN |
SDL_WINDOW_RESIZABLE);
if (!window)
die("failed to create SDL window");
// The OpenGL API is somewhat separate from the normal mpv API. This only
// returns NULL if no OpenGL support is compiled.
mpv_opengl_cb_context *mpv_gl = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
if (!mpv_gl)
die("failed to create mpv GL API handle");
SDL_GLContext glcontext = SDL_GL_CreateContext(window);
if (!glcontext)
die("failed to create SDL GL context");
// This makes mpv use the currently set GL context. It will use the callback
// to resolve GL builtin functions, as well as extensions.
if (mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address_mpv, NULL) < 0)
die("failed to initialize mpv GL context");
// Actually using the opengl_cb state has to be explicitly requested.
// Otherwise, mpv will create a separate platform window.
if (mpv_set_option_string(mpv, "vo", "opengl-cb") < 0)
die("failed to set VO");
// We use events for thread-safe notification of the SDL main loop.
// Generally, the wakeup callbacks (set further below) should do as least
// work as possible, and merely wake up another thread to do actual work.
// On SDL, waking up the mainloop is the ideal course of action. SDL's
// SDL_PushEvent() is thread-safe, so we use that.
wakeup_on_mpv_redraw = SDL_RegisterEvents(1);
wakeup_on_mpv_events = SDL_RegisterEvents(1);
if (wakeup_on_mpv_redraw == (Uint32)-1 || wakeup_on_mpv_events == (Uint32)-1)
die("could not register events");
// When normal mpv events are available.
mpv_set_wakeup_callback(mpv, on_mpv_events, NULL);
// When a new frame should be drawn with mpv_opengl_cb_draw().
// (Separate from the normal event handling mechanism for the sake of
// users which run OpenGL on a different thread.)
mpv_opengl_cb_set_update_callback(mpv_gl, on_mpv_redraw, NULL);
// Play this file. Note that this starts playback asynchronously.
const char *cmd[] = {"loadfile", argv[1], NULL};
mpv_command(mpv, cmd);
while (1) {
SDL_Event event;
if (SDL_WaitEvent(&event) != 1)
die("event loop error");
int redraw = 0;
switch (event.type) {
case SDL_QUIT:
goto done;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_EXPOSED)
redraw = 1;
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_SPACE)
mpv_command_string(mpv, "cycle pause");
break;
default:
// Happens when a new video frame should be rendered, or if the
// current frame has to be redrawn e.g. due to OSD changes.
if (event.type == wakeup_on_mpv_redraw)
redraw = 1;
// Happens when at least 1 new event is in the mpv event queue.
if (event.type == wakeup_on_mpv_events) {
// Handle all remaining mpv events.
while (1) {
mpv_event *mp_event = mpv_wait_event(mpv, 0);
if (mp_event->event_id == MPV_EVENT_NONE)
break;
printf("event: %s\n", mpv_event_name(mp_event->event_id));
}
}
}
if (redraw) {
int w, h;
SDL_GetWindowSize(window, &w, &h);
// Note:
// - The 0 is the FBO to use; 0 is the default framebuffer (i.e.
// render to the window directly.
// - The negative height tells mpv to flip the coordinate system.
// - If you do not want the video to cover the whole screen, or want
// to apply any form of fancy transformation, you will have to
// render to a FBO.
// - See opengl_cb.h on what OpenGL environment mpv expects, and
// other API details.
mpv_opengl_cb_draw(mpv_gl, 0, w, -h);
SDL_GL_SwapWindow(window);
}
}
done:
// Destroy the GL renderer and all of the GL objects it allocated. If video
// is still running, the video track will be deselected.
mpv_opengl_cb_uninit_gl(mpv_gl);
mpv_terminate_destroy(mpv);
return 0;
}
|