1 /*
2  * Copyright (c) 2014-2021 gnome-mpv
3  *
4  * This file is part of Celluloid.
5  *
6  * Celluloid is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Celluloid is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Celluloid.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <gio/gio.h>
21 #include <gtk/gtk.h>
22 #include <stdio.h>
23 #include <locale.h>
24 #include <unistd.h>
25 #include <glib-object.h>
26 #include <glib/gi18n.h>
27 #include <gdk/gdk.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include <epoxy/gl.h>
32 #ifdef GDK_WINDOWING_X11
33 #include <gdk/x11/gdkx.h>
34 #include <epoxy/glx.h>
35 #endif
36 #ifdef GDK_WINDOWING_WAYLAND
37 #include <gdk/wayland/gdkwayland.h>
38 #include <epoxy/egl.h>
39 #endif
40 #ifdef GDK_WINDOWING_WIN32
41 #include <gdk/gdkwin32.h>
42 #include <epoxy/wgl.h>
43 #endif
44 
45 #include "celluloid-mpv.h"
46 #include "celluloid-common.h"
47 #include "celluloid-def.h"
48 #include "celluloid-marshal.h"
49 
50 #define get_private(mpv) \
51 	((CelluloidMpvPrivate *)celluloid_mpv_get_instance_private(mpv))
52 
53 typedef struct _CelluloidMpvPrivate CelluloidMpvPrivate;
54 
55 enum
56 {
57 	PROP_0,
58 	PROP_WID,
59 	PROP_READY,
60 	N_PROPERTIES
61 };
62 
63 struct _CelluloidMpvPrivate
64 {
65 	mpv_handle *mpv_ctx;
66 	mpv_render_context *render_ctx;
67 	gboolean ready;
68 	gchar *tmp_input_file;
69 	GSList *log_level_list;
70 	gboolean init_vo_config;
71 	gboolean force_opengl;
72 	gboolean use_opengl;
73 	gint64 wid;
74 	void *render_update_callback_data;
75 	void (*render_update_callback)(void *data);
76 };
77 
78 static void *
79 get_proc_address(void *fn_ctx, const gchar *name);
80 
81 static void
82 set_property(	GObject *object,
83 		guint property_id,
84 		const GValue *value,
85 		GParamSpec *pspec );
86 
87 static void
88 get_property(	GObject *object,
89 		guint property_id,
90 		GValue *value,
91 		GParamSpec *pspec );
92 
93 static
94 void dispose(GObject *object);
95 
96 static
97 void finalize(GObject *object);
98 
99 static
100 void wakeup_callback(void *data);
101 
102 static void
103 mpv_property_changed(CelluloidMpv *mpv, const gchar *name, gpointer value);
104 
105 static void
106 mpv_log_message(	CelluloidMpv *mpv,
107 			mpv_log_level log_level,
108 			const gchar *prefix,
109 			const gchar *text );
110 
111 static void
112 mpv_event_notify(CelluloidMpv *mpv, gint event_id, gpointer event_data);
113 
114 static gboolean
115 process_mpv_events(gpointer data);
116 
117 static gboolean
118 check_mpv_version(const gchar *version);
119 
120 static gpointer
121 get_wl_display(void);
122 
123 static gpointer
124 get_x11_display(void);
125 
126 static void
127 initialize(CelluloidMpv *mpv);
128 
129 static void
130 load_file(CelluloidMpv *mpv, const gchar *uri, gboolean append);
131 
132 static void
133 reset(CelluloidMpv *mpv);
134 
G_DEFINE_TYPE_WITH_PRIVATE(CelluloidMpv,celluloid_mpv,G_TYPE_OBJECT)135 G_DEFINE_TYPE_WITH_PRIVATE(CelluloidMpv, celluloid_mpv, G_TYPE_OBJECT)
136 
137 static void *
138 get_proc_address(void *fn_ctx, const gchar *name)
139 {
140 	GdkDisplay *display = gdk_display_get_default();
141 
142 #ifdef GDK_WINDOWING_WAYLAND
143 	if (GDK_IS_WAYLAND_DISPLAY(display))
144 		return eglGetProcAddress(name);
145 #endif
146 #ifdef GDK_WINDOWING_X11
147 	if (GDK_IS_X11_DISPLAY(display))
148 		return	(void *)(intptr_t)
149 			glXGetProcAddressARB((const GLubyte *)name);
150 #endif
151 #ifdef GDK_WINDOWING_WIN32
152 	if (GDK_IS_WIN32_DISPLAY(display))
153 		return wglGetProcAddress(name);
154 #endif
155 	g_assert_not_reached();
156 	return NULL;
157 }
158 
159 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)160 set_property(	GObject *object,
161 		guint property_id,
162 		const GValue *value,
163 		GParamSpec *pspec )
164 {
165 	CelluloidMpvPrivate *priv = get_private(CELLULOID_MPV(object));
166 
167 	if(property_id == PROP_WID)
168 	{
169 		priv->wid = g_value_get_int64(value);
170 	}
171 	else if(property_id == PROP_READY)
172 	{
173 		priv->ready = g_value_get_boolean(value);
174 	}
175 	else
176 	{
177 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
178 	}
179 }
180 
181 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)182 get_property(	GObject *object,
183 		guint property_id,
184 		GValue *value,
185 		GParamSpec *pspec )
186 {
187 	CelluloidMpvPrivate *priv = get_private(CELLULOID_MPV(object));
188 
189 	if(property_id == PROP_WID)
190 	{
191 		g_value_set_int64(value, priv->wid);
192 	}
193 	else if(property_id == PROP_READY)
194 	{
195 		g_value_set_boolean(value, priv->ready);
196 	}
197 	else
198 	{
199 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
200 	}
201 }
202 
203 static void
dispose(GObject * object)204 dispose(GObject *object)
205 {
206 	CelluloidMpv *mpv = CELLULOID_MPV(object);
207 
208 	if(get_private(mpv)->mpv_ctx)
209 	{
210 		celluloid_mpv_quit(mpv);
211 		while(g_source_remove_by_user_data(object));
212 	}
213 
214 	G_OBJECT_CLASS(celluloid_mpv_parent_class)->dispose(object);
215 }
216 
217 static void
finalize(GObject * object)218 finalize(GObject *object)
219 {
220 	G_OBJECT_CLASS(celluloid_mpv_parent_class)->finalize(object);
221 }
222 
223 static void
wakeup_callback(void * data)224 wakeup_callback(void *data)
225 {
226 	g_idle_add_full(G_PRIORITY_HIGH_IDLE, process_mpv_events, data, NULL);
227 }
228 
229 static void
mpv_property_changed(CelluloidMpv * mpv,const gchar * name,gpointer value)230 mpv_property_changed(CelluloidMpv *mpv, const gchar *name, gpointer value)
231 {
232 	g_debug("Received mpv property change event for \"%s\"", name);
233 }
234 
235 static void
mpv_event_notify(CelluloidMpv * mpv,gint event_id,gpointer event_data)236 mpv_event_notify(CelluloidMpv *mpv, gint event_id, gpointer event_data)
237 {
238 	if(event_id == MPV_EVENT_PROPERTY_CHANGE)
239 	{
240 		mpv_event_property *prop = event_data;
241 
242 		g_signal_emit_by_name(	mpv,
243 					"mpv-property-changed",
244 					prop->name,
245 					prop->data );
246 	}
247 	else if(event_id == MPV_EVENT_IDLE)
248 	{
249 		celluloid_mpv_set_property_flag(mpv, "pause", TRUE);
250 	}
251 	else if(event_id == MPV_EVENT_END_FILE)
252 	{
253 		GSettings *settings = g_settings_new(CONFIG_ROOT);
254 		const gchar *ignore_key = "ignore-playback-errors";
255 
256 		mpv_event_end_file *ef_event = event_data;
257 
258 		if(	!g_settings_get_boolean(settings, ignore_key) &&
259 			ef_event->reason == MPV_END_FILE_REASON_ERROR )
260 		{
261 			const gchar *err;
262 			gchar *msg;
263 
264 			err = mpv_error_string(ef_event->error);
265 			msg = g_strdup_printf
266 				(	_("Playback was terminated "
267 					"abnormally. Reason: %s."),
268 					err );
269 
270 			celluloid_mpv_set_property_flag(mpv, "pause", TRUE);
271 			g_signal_emit_by_name(mpv, "error", msg);
272 
273 			g_free(msg);
274 		}
275 
276 		g_object_unref(settings);
277 	}
278 	else if(event_id == MPV_EVENT_LOG_MESSAGE)
279 	{
280 		mpv_event_log_message* message = event_data;
281 
282 		g_signal_emit_by_name(	mpv,
283 					"mpv-log-message",
284 					message->log_level,
285 					message->prefix,
286 					message->text );
287 	}
288 	else if(event_id == MPV_EVENT_CLIENT_MESSAGE)
289 	{
290 		mpv_event_client_message *event_cmsg = event_data;
291 		gchar* msg = strnjoinv(	" ",
292 					event_cmsg->args,
293 					(gsize)event_cmsg->num_args );
294 
295 		g_signal_emit_by_name(mpv, "message", msg);
296 		g_free(msg);
297 	}
298 	else if(event_id == MPV_EVENT_SHUTDOWN)
299 	{
300 		g_signal_emit_by_name(mpv, "shutdown");
301 	}
302 }
303 
304 static gboolean
process_mpv_events(gpointer data)305 process_mpv_events(gpointer data)
306 {
307 	CelluloidMpv *mpv = data;
308 	CelluloidMpvPrivate *priv = get_private(mpv);
309 	gboolean done = !mpv;
310 
311 	while(!done)
312 	{
313 		mpv_event *event =	priv->mpv_ctx?
314 					mpv_wait_event(priv->mpv_ctx, 0):
315 					NULL;
316 
317 		if(event)
318 		{
319 			if(	!priv->mpv_ctx ||
320 				event->event_id == MPV_EVENT_SHUTDOWN ||
321 				event->event_id == MPV_EVENT_NONE )
322 			{
323 				done = TRUE;
324 			}
325 
326 			g_signal_emit_by_name(	mpv,
327 						"mpv-event-notify",
328 						event->event_id,
329 						event->data );
330 		}
331 		else
332 		{
333 			done = TRUE;
334 		}
335 	}
336 
337 	return FALSE;
338 }
339 
340 static gboolean
check_mpv_version(const gchar * version)341 check_mpv_version(const gchar *version)
342 {
343 	guint64 min_version[] = {MIN_MPV_MAJOR, MIN_MPV_MINOR, MIN_MPV_PATCH};
344 	const guint min_version_length = G_N_ELEMENTS(min_version);
345 	gchar **tokens = NULL;
346 	gboolean done = FALSE;
347 	gboolean result = TRUE;
348 
349 	/* Skip to the version number */
350 	if(strncmp(version, "mpv ", 4) == 0)
351 	{
352 		tokens = g_strsplit(version+4, ".", (gint)min_version_length);
353 	}
354 
355 	done = !tokens || g_strv_length(tokens) != min_version_length;
356 	result = !done;
357 
358 	for(guint i = 0; i < min_version_length && !done && result; i++)
359 	{
360 		gchar *endptr = NULL;
361 		guint64 token = g_ascii_strtoull(tokens[i], &endptr, 10);
362 
363 		/* If the token is equal to the minimum, continue checking the
364 		 * rest of the tokens. If it is greater, just skip them.
365 		 */
366 		result &= !(*endptr) && (token >= min_version[i]);
367 		done = result && (token >= min_version[i]);
368 	}
369 
370 	g_strfreev(tokens);
371 
372 	return result;
373 }
374 
375 static gpointer
get_wl_display(void)376 get_wl_display(void)
377 {
378 	gpointer wl_display = NULL;
379 
380 #ifdef GDK_WINDOWING_WAYLAND
381 	GdkDisplay *display = gdk_display_get_default();
382 
383 	if(GDK_IS_WAYLAND_DISPLAY(display))
384 	{
385 		wl_display =  gdk_wayland_display_get_wl_display(display);
386 	}
387 #endif
388 
389 	return wl_display;
390 }
391 
392 static gpointer
get_x11_display(void)393 get_x11_display(void)
394 {
395 	gpointer x11_display = NULL;
396 
397 #ifdef GDK_WINDOWING_X11
398 	GdkDisplay *display = gdk_display_get_default();
399 
400 	if(GDK_IS_X11_DISPLAY(display))
401 	{
402 		x11_display = gdk_x11_display_get_xdisplay(display);
403 	}
404 #endif
405 
406 	return x11_display;
407 }
408 
409 static void
initialize(CelluloidMpv * mpv)410 initialize(CelluloidMpv *mpv)
411 {
412 	CelluloidMpvPrivate *priv = get_private(mpv);
413 	gchar *current_vo = NULL;
414 	gchar *mpv_version = NULL;
415 
416 	if(priv->wid == 0)
417 	{
418 		g_info("Forcing --vo=null");
419 		mpv_set_option_string(priv->mpv_ctx, "vo", "null");
420 	}
421 	else
422 	{
423 		g_info("Forcing --vo=libmpv");
424 		mpv_set_option_string(priv->mpv_ctx, "vo", "libmpv");
425 	}
426 
427 	mpv_set_wakeup_callback(priv->mpv_ctx, wakeup_callback, mpv);
428 	mpv_initialize(priv->mpv_ctx);
429 
430 	mpv_version = celluloid_mpv_get_property_string(mpv, "mpv-version");
431 	current_vo = celluloid_mpv_get_property_string(mpv, "current-vo");
432 	priv->use_opengl = (!current_vo && priv->wid != 0);
433 
434 	g_info("Using %s", mpv_version);
435 
436 	if(!check_mpv_version(mpv_version))
437 	{
438 		g_warning(	"Minimum mpv version requirement (%d.%d.%d) not met",
439 				MIN_MPV_MAJOR,
440 				MIN_MPV_MINOR,
441 				MIN_MPV_PATCH );
442 	}
443 
444 	priv->ready = TRUE;
445 	g_object_notify(G_OBJECT(mpv), "ready");
446 
447 	mpv_free(current_vo);
448 	mpv_free(mpv_version);
449 }
450 
451 static void
load_file(CelluloidMpv * mpv,const gchar * uri,gboolean append)452 load_file(CelluloidMpv *mpv, const gchar *uri, gboolean append)
453 {
454 	CelluloidMpvPrivate *priv = get_private(mpv);
455 	gchar *path = get_path_from_uri(uri);
456 	const gchar *load_cmd[] = {"loadfile", path, NULL, NULL};
457 	gint64 playlist_count = 0;
458 
459 	g_assert(uri);
460 	g_info(	"Loading file (append=%s): %s", append?"TRUE":"FALSE", uri);
461 
462 	mpv_get_property(	priv->mpv_ctx,
463 				"playlist-count",
464 				MPV_FORMAT_INT64,
465 				&playlist_count );
466 
467 	load_cmd[2] = (append && playlist_count > 0)?"append":"replace";
468 
469 	if(!append)
470 	{
471 		celluloid_mpv_set_property_flag(mpv, "pause", FALSE);
472 	}
473 
474 	g_assert(priv->mpv_ctx);
475 	mpv_request_event(priv->mpv_ctx, MPV_EVENT_END_FILE, 0);
476 	mpv_command(priv->mpv_ctx, load_cmd);
477 	mpv_request_event(priv->mpv_ctx, MPV_EVENT_END_FILE, 1);
478 
479 	g_free(path);
480 }
481 
482 static void
reset(CelluloidMpv * mpv)483 reset(CelluloidMpv *mpv)
484 {
485 	CelluloidMpvPrivate *priv = get_private(mpv);
486 	gchar *loop_file_str;
487 	gchar *loop_playlist_str;
488 	gboolean loop_file;
489 	gboolean loop_playlist;
490 
491 	loop_file_str =		celluloid_mpv_get_property_string
492 				(mpv, "loop-file");
493 	loop_playlist_str =	celluloid_mpv_get_property_string
494 				(mpv, "loop-playlist");
495 	loop_file =		(g_strcmp0(loop_file_str, "inf") == 0);
496 	loop_playlist =		(g_strcmp0(loop_playlist_str, "inf") == 0);
497 
498 	mpv_free(loop_file_str);
499 	mpv_free(loop_playlist_str);
500 
501 	/* Reset priv->mpv_ctx */
502 	priv->ready = FALSE;
503 	g_object_notify(G_OBJECT(mpv), "ready");
504 
505 	celluloid_mpv_command_string(mpv, "write-watch-later-config");
506 	celluloid_mpv_quit(mpv);
507 
508 	priv->mpv_ctx = mpv_create();
509 	celluloid_mpv_initialize(mpv);
510 
511 	celluloid_mpv_set_render_update_callback
512 		(	mpv,
513 			priv->render_update_callback,
514 			priv->render_update_callback_data );
515 
516 	celluloid_mpv_set_property_string
517 		(mpv, "loop-file", loop_file?"inf":"no");
518 	celluloid_mpv_set_property_string
519 		(mpv, "loop-playlist", loop_playlist?"inf":"no");
520 }
521 
522 static void
mpv_log_message(CelluloidMpv * mpv,mpv_log_level log_level,const gchar * prefix,const gchar * text)523 mpv_log_message(	CelluloidMpv *mpv,
524 			mpv_log_level log_level,
525 			const gchar *prefix,
526 			const gchar *text )
527 {
528 }
529 
530 static void
celluloid_mpv_class_init(CelluloidMpvClass * klass)531 celluloid_mpv_class_init(CelluloidMpvClass* klass)
532 {
533 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
534 	GParamSpec *pspec = NULL;
535 
536 	klass->mpv_event_notify = mpv_event_notify;
537 	klass->mpv_log_message = mpv_log_message;
538 	klass->mpv_property_changed = mpv_property_changed;
539 	klass->initialize = initialize;
540 	klass->load_file = load_file;
541 	klass->reset = reset;
542 	obj_class->set_property = set_property;
543 	obj_class->get_property = get_property;
544 	obj_class->dispose = dispose;
545 	obj_class->finalize = finalize;
546 
547 	pspec = g_param_spec_int64
548 		(	"wid",
549 			"WID",
550 			"The ID of the window to attach to",
551 			G_MININT64,
552 			G_MAXINT64,
553 			-1,
554 			G_PARAM_CONSTRUCT_ONLY|G_PARAM_READWRITE );
555 	g_object_class_install_property(obj_class, PROP_WID, pspec);
556 
557 	pspec = g_param_spec_boolean
558 		(	"ready",
559 			"Ready",
560 			"Whether mpv is initialized and ready to receive commands",
561 			FALSE,
562 			G_PARAM_READABLE );
563 	g_object_class_install_property(obj_class, PROP_READY, pspec);
564 
565 	g_signal_new(	"error",
566 			G_TYPE_FROM_CLASS(klass),
567 			G_SIGNAL_RUN_FIRST,
568 			0,
569 			NULL,
570 			NULL,
571 			g_cclosure_marshal_VOID__STRING,
572 			G_TYPE_NONE,
573 			1,
574 			G_TYPE_STRING );
575 	g_signal_new(	"mpv-event-notify",
576 			G_TYPE_FROM_CLASS(klass),
577 			G_SIGNAL_RUN_FIRST,
578 			G_STRUCT_OFFSET(CelluloidMpvClass, mpv_event_notify),
579 			NULL,
580 			NULL,
581 			g_cclosure_gen_marshal_VOID__INT_POINTER,
582 			G_TYPE_NONE,
583 			2,
584 			G_TYPE_INT,
585 			G_TYPE_POINTER );
586 	g_signal_new(	"mpv-log-message",
587 			G_TYPE_FROM_CLASS(klass),
588 			G_SIGNAL_RUN_FIRST,
589 			G_STRUCT_OFFSET(CelluloidMpvClass, mpv_log_message),
590 			NULL,
591 			NULL,
592 			g_cclosure_gen_marshal_VOID__INT_STRING_STRING,
593 			G_TYPE_NONE,
594 			3,
595 			G_TYPE_INT,
596 			G_TYPE_STRING,
597 			G_TYPE_STRING );
598 	g_signal_new(	"mpv-property-changed",
599 			G_TYPE_FROM_CLASS(klass),
600 			G_SIGNAL_RUN_FIRST,
601 			G_STRUCT_OFFSET(CelluloidMpvClass, mpv_property_changed),
602 			NULL,
603 			NULL,
604 			g_cclosure_gen_marshal_VOID__STRING_POINTER,
605 			G_TYPE_NONE,
606 			2,
607 			G_TYPE_STRING,
608 			G_TYPE_POINTER );
609 	g_signal_new(	"message",
610 			G_TYPE_FROM_CLASS(klass),
611 			G_SIGNAL_RUN_FIRST,
612 			0,
613 			NULL,
614 			NULL,
615 			g_cclosure_marshal_VOID__STRING,
616 			G_TYPE_NONE,
617 			1,
618 			G_TYPE_STRING );
619 	g_signal_new(	"window-resize",
620 			G_TYPE_FROM_CLASS(klass),
621 			G_SIGNAL_RUN_FIRST,
622 			0,
623 			NULL,
624 			NULL,
625 			g_cclosure_gen_marshal_VOID__INT64_INT64,
626 			G_TYPE_NONE,
627 			2,
628 			G_TYPE_INT64,
629 			G_TYPE_INT64 );
630 	g_signal_new(	"window-move",
631 			G_TYPE_FROM_CLASS(klass),
632 			G_SIGNAL_RUN_FIRST,
633 			0,
634 			NULL,
635 			NULL,
636 			g_cclosure_gen_marshal_VOID__BOOLEAN_BOOLEAN_POINTER_POINTER,
637 			G_TYPE_NONE,
638 			4,
639 			G_TYPE_BOOLEAN,
640 			G_TYPE_BOOLEAN,
641 			G_TYPE_POINTER,
642 			G_TYPE_POINTER );
643 	g_signal_new(	"shutdown",
644 			G_TYPE_FROM_CLASS(klass),
645 			G_SIGNAL_RUN_FIRST,
646 			0,
647 			NULL,
648 			NULL,
649 			g_cclosure_marshal_VOID__VOID,
650 			G_TYPE_NONE,
651 			0 );
652 }
653 
654 static void
celluloid_mpv_init(CelluloidMpv * mpv)655 celluloid_mpv_init(CelluloidMpv *mpv)
656 {
657 	CelluloidMpvPrivate *priv = get_private(mpv);
658 
659 	setlocale(LC_NUMERIC, "C");
660 
661 	priv->mpv_ctx = mpv_create();
662 	priv->render_ctx = NULL;
663 	priv->ready = FALSE;
664 	priv->init_vo_config = TRUE;
665 	priv->use_opengl = FALSE;
666 	priv->wid = -1;
667 	priv->render_update_callback_data = NULL;
668 	priv->render_update_callback = NULL;
669 }
670 
671 CelluloidMpv *
celluloid_mpv_new(gint64 wid)672 celluloid_mpv_new(gint64 wid)
673 {
674 	return CELLULOID_MPV(g_object_new(celluloid_mpv_get_type(), "wid", wid, NULL));
675 }
676 
677 inline mpv_render_context *
celluloid_mpv_get_render_context(CelluloidMpv * mpv)678 celluloid_mpv_get_render_context(CelluloidMpv *mpv)
679 {
680 	return get_private(mpv)->render_ctx;
681 }
682 
683 inline gboolean
celluloid_mpv_get_use_opengl_cb(CelluloidMpv * mpv)684 celluloid_mpv_get_use_opengl_cb(CelluloidMpv *mpv)
685 {
686 	return get_private(mpv)->use_opengl;
687 }
688 
689 void
celluloid_mpv_initialize(CelluloidMpv * mpv)690 celluloid_mpv_initialize(CelluloidMpv *mpv)
691 {
692 	CELLULOID_MPV_GET_CLASS(mpv)->initialize(mpv);
693 }
694 
695 void
celluloid_mpv_init_gl(CelluloidMpv * mpv)696 celluloid_mpv_init_gl(CelluloidMpv *mpv)
697 {
698 	CelluloidMpvPrivate *priv = get_private(mpv);
699 	mpv_opengl_init_params init_params =
700 		{.get_proc_address = get_proc_address};
701 	mpv_render_param params[] =
702 		{	{MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL},
703 			{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &init_params},
704 			{MPV_RENDER_PARAM_WL_DISPLAY, get_wl_display()},
705 			{MPV_RENDER_PARAM_X11_DISPLAY, get_x11_display()},
706 			{0, NULL} };
707 	gint rc = mpv_render_context_create(	&priv->render_ctx,
708 						priv->mpv_ctx,
709 						params );
710 
711 	if(rc >= 0)
712 	{
713 		g_debug("Initialized render context");
714 	}
715 	else
716 	{
717 		g_critical("Failed to initialize render context");
718 	}
719 }
720 
721 void
celluloid_mpv_reset(CelluloidMpv * mpv)722 celluloid_mpv_reset(CelluloidMpv *mpv)
723 {
724 	CELLULOID_MPV_GET_CLASS(mpv)->reset(mpv);
725 }
726 
727 void
celluloid_mpv_quit(CelluloidMpv * mpv)728 celluloid_mpv_quit(CelluloidMpv *mpv)
729 {
730 	CelluloidMpvPrivate *priv = get_private(mpv);
731 
732 	g_info("Terminating mpv");
733 	celluloid_mpv_command_string(mpv, "quit");
734 
735 	if(priv->render_ctx)
736 	{
737 		g_debug("Uninitializing render context");
738 		mpv_render_context_free(priv->render_ctx);
739 
740 		priv->render_ctx = NULL;
741 	}
742 
743 	g_assert(priv->mpv_ctx);
744 	mpv_terminate_destroy(priv->mpv_ctx);
745 
746 	priv->mpv_ctx = NULL;
747 }
748 
749 void
celluloid_mpv_load_track(CelluloidMpv * mpv,const gchar * uri,TrackType type)750 celluloid_mpv_load_track(CelluloidMpv *mpv, const gchar *uri, TrackType type)
751 {
752 	const gchar *cmd[3] = {NULL};
753 	gchar *path = g_filename_from_uri(uri, NULL, NULL);
754 
755 	switch(type)
756 	{
757 		case TRACK_TYPE_AUDIO:
758 		cmd[0] = "audio-add";
759 		break;
760 
761 		case TRACK_TYPE_VIDEO:
762 		cmd[0] = "video-add";
763 		break;
764 
765 		case TRACK_TYPE_SUBTITLE:
766 		cmd[0] = "sub-add";
767 		break;
768 
769 		default:
770 		g_assert_not_reached();
771 		break;
772 	}
773 
774 	cmd[1] = path?:uri;
775 
776 	g_debug("Loading external track %s with type %d", cmd[1], type);
777 	celluloid_mpv_command(mpv, cmd);
778 
779 	g_free(path);
780 }
781 
782 void
celluloid_mpv_load_file(CelluloidMpv * mpv,const gchar * uri,gboolean append)783 celluloid_mpv_load_file(CelluloidMpv *mpv, const gchar *uri, gboolean append)
784 {
785 	CELLULOID_MPV_GET_CLASS(mpv)->load_file(mpv, uri, append);
786 }
787 
788 void
celluloid_mpv_load(CelluloidMpv * mpv,const gchar * uri,gboolean append)789 celluloid_mpv_load(CelluloidMpv *mpv, const gchar *uri, gboolean append)
790 {
791 	const gchar *subtitle_exts[] = SUBTITLE_EXTS;
792 
793 	if(extension_matches(uri, subtitle_exts))
794 	{
795 		celluloid_mpv_load_track(mpv, uri, TRACK_TYPE_SUBTITLE);
796 	}
797 	else
798 	{
799 		celluloid_mpv_load_file(mpv, uri, append);
800 	}
801 }
802 
803 gint
celluloid_mpv_command(CelluloidMpv * mpv,const gchar ** cmd)804 celluloid_mpv_command(CelluloidMpv *mpv, const gchar **cmd)
805 {
806 	CelluloidMpvPrivate *priv = get_private(mpv);
807 	gint rc = MPV_ERROR_UNINITIALIZED;
808 
809 	if(priv->mpv_ctx)
810 	{
811 		rc = mpv_command(priv->mpv_ctx, cmd);
812 	}
813 
814 	if(rc < 0)
815 	{
816 		gchar *cmd_str = g_strjoinv(" ", (gchar **)cmd);
817 
818 		g_warning(	"Failed to run mpv command \"%s\". Reason: %s.",
819 				cmd_str,
820 				mpv_error_string(rc) );
821 
822 		g_free(cmd_str);
823 	}
824 
825 	return rc;
826 }
827 
828 gint
celluloid_mpv_command_async(CelluloidMpv * mpv,const gchar ** cmd)829 celluloid_mpv_command_async(CelluloidMpv *mpv, const gchar **cmd)
830 {
831 	CelluloidMpvPrivate *priv = get_private(mpv);
832 	gint rc = MPV_ERROR_UNINITIALIZED;
833 
834 	if(priv->mpv_ctx)
835 	{
836 		rc = mpv_command_async(priv->mpv_ctx, 0, cmd);
837 	}
838 
839 	if(rc < 0)
840 	{
841 		gchar *cmd_str = g_strjoinv(" ", (gchar **)cmd);
842 
843 		g_warning(	"Failed to dispatch async mpv command \"%s\". "
844 				"Reason: %s.",
845 				cmd_str,
846 				mpv_error_string(rc) );
847 
848 		g_free(cmd_str);
849 	}
850 
851 	return rc;
852 }
853 
854 gint
celluloid_mpv_command_string(CelluloidMpv * mpv,const gchar * cmd)855 celluloid_mpv_command_string(CelluloidMpv *mpv, const gchar *cmd)
856 {
857 	CelluloidMpvPrivate *priv = get_private(mpv);
858 	gint rc = MPV_ERROR_UNINITIALIZED;
859 
860 	if(priv->mpv_ctx)
861 	{
862 		rc = mpv_command_string(priv->mpv_ctx, cmd);
863 	}
864 
865 	if(rc < 0)
866 	{
867 		g_warning(	"Failed to run mpv command string \"%s\". "
868 				"Reason: %s.",
869 				cmd,
870 				mpv_error_string(rc) );
871 	}
872 
873 	return rc;
874 }
875 
876 gint
celluloid_mpv_set_option_string(CelluloidMpv * mpv,const gchar * name,const gchar * value)877 celluloid_mpv_set_option_string(	CelluloidMpv *mpv,
878 					const gchar *name,
879 					const gchar *value )
880 {
881 	return mpv_set_option_string(get_private(mpv)->mpv_ctx, name, value);
882 }
883 
884 gint
celluloid_mpv_get_property(CelluloidMpv * mpv,const gchar * name,mpv_format format,void * data)885 celluloid_mpv_get_property(	CelluloidMpv *mpv,
886 				const gchar *name,
887 				mpv_format format,
888 				void *data )
889 {
890 	CelluloidMpvPrivate *priv = get_private(mpv);
891 	gint rc = MPV_ERROR_UNINITIALIZED;
892 
893 	if(priv->mpv_ctx)
894 	{
895 		rc = mpv_get_property(priv->mpv_ctx, name, format, data);
896 	}
897 
898 	if(rc < 0)
899 	{
900 		g_info(	"Failed to retrieve property \"%s\" "
901 			"using mpv format %d. Reason: %s.",
902 			name,
903 			format,
904 			mpv_error_string(rc) );
905 	}
906 
907 	return rc;
908 }
909 
910 gchar *
celluloid_mpv_get_property_string(CelluloidMpv * mpv,const gchar * name)911 celluloid_mpv_get_property_string(CelluloidMpv *mpv, const gchar *name)
912 {
913 	CelluloidMpvPrivate *priv = get_private(mpv);
914 	gchar *value = NULL;
915 
916 	if(priv->mpv_ctx)
917 	{
918 		value = mpv_get_property_string(priv->mpv_ctx, name);
919 	}
920 
921 	if(!value)
922 	{
923 		g_info("Failed to retrieve property \"%s\" as string.", name);
924 	}
925 
926 	return value;
927 }
928 
929 gboolean
celluloid_mpv_get_property_flag(CelluloidMpv * mpv,const gchar * name)930 celluloid_mpv_get_property_flag(CelluloidMpv *mpv, const gchar *name)
931 {
932 	CelluloidMpvPrivate *priv = get_private(mpv);
933 	gboolean value = FALSE;
934 	gint rc = MPV_ERROR_UNINITIALIZED;
935 
936 	if(priv->mpv_ctx)
937 	{
938 		rc =	mpv_get_property
939 			(priv->mpv_ctx, name, MPV_FORMAT_FLAG, &value);
940 	}
941 
942 	if(rc < 0)
943 	{
944 		g_info(	"Failed to retrieve property \"%s\" as flag. "
945 			"Reason: %s.",
946 			name,
947 			mpv_error_string(rc) );
948 	}
949 
950 	return value;
951 }
952 
953 gint
celluloid_mpv_set_property(CelluloidMpv * mpv,const gchar * name,mpv_format format,void * data)954 celluloid_mpv_set_property(	CelluloidMpv *mpv,
955 				const gchar *name,
956 				mpv_format format,
957 				void *data )
958 {
959 	CelluloidMpvPrivate *priv = get_private(mpv);
960 	gint rc = MPV_ERROR_UNINITIALIZED;
961 
962 	if(priv->mpv_ctx)
963 	{
964 		rc = mpv_set_property(priv->mpv_ctx, name, format, data);
965 	}
966 
967 	if(rc < 0)
968 	{
969 		g_info(	"Failed to set property \"%s\" using mpv format %d. "
970 			"Reason: %s.",
971 			name,
972 			format,
973 			mpv_error_string(rc) );
974 	}
975 
976 	return rc;
977 }
978 
979 gint
celluloid_mpv_set_property_string(CelluloidMpv * mpv,const gchar * name,const char * data)980 celluloid_mpv_set_property_string(	CelluloidMpv *mpv,
981 					const gchar *name,
982 					const char *data )
983 {
984 	CelluloidMpvPrivate *priv = get_private(mpv);
985 	gint rc = MPV_ERROR_UNINITIALIZED;
986 
987 	if(priv->mpv_ctx)
988 	{
989 		rc = mpv_set_property_string(priv->mpv_ctx, name, data);
990 	}
991 
992 	if(rc < 0)
993 	{
994 		g_info(	"Failed to set property \"%s\" as string. Reason: %s.",
995 			name,
996 			mpv_error_string(rc) );
997 	}
998 
999 	return rc;
1000 }
1001 
1002 gint
celluloid_mpv_set_property_flag(CelluloidMpv * mpv,const gchar * name,gboolean value)1003 celluloid_mpv_set_property_flag(	CelluloidMpv *mpv,
1004 					const gchar *name,
1005 					gboolean value )
1006 {
1007 	CelluloidMpvPrivate *priv = get_private(mpv);
1008 	gint rc = MPV_ERROR_UNINITIALIZED;
1009 
1010 	if(priv->mpv_ctx)
1011 	{
1012 		rc =	mpv_set_property
1013 			(priv->mpv_ctx, name, MPV_FORMAT_FLAG, &value);
1014 	}
1015 
1016 	if(rc < 0)
1017 	{
1018 		g_info(	"Failed to set property \"%s\" as flag. Reason: %s.",
1019 			name,
1020 			mpv_error_string(rc) );
1021 	}
1022 
1023 	return rc;
1024 }
1025 
1026 void
celluloid_mpv_set_render_update_callback(CelluloidMpv * mpv,mpv_render_update_fn func,void * data)1027 celluloid_mpv_set_render_update_callback(	CelluloidMpv *mpv,
1028 						mpv_render_update_fn func,
1029 						void *data )
1030 {
1031 	CelluloidMpvPrivate *priv = get_private(mpv);
1032 
1033 	priv->render_update_callback = func;
1034 	priv->render_update_callback_data = data;
1035 
1036 	if(priv->render_ctx)
1037 	{
1038 		mpv_render_context_set_update_callback
1039 			(priv->render_ctx, func, data);
1040 	}
1041 }
1042 
1043 guint64
celluloid_mpv_render_context_update(CelluloidMpv * mpv)1044 celluloid_mpv_render_context_update(CelluloidMpv *mpv)
1045 {
1046 	return mpv_render_context_update(get_private(mpv)->render_ctx);
1047 }
1048 
1049 gint
celluloid_mpv_load_config_file(CelluloidMpv * mpv,const gchar * filename)1050 celluloid_mpv_load_config_file(CelluloidMpv *mpv, const gchar *filename)
1051 {
1052 	return mpv_load_config_file(get_private(mpv)->mpv_ctx, filename);
1053 }
1054 
1055 gint
celluloid_mpv_observe_property(CelluloidMpv * mpv,guint64 reply_userdata,const gchar * name,mpv_format format)1056 celluloid_mpv_observe_property(	CelluloidMpv *mpv,
1057 				guint64 reply_userdata,
1058 				const gchar *name,
1059 				mpv_format format )
1060 {
1061 	return mpv_observe_property(	get_private(mpv)->mpv_ctx,
1062 					reply_userdata,
1063 					name,
1064 					format );
1065 }
1066 
1067 gint
celluloid_mpv_request_log_messages(CelluloidMpv * mpv,const gchar * min_level)1068 celluloid_mpv_request_log_messages(CelluloidMpv *mpv, const gchar *min_level)
1069 {
1070 	return mpv_request_log_messages(get_private(mpv)->mpv_ctx, min_level);
1071 }
1072