1 /*
2  * Copyright (c) 2017-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 <epoxy/gl.h>
21 
22 #include "celluloid-model.h"
23 #include "celluloid-marshal.h"
24 #include "celluloid-mpv.h"
25 #include "celluloid-option-parser.h"
26 #include "celluloid-def.h"
27 
28 enum
29 {
30 	PROP_INVALID,
31 	PROP_AID,
32 	PROP_VID,
33 	PROP_SID,
34 	PROP_CHAPTERS,
35 	PROP_CORE_IDLE,
36 	PROP_IDLE_ACTIVE,
37 	PROP_BORDER,
38 	PROP_FULLSCREEN,
39 	PROP_PAUSE,
40 	PROP_LOOP_FILE,
41 	PROP_LOOP_PLAYLIST,
42 	PROP_SHUFFLE,
43 	PROP_DURATION,
44 	PROP_MEDIA_TITLE,
45 	PROP_PLAYLIST_COUNT,
46 	PROP_PLAYLIST_POS,
47 	PROP_SPEED,
48 	PROP_VOLUME,
49 	PROP_VOLUME_MAX,
50 	PROP_WINDOW_MAXIMIZED,
51 	PROP_WINDOW_SCALE,
52 	PROP_DISPLAY_FPS,
53 	N_PROPERTIES
54 };
55 
56 struct _CelluloidModel
57 {
58 	CelluloidPlayer parent;
59 	gchar *extra_options;
60 	GPtrArray *metadata;
61 	GPtrArray *track_list;
62 	gboolean update_mpv_properties;
63 	gboolean resetting;
64 	gchar *aid;
65 	gchar *vid;
66 	gchar *sid;
67 	gint64 chapters;
68 	gboolean core_idle;
69 	gboolean idle_active;
70 	gboolean border;
71 	gboolean fullscreen;
72 	gboolean pause;
73 	gchar *loop_file;
74 	gchar *loop_playlist;
75 	gboolean shuffle;
76 	gdouble duration;
77 	gchar *media_title;
78 	gint64 playlist_count;
79 	gint64 playlist_pos;
80 	gdouble speed;
81 	gdouble volume;
82 	gdouble volume_max;
83 	gboolean window_maximized;
84 	gdouble window_scale;
85 	gdouble display_fps;
86 };
87 
88 struct _CelluloidModelClass
89 {
90 	GObjectClass parent_class;
91 };
92 
93 static gboolean
94 extra_options_contains(CelluloidModel *model, const gchar *option);
95 
96 static void
97 set_property(	GObject *object,
98 		guint property_id,
99 		const GValue *value,
100 		GParamSpec *pspec );
101 
102 static void
103 get_property(	GObject *object,
104 		guint property_id,
105 		GValue *value,
106 		GParamSpec *pspec );
107 
108 static void
109 dispose(GObject *object);
110 
111 static void
112 finalize(GObject *object);
113 
114 static void
115 set_mpv_property(	GObject *object,
116 			guint property_id,
117 			const GValue *value,
118 			GParamSpec *pspec );
119 
120 static void
121 g_value_set_by_type(GValue *gvalue, GType type, gpointer value);
122 
123 static GParamSpec *
124 g_param_spec_by_type(	const gchar *name,
125 			const gchar *nick,
126 			const gchar *blurb,
127 			GType type,
128 			GParamFlags flags );
129 
130 static gboolean
131 emit_frame_ready(gpointer data);
132 
133 static void
134 render_update_callback(gpointer render_ctx);
135 
136 static void
137 mpv_prop_change_handler(	CelluloidMpv *mpv,
138 				const gchar *name,
139 				gpointer value,
140 				gpointer data );
141 
G_DEFINE_TYPE(CelluloidModel,celluloid_model,CELLULOID_TYPE_PLAYER)142 G_DEFINE_TYPE(CelluloidModel, celluloid_model, CELLULOID_TYPE_PLAYER)
143 
144 static gboolean
145 extra_options_contains(CelluloidModel *model, const gchar *option)
146 {
147 	gboolean result = FALSE;
148 	gchar *extra_options = NULL;
149 	const gchar *cur = NULL;
150 
151 	g_object_get(model, "extra-options", &extra_options, NULL);
152 
153 	cur = extra_options;
154 
155 	while(cur && *cur && !result)
156 	{
157 		gchar *key = NULL;
158 		gchar *value = NULL;
159 
160 		cur = parse_option(cur, &key, &value);
161 
162 		if(key && *key)
163 		{
164 			result |= g_strcmp0(key, option) == 0;
165 		}
166 		else
167 		{
168 			g_warning("Failed to parse options");
169 
170 			cur = NULL;
171 		}
172 
173 		g_free(key);
174 		g_free(value);
175 	}
176 
177 	g_free(extra_options);
178 
179 	return result;
180 }
181 
182 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)183 set_property(	GObject *object,
184 		guint property_id,
185 		const GValue *value,
186 		GParamSpec *pspec )
187 {
188 	CelluloidModel *self = CELLULOID_MODEL(object);
189 
190 	switch(property_id)
191 	{
192 		case PROP_AID:
193 		g_free(self->aid);
194 		self->aid = g_value_dup_string(value);
195 		break;
196 
197 		case PROP_VID:
198 		g_free(self->vid);
199 		self->vid = g_value_dup_string(value);
200 		break;
201 
202 		case PROP_SID:
203 		g_free(self->sid);
204 		self->sid = g_value_dup_string(value);
205 		break;
206 
207 		case PROP_CHAPTERS:
208 		self->chapters = g_value_get_int64(value);
209 		break;
210 
211 		case PROP_CORE_IDLE:
212 		self->core_idle = g_value_get_boolean(value);
213 
214 		if(self->resetting)
215 		{
216 			if(self->pause)
217 			{
218 				celluloid_model_pause(self);
219 			}
220 			else
221 			{
222 				celluloid_model_play(self);
223 			}
224 
225 			self->resetting = FALSE;
226 		}
227 		break;
228 
229 		case PROP_IDLE_ACTIVE:
230 		self->idle_active = g_value_get_boolean(value);
231 
232 		if(self->idle_active)
233 		{
234 			g_object_notify(object, "playlist-pos");
235 		}
236 		break;
237 
238 		case PROP_BORDER:
239 		self->border = g_value_get_boolean(value);
240 		break;
241 
242 		case PROP_FULLSCREEN:
243 		self->fullscreen = g_value_get_boolean(value);
244 		break;
245 
246 		case PROP_PAUSE:
247 		self->pause = g_value_get_boolean(value);
248 		break;
249 
250 		case PROP_LOOP_FILE:
251 		g_free(self->loop_file);
252 		self->loop_file = g_value_dup_string(value);
253 		break;
254 
255 		case PROP_LOOP_PLAYLIST:
256 		g_free(self->loop_playlist);
257 		self->loop_playlist = g_value_dup_string(value);
258 		break;
259 
260 		case PROP_SHUFFLE:
261 		{
262 			gboolean ready = FALSE;
263 
264 			self->shuffle = g_value_get_boolean(value);
265 			g_object_get(self, "ready", &ready, NULL);
266 
267 			if(ready)
268 			{
269 				if(self->shuffle)
270 				{
271 					celluloid_model_shuffle_playlist(self);
272 				}
273 				else
274 				{
275 					celluloid_model_unshuffle_playlist(self);
276 				}
277 			}
278 		}
279 		break;
280 
281 		case PROP_DURATION:
282 		self->duration = g_value_get_double(value);
283 		break;
284 
285 		case PROP_MEDIA_TITLE:
286 		g_free(self->media_title);
287 		self->media_title = g_value_dup_string(value);
288 		break;
289 
290 		case PROP_PLAYLIST_COUNT:
291 		self->playlist_count = g_value_get_int64(value);
292 		break;
293 
294 		case PROP_PLAYLIST_POS:
295 		self->playlist_pos = g_value_get_int64(value);
296 		break;
297 
298 		case PROP_SPEED:
299 		self->speed = g_value_get_double(value);
300 		break;
301 
302 		case PROP_VOLUME:
303 		self->volume = g_value_get_double(value);
304 		break;
305 
306 		case PROP_VOLUME_MAX:
307 		self->volume_max = g_value_get_double(value);
308 		break;
309 
310 		case PROP_WINDOW_MAXIMIZED:
311 		self->window_maximized = g_value_get_boolean(value);
312 		break;
313 
314 		case PROP_WINDOW_SCALE:
315 		self->window_scale = g_value_get_double(value);
316 		break;
317 
318 		case PROP_DISPLAY_FPS:
319 		self->display_fps = g_value_get_double(value);
320 		break;
321 
322 		default:
323 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
324 		break;
325 	}
326 
327 	/* Do not propagate changes from mpv back to itself */
328 	if(self->update_mpv_properties)
329 	{
330 		set_mpv_property(object, property_id, value, pspec);
331 	}
332 }
333 
334 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)335 get_property(	GObject *object,
336 		guint property_id,
337 		GValue *value,
338 		GParamSpec *pspec )
339 {
340 	CelluloidModel *self = CELLULOID_MODEL(object);
341 
342 	switch(property_id)
343 	{
344 		case PROP_AID:
345 		g_value_set_string(value, self->aid);
346 		break;
347 
348 		case PROP_VID:
349 		g_value_set_string(value, self->vid);
350 		break;
351 
352 		case PROP_SID:
353 		g_value_set_string(value, self->sid);
354 		break;
355 
356 		case PROP_CHAPTERS:
357 		g_value_set_int64(value, self->chapters);
358 		break;
359 
360 		case PROP_CORE_IDLE:
361 		g_value_set_boolean(value, self->core_idle);
362 		break;
363 
364 		case PROP_IDLE_ACTIVE:
365 		g_value_set_boolean(value, self->idle_active);
366 		break;
367 
368 		case PROP_BORDER:
369 		g_value_set_boolean(value, self->border);
370 		break;
371 
372 		case PROP_FULLSCREEN:
373 		g_value_set_boolean(value, self->fullscreen);
374 		break;
375 
376 		case PROP_PAUSE:
377 		g_value_set_boolean(value, self->pause);
378 		break;
379 
380 		case PROP_LOOP_FILE:
381 		g_value_set_string(value, self->loop_file);
382 		break;
383 
384 		case PROP_LOOP_PLAYLIST:
385 		g_value_set_string(value, self->loop_playlist);
386 		break;
387 
388 		case PROP_SHUFFLE:
389 		g_value_set_boolean(value, self->shuffle);
390 		break;
391 
392 		case PROP_DURATION:
393 		g_value_set_double(value, self->duration);
394 		break;
395 
396 		case PROP_MEDIA_TITLE:
397 		g_value_set_string(value, self->media_title);
398 		break;
399 
400 		case PROP_PLAYLIST_COUNT:
401 		g_value_set_int64(value, self->playlist_count);
402 		break;
403 
404 		case PROP_PLAYLIST_POS:
405 		g_value_set_int64(value, self->idle_active?0:self->playlist_pos);
406 		break;
407 
408 		case PROP_SPEED:
409 		g_value_set_double(value, self->speed);
410 		break;
411 
412 		case PROP_VOLUME:
413 		g_value_set_double(value, self->volume);
414 		break;
415 
416 		case PROP_VOLUME_MAX:
417 		g_value_set_double(value, self->volume_max);
418 		break;
419 
420 		case PROP_WINDOW_MAXIMIZED:
421 		g_value_set_boolean(value, self->window_maximized);
422 		break;
423 
424 		case PROP_WINDOW_SCALE:
425 		g_value_set_double(value, self->window_scale);
426 		break;
427 
428 		case PROP_DISPLAY_FPS:
429 		g_value_set_double(value, self->display_fps);
430 		break;
431 
432 		default:
433 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
434 		break;
435 	}
436 }
437 
438 static void
dispose(GObject * object)439 dispose(GObject *object)
440 {
441 	CelluloidModel *model = CELLULOID_MODEL(object);
442 	CelluloidMpv *mpv = CELLULOID_MPV(model);
443 
444 	if(mpv)
445 	{
446 		celluloid_mpv_set_render_update_callback(mpv, NULL, NULL);
447 		while(g_source_remove_by_user_data(model));
448 	}
449 
450 	g_free(model->extra_options);
451 
452 	G_OBJECT_CLASS(celluloid_model_parent_class)->dispose(object);
453 }
454 
finalize(GObject * object)455 static void finalize(GObject *object)
456 {
457 	CelluloidModel *model = CELLULOID_MODEL(object);
458 
459 	g_free(model->aid);
460 	g_free(model->vid);
461 	g_free(model->sid);
462 	g_free(model->loop_file);
463 	g_free(model->loop_playlist);
464 	g_free(model->media_title);
465 
466 	G_OBJECT_CLASS(celluloid_model_parent_class)->finalize(object);
467 }
468 
469 static void
set_mpv_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)470 set_mpv_property(	GObject *object,
471 			guint property_id,
472 			const GValue *value,
473 			GParamSpec *pspec )
474 {
475 	CelluloidModel *self = CELLULOID_MODEL(object);
476 	CelluloidMpv *mpv = CELLULOID_MPV(self);
477 
478 	switch(property_id)
479 	{
480 		case PROP_AID:
481 		celluloid_mpv_set_property(	mpv,
482 						"aid",
483 						MPV_FORMAT_STRING,
484 						&self->aid );
485 		break;
486 
487 		case PROP_VID:
488 		celluloid_mpv_set_property(	mpv,
489 						"vid",
490 						MPV_FORMAT_STRING,
491 						&self->vid );
492 		break;
493 
494 		case PROP_SID:
495 		celluloid_mpv_set_property(	mpv,
496 						"sid",
497 						MPV_FORMAT_STRING,
498 						&self->sid );
499 		break;
500 
501 		case PROP_BORDER:
502 		celluloid_mpv_set_property(	mpv,
503 						"border",
504 						MPV_FORMAT_FLAG,
505 						&self->border );
506 		break;
507 
508 		case PROP_FULLSCREEN:
509 		celluloid_mpv_set_property(	mpv,
510 						"fullscreen",
511 						MPV_FORMAT_FLAG,
512 						&self->fullscreen );
513 		break;
514 
515 		case PROP_PAUSE:
516 		celluloid_mpv_set_property(	mpv,
517 						"pause",
518 						MPV_FORMAT_FLAG,
519 						&self->pause );
520 		break;
521 
522 		case PROP_LOOP_FILE:
523 		celluloid_mpv_set_property(	mpv,
524 						"loop-file",
525 						MPV_FORMAT_STRING,
526 						&self->loop_file );
527 		break;
528 
529 		case PROP_LOOP_PLAYLIST:
530 		celluloid_mpv_set_property(	mpv,
531 						"loop-playlist",
532 						MPV_FORMAT_STRING,
533 						&self->loop_playlist );
534 		break;
535 
536 		case PROP_PLAYLIST_POS:
537 		celluloid_mpv_set_property(	mpv,
538 						"playlist-pos",
539 						MPV_FORMAT_INT64,
540 						&self->playlist_pos );
541 		break;
542 
543 		case PROP_SPEED:
544 		celluloid_mpv_set_property(	mpv,
545 						"speed",
546 						MPV_FORMAT_DOUBLE,
547 						&self->speed );
548 		break;
549 
550 		case PROP_VOLUME:
551 		celluloid_mpv_set_property(	mpv,
552 						"volume",
553 						MPV_FORMAT_DOUBLE,
554 						&self->volume );
555 		break;
556 
557 		case PROP_VOLUME_MAX:
558 		celluloid_mpv_set_property(	mpv,
559 						"volume-max",
560 						MPV_FORMAT_DOUBLE,
561 						&self->volume_max );
562 		break;
563 
564 		case PROP_WINDOW_MAXIMIZED:
565 		celluloid_mpv_set_property(	mpv,
566 						"window-maximized",
567 						MPV_FORMAT_FLAG,
568 						&self->window_maximized );
569 		break;
570 
571 		case PROP_WINDOW_SCALE:
572 		celluloid_mpv_set_property(	mpv,
573 						"window-scale",
574 						MPV_FORMAT_DOUBLE,
575 						&self->window_scale );
576 		break;
577 
578 		case PROP_DISPLAY_FPS:
579 		celluloid_mpv_set_property(	mpv,
580 						"display-fps",
581 						MPV_FORMAT_DOUBLE,
582 						&self->display_fps );
583 		break;
584 	}
585 }
586 
587 static void
g_value_set_by_type(GValue * gvalue,GType type,gpointer value)588 g_value_set_by_type(GValue *gvalue, GType type, gpointer value)
589 {
590 	g_value_unset(gvalue);
591 	g_value_init(gvalue, type);
592 
593 	switch(type)
594 	{
595 		case G_TYPE_STRING:
596 		g_value_set_string(gvalue, *((const gchar **)value));
597 		break;
598 
599 		case G_TYPE_BOOLEAN:
600 		g_value_set_boolean(gvalue, *((gboolean *)value));
601 		break;
602 
603 		case G_TYPE_INT64:
604 		g_value_set_int64(gvalue, *((gint64 *)value));
605 		break;
606 
607 		case G_TYPE_DOUBLE:
608 		g_value_set_double(gvalue, *((gdouble *)value));
609 		break;
610 
611 		case G_TYPE_POINTER:
612 		g_value_set_pointer(gvalue, *((gpointer *)value));
613 		break;
614 
615 		default:
616 		g_assert_not_reached();
617 		break;
618 	}
619 }
620 
621 static GParamSpec *
g_param_spec_by_type(const gchar * name,const gchar * nick,const gchar * blurb,GType type,GParamFlags flags)622 g_param_spec_by_type(	const gchar *name,
623 			const gchar *nick,
624 			const gchar *blurb,
625 			GType type,
626 			GParamFlags flags )
627 {
628 	GParamSpec *result = NULL;
629 
630 	switch(type)
631 	{
632 		case G_TYPE_STRING:
633 		result = g_param_spec_string(name, nick, blurb, NULL, flags);
634 		break;
635 
636 		case G_TYPE_BOOLEAN:
637 		result = g_param_spec_boolean(name, nick, blurb, FALSE, flags);
638 		break;
639 
640 		case G_TYPE_INT64:
641 		result = g_param_spec_int64(	name,
642 						nick,
643 						blurb,
644 						G_MININT64,
645 						G_MAXINT64,
646 						0,
647 						flags );
648 		break;
649 
650 		case G_TYPE_DOUBLE:
651 		result = g_param_spec_double(	name,
652 						nick,
653 						blurb,
654 						-G_MAXDOUBLE,
655 						G_MAXDOUBLE,
656 						0.0,
657 						flags );
658 		break;
659 
660 		case G_TYPE_POINTER:
661 		result = g_param_spec_pointer(name, nick, blurb, flags);
662 		break;
663 
664 		default:
665 		g_assert_not_reached();
666 		break;
667 	}
668 
669 	return result;
670 }
671 
672 static gboolean
emit_frame_ready(gpointer data)673 emit_frame_ready(gpointer data)
674 {
675 	CelluloidModel *model = data;
676 	guint64 flags = celluloid_mpv_render_context_update(CELLULOID_MPV(model));
677 
678 	if(flags&MPV_RENDER_UPDATE_FRAME)
679 	{
680 		g_signal_emit_by_name(model, "frame-ready");
681 	}
682 
683 	return FALSE;
684 }
685 
686 static void
render_update_callback(gpointer data)687 render_update_callback(gpointer data)
688 {
689 	g_idle_add_full(	G_PRIORITY_HIGH,
690 				emit_frame_ready,
691 				data,
692 				NULL );
693 }
694 
695 static void
mpv_prop_change_handler(CelluloidMpv * mpv,const gchar * name,gpointer value,gpointer data)696 mpv_prop_change_handler(	CelluloidMpv *mpv,
697 				const gchar *name,
698 				gpointer value,
699 				gpointer data )
700 {
701 	if(	g_strcmp0(name, "playlist") != 0 &&
702 		g_strcmp0(name, "metadata") != 0 &&
703 		g_strcmp0(name, "track-list") != 0 )
704 	{
705 		GObjectClass *klass;
706 		GParamSpec *pspec;
707 		GValue gvalue = G_VALUE_INIT;
708 
709 		klass =	G_TYPE_INSTANCE_GET_CLASS
710 			(data, CELLULOID_TYPE_MODEL, GObjectClass);
711 		pspec = g_object_class_find_property(klass, name);
712 
713 		if(pspec && value)
714 		{
715 			CELLULOID_MODEL(data)->update_mpv_properties = FALSE;
716 
717 			g_value_set_by_type(&gvalue, pspec->value_type, value);
718 			g_object_set_property(data, name, &gvalue);
719 
720 			CELLULOID_MODEL(data)->update_mpv_properties = TRUE;
721 		}
722 	}
723 }
724 
725 static void
celluloid_model_class_init(CelluloidModelClass * klass)726 celluloid_model_class_init(CelluloidModelClass *klass)
727 {
728 	/* The "no" value of aid, vid, and sid cannot be represented with an
729 	 * int64, so we need to observe them as string to receive notifications
730 	 * for all possible values.
731 	 */
732 	const struct
733 	{
734 		const gchar *name;
735 		guint id;
736 		GType type;
737 	}
738 	mpv_props[] = {	{"aid", PROP_AID, G_TYPE_STRING},
739 			{"vid", PROP_VID, G_TYPE_STRING},
740 			{"sid", PROP_SID, G_TYPE_STRING},
741 			{"chapters", PROP_CHAPTERS, G_TYPE_INT64},
742 			{"core-idle", PROP_CORE_IDLE, G_TYPE_BOOLEAN},
743 			{"idle-active", PROP_IDLE_ACTIVE, G_TYPE_BOOLEAN},
744 			{"border", PROP_BORDER, G_TYPE_BOOLEAN},
745 			{"fullscreen", PROP_FULLSCREEN, G_TYPE_BOOLEAN},
746 			{"pause", PROP_PAUSE, G_TYPE_BOOLEAN},
747 			{"loop-file", PROP_LOOP_FILE, G_TYPE_STRING},
748 			{"loop-playlist", PROP_LOOP_PLAYLIST, G_TYPE_STRING},
749 			{"duration", PROP_DURATION, G_TYPE_DOUBLE},
750 			{"media-title", PROP_MEDIA_TITLE, G_TYPE_STRING},
751 			{"playlist-count", PROP_PLAYLIST_COUNT, G_TYPE_INT64},
752 			{"playlist-pos", PROP_PLAYLIST_POS, G_TYPE_INT64},
753 			{"speed", PROP_SPEED, G_TYPE_DOUBLE},
754 			{"volume", PROP_VOLUME, G_TYPE_DOUBLE},
755 			{"volume-max", PROP_VOLUME_MAX, G_TYPE_DOUBLE},
756 			{"window-maximized", PROP_WINDOW_MAXIMIZED, G_TYPE_BOOLEAN},
757 			{"window-scale", PROP_WINDOW_SCALE, G_TYPE_DOUBLE},
758 			{NULL, PROP_INVALID, 0} };
759 
760 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
761 	GParamSpec *pspec = NULL;
762 
763 	obj_class->set_property = set_property;
764 	obj_class->get_property = get_property;
765 	obj_class->dispose = dispose;
766 	obj_class->finalize = finalize;
767 
768 	for(int i = 0; mpv_props[i].name; i++)
769 	{
770 		pspec = g_param_spec_by_type(	mpv_props[i].name,
771 						mpv_props[i].name,
772 						mpv_props[i].name,
773 						mpv_props[i].type,
774 						G_PARAM_READWRITE );
775 		g_object_class_install_property
776 			(obj_class, mpv_props[i].id, pspec);
777 	}
778 
779 	pspec = g_param_spec_boolean
780 		(	"shuffle",
781 			"Shuffle",
782 			"Whether or not the playlist is shuffled",
783 			FALSE,
784 			G_PARAM_READWRITE );
785 	g_object_class_install_property(obj_class, PROP_SHUFFLE, pspec);
786 
787 	g_signal_new(	"playlist-replaced",
788 			G_TYPE_FROM_CLASS(klass),
789 			G_SIGNAL_RUN_FIRST,
790 			0,
791 			NULL,
792 			NULL,
793 			g_cclosure_marshal_VOID__VOID,
794 			G_TYPE_NONE,
795 			0 );
796 	g_signal_new(	"playback-restart",
797 			G_TYPE_FROM_CLASS(klass),
798 			G_SIGNAL_RUN_FIRST,
799 			0,
800 			NULL,
801 			NULL,
802 			g_cclosure_marshal_VOID__VOID,
803 			G_TYPE_NONE,
804 			0 );
805 	g_signal_new(	"frame-ready",
806 			G_TYPE_FROM_CLASS(klass),
807 			G_SIGNAL_RUN_FIRST,
808 			0,
809 			NULL,
810 			NULL,
811 			g_cclosure_marshal_VOID__VOID,
812 			G_TYPE_NONE,
813 			0 );
814 }
815 
816 static void
celluloid_model_init(CelluloidModel * model)817 celluloid_model_init(CelluloidModel *model)
818 {
819 	model->extra_options = NULL;
820 	model->metadata = NULL;
821 	model->track_list = NULL;
822 	model->update_mpv_properties = TRUE;
823 	model->resetting = FALSE;
824 	model->aid = NULL;
825 	model->vid = NULL;
826 	model->sid = NULL;
827 	model->chapters = 0;
828 	model->core_idle = FALSE;
829 	model->idle_active = FALSE;
830 	model->border = FALSE;
831 	model->fullscreen = FALSE;
832 	model->pause = TRUE;
833 	model->loop_file = NULL;
834 	model->loop_playlist = NULL;
835 	model->shuffle = FALSE;
836 	model->duration = 0.0;
837 	model->media_title = NULL;
838 	model->playlist_count = 0;
839 	model->playlist_pos = 0;
840 	model->speed = 1.0;
841 	model->volume = 1.0;
842 	model->volume_max = 100.0;
843 	model->window_maximized = FALSE;
844 	model->window_scale = 1.0;
845 	model->display_fps = 0.0;
846 }
847 
848 CelluloidModel *
celluloid_model_new(gint64 wid)849 celluloid_model_new(gint64 wid)
850 {
851 	const GType type = celluloid_model_get_type();
852 	CelluloidModel *model =  CELLULOID_MODEL(g_object_new(	type,
853 								"wid", wid,
854 								NULL ));
855 
856 	g_signal_connect(	model,
857 				"mpv-property-changed",
858 				G_CALLBACK(mpv_prop_change_handler),
859 				model );
860 
861 	return model;
862 }
863 
864 void
celluloid_model_initialize(CelluloidModel * model)865 celluloid_model_initialize(CelluloidModel *model)
866 {
867 	CelluloidMpv *mpv = CELLULOID_MPV(model);
868 	GSettings *win_settings = g_settings_new(CONFIG_WIN_STATE);
869 
870 	celluloid_mpv_initialize
871 		(mpv);
872 	celluloid_mpv_set_render_update_callback
873 		(mpv, render_update_callback, model);
874 
875 	if(!extra_options_contains(model, "volume"))
876 	{
877 		gdouble volume = g_settings_get_double(win_settings, "volume")*100;
878 
879 		g_debug("Setting volume to %f", volume);
880 		g_object_set(model, "volume", volume, NULL);
881 	}
882 
883 	if(extra_options_contains(model, "shuffle"))
884 	{
885 		// Sync the property to match mpv's
886 		CelluloidMpv *mpv =
887 			CELLULOID_MPV(model);
888 		gboolean shuffle =
889 			celluloid_mpv_get_property_flag(mpv, "shuffle");
890 
891 		g_object_set(model, "shuffle", shuffle, NULL);
892 	}
893 
894 	if(extra_options_contains(model, "loop-playlist"))
895 	{
896 		// Sync the property to match mpv's
897 		CelluloidMpv *mpv =
898 			CELLULOID_MPV(model);
899 		gchar *loop_playlist =
900 			celluloid_mpv_get_property_string(mpv, "loop-playlist");
901 
902 		g_object_set(model, "loop-playlist", loop_playlist, NULL);
903 
904 		mpv_free(loop_playlist);
905 	}
906 	else
907 	{
908 		const gchar *loop_playlist =
909 			g_settings_get_boolean(win_settings, "loop-playlist") ?
910 			"inf" : "no";
911 
912 		g_object_set(model, "loop-playlist", loop_playlist, NULL);
913 	}
914 
915 	g_object_unref(win_settings);
916 }
917 
918 void
celluloid_model_reset(CelluloidModel * model)919 celluloid_model_reset(CelluloidModel *model)
920 {
921 	model->resetting = TRUE;
922 
923 	celluloid_mpv_reset(CELLULOID_MPV(model));
924 }
925 
926 void
celluloid_model_quit(CelluloidModel * model)927 celluloid_model_quit(CelluloidModel *model)
928 {
929 	celluloid_mpv_quit(CELLULOID_MPV(model));
930 }
931 
932 void
celluloid_model_mouse(CelluloidModel * model,gint x,gint y)933 celluloid_model_mouse(CelluloidModel *model, gint x, gint y)
934 {
935 	gchar *x_str = g_strdup_printf("%d", x);
936 	gchar *y_str = g_strdup_printf("%d", y);
937 	const gchar *cmd[] = {"mouse", x_str, y_str, NULL};
938 
939 	g_debug("Set mouse location to (%s, %s)", x_str, y_str);
940 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
941 
942 	g_free(x_str);
943 	g_free(y_str);
944 }
945 
946 void
celluloid_model_key_down(CelluloidModel * model,const gchar * keystr)947 celluloid_model_key_down(CelluloidModel *model, const gchar* keystr)
948 {
949 	const gchar *cmd[] = {"keydown", keystr, NULL};
950 
951 	g_debug("Sent '%s' key down to mpv", keystr);
952 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
953 }
954 
955 void
celluloid_model_key_up(CelluloidModel * model,const gchar * keystr)956 celluloid_model_key_up(CelluloidModel *model, const gchar* keystr)
957 {
958 	const gchar *cmd[] = {"keyup", keystr, NULL};
959 
960 	g_debug("Sent '%s' key up to mpv", keystr);
961 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
962 }
963 
964 void
celluloid_model_key_press(CelluloidModel * model,const gchar * keystr)965 celluloid_model_key_press(CelluloidModel *model, const gchar* keystr)
966 {
967 	const gchar *cmd[] = {"keypress", keystr, NULL};
968 
969 	g_debug("Sent '%s' key press to mpv", keystr);
970 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
971 }
972 
973 void
celluloid_model_reset_keys(CelluloidModel * model)974 celluloid_model_reset_keys(CelluloidModel *model)
975 {
976 	// As of Sep 20, 2019, mpv will crash if the command doesn't have any
977 	// arguments. The empty string is there to work around the issue.
978 	const gchar *cmd[] = {"keyup", "", NULL};
979 
980 	g_debug("Sent global key up to mpv");
981 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
982 }
983 
984 void
celluloid_model_play(CelluloidModel * model)985 celluloid_model_play(CelluloidModel *model)
986 {
987 	celluloid_mpv_set_property_flag(CELLULOID_MPV(model), "pause", FALSE);
988 }
989 
990 void
celluloid_model_pause(CelluloidModel * model)991 celluloid_model_pause(CelluloidModel *model)
992 {
993 	celluloid_mpv_set_property_flag(CELLULOID_MPV(model), "pause", TRUE);
994 }
995 
996 void
celluloid_model_stop(CelluloidModel * model)997 celluloid_model_stop(CelluloidModel *model)
998 {
999 	const gchar *cmd[] = {"stop", NULL};
1000 
1001 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1002 }
1003 
1004 void
celluloid_model_forward(CelluloidModel * model)1005 celluloid_model_forward(CelluloidModel *model)
1006 {
1007 	const gchar *cmd[] = {"seek", "10", NULL};
1008 
1009 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1010 }
1011 
1012 void
celluloid_model_rewind(CelluloidModel * model)1013 celluloid_model_rewind(CelluloidModel *model)
1014 {
1015 	const gchar *cmd[] = {"seek", "-10", NULL};
1016 
1017 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1018 }
1019 
1020 void
celluloid_model_next_chapter(CelluloidModel * model)1021 celluloid_model_next_chapter(CelluloidModel *model)
1022 {
1023 	const gchar *cmd[] = {"osd-msg", "cycle", "chapter", NULL};
1024 
1025 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1026 }
1027 
1028 void
celluloid_model_previous_chapter(CelluloidModel * model)1029 celluloid_model_previous_chapter(CelluloidModel *model)
1030 {
1031 	const gchar *cmd[] = {"osd-msg", "cycle", "chapter", "down", NULL};
1032 
1033 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1034 }
1035 
1036 void
celluloid_model_next_playlist_entry(CelluloidModel * model)1037 celluloid_model_next_playlist_entry(CelluloidModel *model)
1038 {
1039 	const gchar *cmd[] = {"osd-msg", "playlist-next", "weak", NULL};
1040 
1041 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1042 }
1043 
1044 void
celluloid_model_previous_playlist_entry(CelluloidModel * model)1045 celluloid_model_previous_playlist_entry(CelluloidModel *model)
1046 {
1047 	const gchar *cmd[] = {"osd-msg", "playlist-prev", "weak", NULL};
1048 
1049 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1050 }
1051 
1052 void
celluloid_model_shuffle_playlist(CelluloidModel * model)1053 celluloid_model_shuffle_playlist(CelluloidModel *model)
1054 {
1055 	const gchar *cmd[] = {"osd-msg", "playlist-shuffle", NULL};
1056 
1057 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1058 }
1059 
1060 void
celluloid_model_unshuffle_playlist(CelluloidModel * model)1061 celluloid_model_unshuffle_playlist(CelluloidModel *model)
1062 {
1063 	const gchar *cmd[] = {"osd-msg", "playlist-unshuffle", NULL};
1064 
1065 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1066 }
1067 
1068 void
celluloid_model_seek(CelluloidModel * model,gdouble value)1069 celluloid_model_seek(CelluloidModel *model, gdouble value)
1070 {
1071 	celluloid_mpv_set_property(CELLULOID_MPV(model), "time-pos", MPV_FORMAT_DOUBLE, &value);
1072 }
1073 
1074 void
celluloid_model_seek_offset(CelluloidModel * model,gdouble offset)1075 celluloid_model_seek_offset(CelluloidModel *model, gdouble offset)
1076 {
1077 	const gchar *cmd[] = {"seek", NULL, NULL};
1078 	gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
1079 
1080 	g_ascii_dtostr(buf, G_ASCII_DTOSTR_BUF_SIZE, offset);
1081 	cmd[1] = buf;
1082 
1083 	celluloid_mpv_command_async(CELLULOID_MPV(model), cmd);
1084 }
1085 
1086 void
celluloid_model_load_audio_track(CelluloidModel * model,const gchar * filename)1087 celluloid_model_load_audio_track(CelluloidModel *model, const gchar *filename)
1088 {
1089 	celluloid_mpv_load_track
1090 		(CELLULOID_MPV(model), filename, TRACK_TYPE_AUDIO);
1091 }
1092 
1093 void
celluloid_model_load_video_track(CelluloidModel * model,const gchar * filename)1094 celluloid_model_load_video_track(CelluloidModel *model, const gchar *filename)
1095 {
1096 	celluloid_mpv_load_track
1097 		(CELLULOID_MPV(model), filename, TRACK_TYPE_VIDEO);
1098 }
1099 
1100 void
celluloid_model_load_subtitle_track(CelluloidModel * model,const gchar * filename)1101 celluloid_model_load_subtitle_track(	CelluloidModel *model,
1102 					const gchar *filename )
1103 {
1104 	celluloid_mpv_load_track
1105 		(CELLULOID_MPV(model), filename, TRACK_TYPE_SUBTITLE);
1106 }
1107 
1108 gdouble
celluloid_model_get_time_position(CelluloidModel * model)1109 celluloid_model_get_time_position(CelluloidModel *model)
1110 {
1111 	gdouble time_pos = 0.0;
1112 
1113 	if(!model->idle_active)
1114 	{
1115 		celluloid_mpv_get_property(	CELLULOID_MPV(model),
1116 						"time-pos",
1117 						MPV_FORMAT_DOUBLE,
1118 						&time_pos );
1119 	}
1120 
1121 	/* time-pos may become negative during seeks */
1122 	return MAX(0, time_pos);
1123 }
1124 
1125 void
celluloid_model_set_playlist_position(CelluloidModel * model,gint64 position)1126 celluloid_model_set_playlist_position(CelluloidModel *model, gint64 position)
1127 {
1128 	celluloid_player_set_playlist_position
1129 		(CELLULOID_PLAYER(model), position);
1130 }
1131 
1132 void
celluloid_model_remove_playlist_entry(CelluloidModel * model,gint64 position)1133 celluloid_model_remove_playlist_entry(CelluloidModel *model, gint64 position)
1134 {
1135 	celluloid_player_remove_playlist_entry
1136 		(CELLULOID_PLAYER(model), position);
1137 }
1138 
1139 void
celluloid_model_move_playlist_entry(CelluloidModel * model,gint64 src,gint64 dst)1140 celluloid_model_move_playlist_entry(	CelluloidModel *model,
1141 					gint64 src,
1142 					gint64 dst )
1143 {
1144 	celluloid_player_move_playlist_entry(CELLULOID_PLAYER(model), src, dst);
1145 }
1146 
1147 void
celluloid_model_load_file(CelluloidModel * model,const gchar * uri,gboolean append)1148 celluloid_model_load_file(	CelluloidModel *model,
1149 				const gchar *uri,
1150 				gboolean append )
1151 {
1152 	GSettings *settings = g_settings_new(CONFIG_ROOT);
1153 
1154 	append |= g_settings_get_boolean(settings, "always-append-to-playlist");
1155 
1156 	celluloid_mpv_load(CELLULOID_MPV(model), uri, append);
1157 
1158 	/* Start playing when replacing the playlist, ie. not appending, or
1159 	 * adding the first file to the playlist.
1160 	 */
1161 	if(!append || model->playlist_count == 0)
1162 	{
1163 		g_signal_emit_by_name(model, "playlist-replaced");
1164 		celluloid_model_play(model);
1165 	}
1166 
1167 	g_object_unref(settings);
1168 }
1169 
1170 gboolean
celluloid_model_get_use_opengl_cb(CelluloidModel * model)1171 celluloid_model_get_use_opengl_cb(CelluloidModel *model)
1172 {
1173 	return celluloid_mpv_get_use_opengl_cb(CELLULOID_MPV(model));
1174 }
1175 
1176 void
celluloid_model_initialize_gl(CelluloidModel * model)1177 celluloid_model_initialize_gl(CelluloidModel *model)
1178 {
1179 	celluloid_mpv_init_gl(CELLULOID_MPV(model));
1180 }
1181 
1182 void
celluloid_model_render_frame(CelluloidModel * model,gint width,gint height)1183 celluloid_model_render_frame(CelluloidModel *model, gint width, gint height)
1184 {
1185 	mpv_render_context *render_ctx;
1186 
1187 	render_ctx = celluloid_mpv_get_render_context(CELLULOID_MPV(model));
1188 
1189 	if(render_ctx)
1190 	{
1191 		gint fbo = -1;
1192 		glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);
1193 
1194 		mpv_opengl_fbo opengl_fbo =
1195 			{fbo, width, height, 0};
1196 		mpv_render_param params[] =
1197 			{	{MPV_RENDER_PARAM_OPENGL_FBO, &opengl_fbo},
1198 				{MPV_RENDER_PARAM_FLIP_Y, &(int){1}},
1199 				{0, NULL} };
1200 
1201 		mpv_render_context_render(render_ctx, params);
1202 	}
1203 }
1204 
1205 void
celluloid_model_get_video_geometry(CelluloidModel * model,gint64 * width,gint64 * height)1206 celluloid_model_get_video_geometry(	CelluloidModel *model,
1207 					gint64 *width,
1208 					gint64 *height )
1209 {
1210 	CelluloidMpv *mpv = CELLULOID_MPV(model);
1211 
1212 	celluloid_mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, width);
1213 	celluloid_mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, height);
1214 }
1215 
1216 gchar *
celluloid_model_get_current_path(CelluloidModel * model)1217 celluloid_model_get_current_path(CelluloidModel *model)
1218 {
1219 	CelluloidMpv *mpv = CELLULOID_MPV(model);
1220 	gchar *path = celluloid_mpv_get_property_string(mpv, "path");
1221 	gchar *buf = g_strdup(path);
1222 
1223 	mpv_free(path);
1224 
1225 	return buf;
1226 }
1227