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