/* * Copyright (c) 2017-2021 gnome-mpv * * This file is part of Celluloid. * * Celluloid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Celluloid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Celluloid. If not, see . */ #include #include "celluloid-model.h" #include "celluloid-marshal.h" #include "celluloid-mpv.h" #include "celluloid-option-parser.h" #include "celluloid-def.h" enum { PROP_INVALID, PROP_AID, PROP_VID, PROP_SID, PROP_CHAPTERS, PROP_CORE_IDLE, PROP_IDLE_ACTIVE, PROP_BORDER, PROP_FULLSCREEN, PROP_PAUSE, PROP_LOOP_FILE, PROP_LOOP_PLAYLIST, PROP_SHUFFLE, PROP_DURATION, PROP_MEDIA_TITLE, PROP_PLAYLIST_COUNT, PROP_PLAYLIST_POS, PROP_SPEED, PROP_VOLUME, PROP_VOLUME_MAX, PROP_WINDOW_MAXIMIZED, PROP_WINDOW_SCALE, PROP_DISPLAY_FPS, N_PROPERTIES }; struct _CelluloidModel { CelluloidPlayer parent; gchar *extra_options; GPtrArray *metadata; GPtrArray *track_list; gboolean update_mpv_properties; gboolean resetting; gchar *aid; gchar *vid; gchar *sid; gint64 chapters; gboolean core_idle; gboolean idle_active; gboolean border; gboolean fullscreen; gboolean pause; gchar *loop_file; gchar *loop_playlist; gboolean shuffle; gdouble duration; gchar *media_title; gint64 playlist_count; gint64 playlist_pos; gdouble speed; gdouble volume; gdouble volume_max; gboolean window_maximized; gdouble window_scale; gdouble display_fps; }; struct _CelluloidModelClass { GObjectClass parent_class; }; static gboolean extra_options_contains(CelluloidModel *model, const gchar *option); static void set_property( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec ); static void get_property( GObject *object, guint property_id, GValue *value, GParamSpec *pspec ); static void dispose(GObject *object); static void finalize(GObject *object); static void set_mpv_property( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec ); static void g_value_set_by_type(GValue *gvalue, GType type, gpointer value); static GParamSpec * g_param_spec_by_type( const gchar *name, const gchar *nick, const gchar *blurb, GType type, GParamFlags flags ); static gboolean emit_frame_ready(gpointer data); static void render_update_callback(gpointer render_ctx); static void mpv_prop_change_handler( CelluloidMpv *mpv, const gchar *name, gpointer value, gpointer data ); G_DEFINE_TYPE(CelluloidModel, celluloid_model, CELLULOID_TYPE_PLAYER) static gboolean extra_options_contains(CelluloidModel *model, const gchar *option) { gboolean result = FALSE; gchar *extra_options = NULL; const gchar *cur = NULL; g_object_get(model, "extra-options", &extra_options, NULL); cur = extra_options; while(cur && *cur && !result) { gchar *key = NULL; gchar *value = NULL; cur = parse_option(cur, &key, &value); if(key && *key) { result |= g_strcmp0(key, option) == 0; } else { g_warning("Failed to parse options"); cur = NULL; } g_free(key); g_free(value); } g_free(extra_options); return result; } static void set_property( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec ) { CelluloidModel *self = CELLULOID_MODEL(object); switch(property_id) { case PROP_AID: g_free(self->aid); self->aid = g_value_dup_string(value); break; case PROP_VID: g_free(self->vid); self->vid = g_value_dup_string(value); break; case PROP_SID: g_free(self->sid); self->sid = g_value_dup_string(value); break; case PROP_CHAPTERS: self->chapters = g_value_get_int64(value); break; case PROP_CORE_IDLE: self->core_idle = g_value_get_boolean(value); if(self->resetting) { if(self->pause) { celluloid_model_pause(self); } else { celluloid_model_play(self); } self->resetting = FALSE; } break; case PROP_IDLE_ACTIVE: self->idle_active = g_value_get_boolean(value); if(self->idle_active) { g_object_notify(object, "playlist-pos"); } break; case PROP_BORDER: self->border = g_value_get_boolean(value); break; case PROP_FULLSCREEN: self->fullscreen = g_value_get_boolean(value); break; case PROP_PAUSE: self->pause = g_value_get_boolean(value); break; case PROP_LOOP_FILE: g_free(self->loop_file); self->loop_file = g_value_dup_string(value); break; case PROP_LOOP_PLAYLIST: g_free(self->loop_playlist); self->loop_playlist = g_value_dup_string(value); break; case PROP_SHUFFLE: { gboolean ready = FALSE; self->shuffle = g_value_get_boolean(value); g_object_get(self, "ready", &ready, NULL); if(ready) { if(self->shuffle) { celluloid_model_shuffle_playlist(self); } else { celluloid_model_unshuffle_playlist(self); } } } break; case PROP_DURATION: self->duration = g_value_get_double(value); break; case PROP_MEDIA_TITLE: g_free(self->media_title); self->media_title = g_value_dup_string(value); break; case PROP_PLAYLIST_COUNT: self->playlist_count = g_value_get_int64(value); break; case PROP_PLAYLIST_POS: self->playlist_pos = g_value_get_int64(value); break; case PROP_SPEED: self->speed = g_value_get_double(value); break; case PROP_VOLUME: self->volume = g_value_get_double(value); break; case PROP_VOLUME_MAX: self->volume_max = g_value_get_double(value); break; case PROP_WINDOW_MAXIMIZED: self->window_maximized = g_value_get_boolean(value); break; case PROP_WINDOW_SCALE: self->window_scale = g_value_get_double(value); break; case PROP_DISPLAY_FPS: self->display_fps = g_value_get_double(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } /* Do not propagate changes from mpv back to itself */ if(self->update_mpv_properties) { set_mpv_property(object, property_id, value, pspec); } } static void get_property( GObject *object, guint property_id, GValue *value, GParamSpec *pspec ) { CelluloidModel *self = CELLULOID_MODEL(object); switch(property_id) { case PROP_AID: g_value_set_string(value, self->aid); break; case PROP_VID: g_value_set_string(value, self->vid); break; case PROP_SID: g_value_set_string(value, self->sid); break; case PROP_CHAPTERS: g_value_set_int64(value, self->chapters); break; case PROP_CORE_IDLE: g_value_set_boolean(value, self->core_idle); break; case PROP_IDLE_ACTIVE: g_value_set_boolean(value, self->idle_active); break; case PROP_BORDER: g_value_set_boolean(value, self->border); break; case PROP_FULLSCREEN: g_value_set_boolean(value, self->fullscreen); break; case PROP_PAUSE: g_value_set_boolean(value, self->pause); break; case PROP_LOOP_FILE: g_value_set_string(value, self->loop_file); break; case PROP_LOOP_PLAYLIST: g_value_set_string(value, self->loop_playlist); break; case PROP_SHUFFLE: g_value_set_boolean(value, self->shuffle); break; case PROP_DURATION: g_value_set_double(value, self->duration); break; case PROP_MEDIA_TITLE: g_value_set_string(value, self->media_title); break; case PROP_PLAYLIST_COUNT: g_value_set_int64(value, self->playlist_count); break; case PROP_PLAYLIST_POS: g_value_set_int64(value, self->idle_active?0:self->playlist_pos); break; case PROP_SPEED: g_value_set_double(value, self->speed); break; case PROP_VOLUME: g_value_set_double(value, self->volume); break; case PROP_VOLUME_MAX: g_value_set_double(value, self->volume_max); break; case PROP_WINDOW_MAXIMIZED: g_value_set_boolean(value, self->window_maximized); break; case PROP_WINDOW_SCALE: g_value_set_double(value, self->window_scale); break; case PROP_DISPLAY_FPS: g_value_set_double(value, self->display_fps); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void dispose(GObject *object) { CelluloidModel *model = CELLULOID_MODEL(object); CelluloidMpv *mpv = CELLULOID_MPV(model); if(mpv) { celluloid_mpv_set_render_update_callback(mpv, NULL, NULL); while(g_source_remove_by_user_data(model)); } g_free(model->extra_options); G_OBJECT_CLASS(celluloid_model_parent_class)->dispose(object); } static void finalize(GObject *object) { CelluloidModel *model = CELLULOID_MODEL(object); g_free(model->aid); g_free(model->vid); g_free(model->sid); g_free(model->loop_file); g_free(model->loop_playlist); g_free(model->media_title); G_OBJECT_CLASS(celluloid_model_parent_class)->finalize(object); } static void set_mpv_property( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec ) { CelluloidModel *self = CELLULOID_MODEL(object); CelluloidMpv *mpv = CELLULOID_MPV(self); switch(property_id) { case PROP_AID: celluloid_mpv_set_property( mpv, "aid", MPV_FORMAT_STRING, &self->aid ); break; case PROP_VID: celluloid_mpv_set_property( mpv, "vid", MPV_FORMAT_STRING, &self->vid ); break; case PROP_SID: celluloid_mpv_set_property( mpv, "sid", MPV_FORMAT_STRING, &self->sid ); break; case PROP_BORDER: celluloid_mpv_set_property( mpv, "border", MPV_FORMAT_FLAG, &self->border ); break; case PROP_FULLSCREEN: celluloid_mpv_set_property( mpv, "fullscreen", MPV_FORMAT_FLAG, &self->fullscreen ); break; case PROP_PAUSE: celluloid_mpv_set_property( mpv, "pause", MPV_FORMAT_FLAG, &self->pause ); break; case PROP_LOOP_FILE: celluloid_mpv_set_property( mpv, "loop-file", MPV_FORMAT_STRING, &self->loop_file ); break; case PROP_LOOP_PLAYLIST: celluloid_mpv_set_property( mpv, "loop-playlist", MPV_FORMAT_STRING, &self->loop_playlist ); break; case PROP_PLAYLIST_POS: celluloid_mpv_set_property( mpv, "playlist-pos", MPV_FORMAT_INT64, &self->playlist_pos ); break; case PROP_SPEED: celluloid_mpv_set_property( mpv, "speed", MPV_FORMAT_DOUBLE, &self->speed ); break; case PROP_VOLUME: celluloid_mpv_set_property( mpv, "volume", MPV_FORMAT_DOUBLE, &self->volume ); break; case PROP_VOLUME_MAX: celluloid_mpv_set_property( mpv, "volume-max", MPV_FORMAT_DOUBLE, &self->volume_max ); break; case PROP_WINDOW_MAXIMIZED: celluloid_mpv_set_property( mpv, "window-maximized", MPV_FORMAT_FLAG, &self->window_maximized ); break; case PROP_WINDOW_SCALE: celluloid_mpv_set_property( mpv, "window-scale", MPV_FORMAT_DOUBLE, &self->window_scale ); break; case PROP_DISPLAY_FPS: celluloid_mpv_set_property( mpv, "display-fps", MPV_FORMAT_DOUBLE, &self->display_fps ); break; } } static void g_value_set_by_type(GValue *gvalue, GType type, gpointer value) { g_value_unset(gvalue); g_value_init(gvalue, type); switch(type) { case G_TYPE_STRING: g_value_set_string(gvalue, *((const gchar **)value)); break; case G_TYPE_BOOLEAN: g_value_set_boolean(gvalue, *((gboolean *)value)); break; case G_TYPE_INT64: g_value_set_int64(gvalue, *((gint64 *)value)); break; case G_TYPE_DOUBLE: g_value_set_double(gvalue, *((gdouble *)value)); break; case G_TYPE_POINTER: g_value_set_pointer(gvalue, *((gpointer *)value)); break; default: g_assert_not_reached(); break; } } static GParamSpec * g_param_spec_by_type( const gchar *name, const gchar *nick, const gchar *blurb, GType type, GParamFlags flags ) { GParamSpec *result = NULL; switch(type) { case G_TYPE_STRING: result = g_param_spec_string(name, nick, blurb, NULL, flags); break; case G_TYPE_BOOLEAN: result = g_param_spec_boolean(name, nick, blurb, FALSE, flags); break; case G_TYPE_INT64: result = g_param_spec_int64( name, nick, blurb, G_MININT64, G_MAXINT64, 0, flags ); break; case G_TYPE_DOUBLE: result = g_param_spec_double( name, nick, blurb, -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, flags ); break; case G_TYPE_POINTER: result = g_param_spec_pointer(name, nick, blurb, flags); break; default: g_assert_not_reached(); break; } return result; } static gboolean emit_frame_ready(gpointer data) { CelluloidModel *model = data; guint64 flags = celluloid_mpv_render_context_update(CELLULOID_MPV(model)); if(flags&MPV_RENDER_UPDATE_FRAME) { g_signal_emit_by_name(model, "frame-ready"); } return FALSE; } static void render_update_callback(gpointer data) { g_idle_add_full( G_PRIORITY_HIGH, emit_frame_ready, data, NULL ); } static void mpv_prop_change_handler( CelluloidMpv *mpv, const gchar *name, gpointer value, gpointer data ) { if( g_strcmp0(name, "playlist") != 0 && g_strcmp0(name, "metadata") != 0 && g_strcmp0(name, "track-list") != 0 ) { GObjectClass *klass; GParamSpec *pspec; GValue gvalue = G_VALUE_INIT; klass = G_TYPE_INSTANCE_GET_CLASS (data, CELLULOID_TYPE_MODEL, GObjectClass); pspec = g_object_class_find_property(klass, name); if(pspec && value) { CELLULOID_MODEL(data)->update_mpv_properties = FALSE; g_value_set_by_type(&gvalue, pspec->value_type, value); g_object_set_property(data, name, &gvalue); CELLULOID_MODEL(data)->update_mpv_properties = TRUE; } } } static void celluloid_model_class_init(CelluloidModelClass *klass) { /* The "no" value of aid, vid, and sid cannot be represented with an * int64, so we need to observe them as string to receive notifications * for all possible values. */ const struct { const gchar *name; guint id; GType type; } mpv_props[] = { {"aid", PROP_AID, G_TYPE_STRING}, {"vid", PROP_VID, G_TYPE_STRING}, {"sid", PROP_SID, G_TYPE_STRING}, {"chapters", PROP_CHAPTERS, G_TYPE_INT64}, {"core-idle", PROP_CORE_IDLE, G_TYPE_BOOLEAN}, {"idle-active", PROP_IDLE_ACTIVE, G_TYPE_BOOLEAN}, {"border", PROP_BORDER, G_TYPE_BOOLEAN}, {"fullscreen", PROP_FULLSCREEN, G_TYPE_BOOLEAN}, {"pause", PROP_PAUSE, G_TYPE_BOOLEAN}, {"loop-file", PROP_LOOP_FILE, G_TYPE_STRING}, {"loop-playlist", PROP_LOOP_PLAYLIST, G_TYPE_STRING}, {"duration", PROP_DURATION, G_TYPE_DOUBLE}, {"media-title", PROP_MEDIA_TITLE, G_TYPE_STRING}, {"playlist-count", PROP_PLAYLIST_COUNT, G_TYPE_INT64}, {"playlist-pos", PROP_PLAYLIST_POS, G_TYPE_INT64}, {"speed", PROP_SPEED, G_TYPE_DOUBLE}, {"volume", PROP_VOLUME, G_TYPE_DOUBLE}, {"volume-max", PROP_VOLUME_MAX, G_TYPE_DOUBLE}, {"window-maximized", PROP_WINDOW_MAXIMIZED, G_TYPE_BOOLEAN}, {"window-scale", PROP_WINDOW_SCALE, G_TYPE_DOUBLE}, {NULL, PROP_INVALID, 0} }; GObjectClass *obj_class = G_OBJECT_CLASS(klass); GParamSpec *pspec = NULL; obj_class->set_property = set_property; obj_class->get_property = get_property; obj_class->dispose = dispose; obj_class->finalize = finalize; for(int i = 0; mpv_props[i].name; i++) { pspec = g_param_spec_by_type( mpv_props[i].name, mpv_props[i].name, mpv_props[i].name, mpv_props[i].type, G_PARAM_READWRITE ); g_object_class_install_property (obj_class, mpv_props[i].id, pspec); } pspec = g_param_spec_boolean ( "shuffle", "Shuffle", "Whether or not the playlist is shuffled", FALSE, G_PARAM_READWRITE ); g_object_class_install_property(obj_class, PROP_SHUFFLE, pspec); g_signal_new( "playlist-replaced", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); g_signal_new( "playback-restart", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); g_signal_new( "frame-ready", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); } static void celluloid_model_init(CelluloidModel *model) { model->extra_options = NULL; model->metadata = NULL; model->track_list = NULL; model->update_mpv_properties = TRUE; model->resetting = FALSE; model->aid = NULL; model->vid = NULL; model->sid = NULL; model->chapters = 0; model->core_idle = FALSE; model->idle_active = FALSE; model->border = FALSE; model->fullscreen = FALSE; model->pause = TRUE; model->loop_file = NULL; model->loop_playlist = NULL; model->shuffle = FALSE; model->duration = 0.0; model->media_title = NULL; model->playlist_count = 0; model->playlist_pos = 0; model->speed = 1.0; model->volume = 1.0; model->volume_max = 100.0; model->window_maximized = FALSE; model->window_scale = 1.0; model->display_fps = 0.0; } CelluloidModel * celluloid_model_new(gint64 wid) { const GType type = celluloid_model_get_type(); CelluloidModel *model = CELLULOID_MODEL(g_object_new( type, "wid", wid, NULL )); g_signal_connect( model, "mpv-property-changed", G_CALLBACK(mpv_prop_change_handler), model ); return model; } void celluloid_model_initialize(CelluloidModel *model) { CelluloidMpv *mpv = CELLULOID_MPV(model); GSettings *win_settings = g_settings_new(CONFIG_WIN_STATE); celluloid_mpv_initialize (mpv); celluloid_mpv_set_render_update_callback (mpv, render_update_callback, model); if(!extra_options_contains(model, "volume")) { gdouble volume = g_settings_get_double(win_settings, "volume")*100; g_debug("Setting volume to %f", volume); g_object_set(model, "volume", volume, NULL); } if(extra_options_contains(model, "shuffle")) { // Sync the property to match mpv's CelluloidMpv *mpv = CELLULOID_MPV(model); gboolean shuffle = celluloid_mpv_get_property_flag(mpv, "shuffle"); g_object_set(model, "shuffle", shuffle, NULL); } if(extra_options_contains(model, "loop-playlist")) { // Sync the property to match mpv's CelluloidMpv *mpv = CELLULOID_MPV(model); gchar *loop_playlist = celluloid_mpv_get_property_string(mpv, "loop-playlist"); g_object_set(model, "loop-playlist", loop_playlist, NULL); mpv_free(loop_playlist); } else { const gchar *loop_playlist = g_settings_get_boolean(win_settings, "loop-playlist") ? "inf" : "no"; g_object_set(model, "loop-playlist", loop_playlist, NULL); } g_object_unref(win_settings); } void celluloid_model_reset(CelluloidModel *model) { model->resetting = TRUE; celluloid_mpv_reset(CELLULOID_MPV(model)); } void celluloid_model_quit(CelluloidModel *model) { celluloid_mpv_quit(CELLULOID_MPV(model)); } void celluloid_model_mouse(CelluloidModel *model, gint x, gint y) { gchar *x_str = g_strdup_printf("%d", x); gchar *y_str = g_strdup_printf("%d", y); const gchar *cmd[] = {"mouse", x_str, y_str, NULL}; g_debug("Set mouse location to (%s, %s)", x_str, y_str); celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); g_free(x_str); g_free(y_str); } void celluloid_model_key_down(CelluloidModel *model, const gchar* keystr) { const gchar *cmd[] = {"keydown", keystr, NULL}; g_debug("Sent '%s' key down to mpv", keystr); celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_key_up(CelluloidModel *model, const gchar* keystr) { const gchar *cmd[] = {"keyup", keystr, NULL}; g_debug("Sent '%s' key up to mpv", keystr); celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_key_press(CelluloidModel *model, const gchar* keystr) { const gchar *cmd[] = {"keypress", keystr, NULL}; g_debug("Sent '%s' key press to mpv", keystr); celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_reset_keys(CelluloidModel *model) { // As of Sep 20, 2019, mpv will crash if the command doesn't have any // arguments. The empty string is there to work around the issue. const gchar *cmd[] = {"keyup", "", NULL}; g_debug("Sent global key up to mpv"); celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_play(CelluloidModel *model) { celluloid_mpv_set_property_flag(CELLULOID_MPV(model), "pause", FALSE); } void celluloid_model_pause(CelluloidModel *model) { celluloid_mpv_set_property_flag(CELLULOID_MPV(model), "pause", TRUE); } void celluloid_model_stop(CelluloidModel *model) { const gchar *cmd[] = {"stop", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_forward(CelluloidModel *model) { const gchar *cmd[] = {"seek", "10", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_rewind(CelluloidModel *model) { const gchar *cmd[] = {"seek", "-10", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_next_chapter(CelluloidModel *model) { const gchar *cmd[] = {"osd-msg", "cycle", "chapter", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_previous_chapter(CelluloidModel *model) { const gchar *cmd[] = {"osd-msg", "cycle", "chapter", "down", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_next_playlist_entry(CelluloidModel *model) { const gchar *cmd[] = {"osd-msg", "playlist-next", "weak", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_previous_playlist_entry(CelluloidModel *model) { const gchar *cmd[] = {"osd-msg", "playlist-prev", "weak", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_shuffle_playlist(CelluloidModel *model) { const gchar *cmd[] = {"osd-msg", "playlist-shuffle", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_unshuffle_playlist(CelluloidModel *model) { const gchar *cmd[] = {"osd-msg", "playlist-unshuffle", NULL}; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_seek(CelluloidModel *model, gdouble value) { celluloid_mpv_set_property(CELLULOID_MPV(model), "time-pos", MPV_FORMAT_DOUBLE, &value); } void celluloid_model_seek_offset(CelluloidModel *model, gdouble offset) { const gchar *cmd[] = {"seek", NULL, NULL}; gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; g_ascii_dtostr(buf, G_ASCII_DTOSTR_BUF_SIZE, offset); cmd[1] = buf; celluloid_mpv_command_async(CELLULOID_MPV(model), cmd); } void celluloid_model_load_audio_track(CelluloidModel *model, const gchar *filename) { celluloid_mpv_load_track (CELLULOID_MPV(model), filename, TRACK_TYPE_AUDIO); } void celluloid_model_load_video_track(CelluloidModel *model, const gchar *filename) { celluloid_mpv_load_track (CELLULOID_MPV(model), filename, TRACK_TYPE_VIDEO); } void celluloid_model_load_subtitle_track( CelluloidModel *model, const gchar *filename ) { celluloid_mpv_load_track (CELLULOID_MPV(model), filename, TRACK_TYPE_SUBTITLE); } gdouble celluloid_model_get_time_position(CelluloidModel *model) { gdouble time_pos = 0.0; if(!model->idle_active) { celluloid_mpv_get_property( CELLULOID_MPV(model), "time-pos", MPV_FORMAT_DOUBLE, &time_pos ); } /* time-pos may become negative during seeks */ return MAX(0, time_pos); } void celluloid_model_set_playlist_position(CelluloidModel *model, gint64 position) { celluloid_player_set_playlist_position (CELLULOID_PLAYER(model), position); } void celluloid_model_remove_playlist_entry(CelluloidModel *model, gint64 position) { celluloid_player_remove_playlist_entry (CELLULOID_PLAYER(model), position); } void celluloid_model_move_playlist_entry( CelluloidModel *model, gint64 src, gint64 dst ) { celluloid_player_move_playlist_entry(CELLULOID_PLAYER(model), src, dst); } void celluloid_model_load_file( CelluloidModel *model, const gchar *uri, gboolean append ) { GSettings *settings = g_settings_new(CONFIG_ROOT); append |= g_settings_get_boolean(settings, "always-append-to-playlist"); celluloid_mpv_load(CELLULOID_MPV(model), uri, append); /* Start playing when replacing the playlist, ie. not appending, or * adding the first file to the playlist. */ if(!append || model->playlist_count == 0) { g_signal_emit_by_name(model, "playlist-replaced"); celluloid_model_play(model); } g_object_unref(settings); } gboolean celluloid_model_get_use_opengl_cb(CelluloidModel *model) { return celluloid_mpv_get_use_opengl_cb(CELLULOID_MPV(model)); } void celluloid_model_initialize_gl(CelluloidModel *model) { celluloid_mpv_init_gl(CELLULOID_MPV(model)); } void celluloid_model_render_frame(CelluloidModel *model, gint width, gint height) { mpv_render_context *render_ctx; render_ctx = celluloid_mpv_get_render_context(CELLULOID_MPV(model)); if(render_ctx) { gint fbo = -1; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo); mpv_opengl_fbo opengl_fbo = {fbo, width, height, 0}; mpv_render_param params[] = { {MPV_RENDER_PARAM_OPENGL_FBO, &opengl_fbo}, {MPV_RENDER_PARAM_FLIP_Y, &(int){1}}, {0, NULL} }; mpv_render_context_render(render_ctx, params); } } void celluloid_model_get_video_geometry( CelluloidModel *model, gint64 *width, gint64 *height ) { CelluloidMpv *mpv = CELLULOID_MPV(model); celluloid_mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, width); celluloid_mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, height); } gchar * celluloid_model_get_current_path(CelluloidModel *model) { CelluloidMpv *mpv = CELLULOID_MPV(model); gchar *path = celluloid_mpv_get_property_string(mpv, "path"); gchar *buf = g_strdup(path); mpv_free(path); return buf; }