mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 16:31:58 +02:00
Compare commits
35 Commits
crowdin_sy
...
reactable
Author | SHA1 | Date | |
---|---|---|---|
|
c354d31436 | ||
|
c0b360dc0f | ||
|
a6ca0b726c | ||
|
976bcc338f | ||
|
6273446817 | ||
|
72ab32d4ef | ||
|
e9d0d8f345 | ||
|
0b8d359844 | ||
|
4a93bea203 | ||
|
5e2c1a8e30 | ||
|
72c8e4ab84 | ||
|
db61b9c773 | ||
|
682ad6c3c8 | ||
|
749796a12f | ||
|
c557c11e86 | ||
|
a2f67a9bc0 | ||
|
ddc0a4d8f9 | ||
|
92e3e686db | ||
|
9fd87dbbb9 | ||
|
ca15f4760a | ||
|
6ddb53252a | ||
|
b30d53d8ce | ||
|
1527873bcc | ||
|
e23f2acb3e | ||
|
1dfcb218ac | ||
|
b05f0f2b30 | ||
|
dad0d46196 | ||
|
e34f729f62 | ||
|
a97e7d1a96 | ||
|
c6c4fe309b | ||
|
3ef6e9694a | ||
|
d951be7a56 | ||
|
98fdd7c58b | ||
|
147d94088c | ||
|
c7790d9f7b |
@@ -11,7 +11,7 @@ project('clapper', 'c',
|
||||
glib_req = '>= 2.76.0'
|
||||
gst_req = '>= 1.24.0'
|
||||
gtk4_req = '>= 4.10.0'
|
||||
adw_req = '>= 1.4.0'
|
||||
adw_req = '>= 1.5.0'
|
||||
|
||||
clapper_version = meson.project_version().split('-')[0]
|
||||
version_array = clapper_version.split('.')
|
||||
|
Submodule pkgs/flatpak/flathub updated: f58477c356...b7c25acba8
@@ -72,6 +72,43 @@ _open_subtitles_cb (GtkFileDialog *dialog, GAsyncResult *result, ClapperMediaIte
|
||||
gst_object_unref (item); // Borrowed reference
|
||||
}
|
||||
|
||||
static void
|
||||
_on_select_file_dir_finish (GFile *file, AdwActionRow *action_row, GError *error)
|
||||
{
|
||||
if (G_LIKELY (error == NULL)) {
|
||||
gchar *path = g_file_get_path (file);
|
||||
|
||||
adw_action_row_set_subtitle (action_row, path);
|
||||
g_free (path);
|
||||
} else {
|
||||
if (error->domain != GTK_DIALOG_ERROR || error->code != GTK_DIALOG_ERROR_DISMISSED) {
|
||||
g_printerr ("Error: %s\n",
|
||||
(error->message) ? error->message : "Could not open file dialog");
|
||||
}
|
||||
g_error_free (error);
|
||||
}
|
||||
g_clear_object (&file);
|
||||
g_object_unref (action_row); // Borrowed reference
|
||||
}
|
||||
|
||||
static void
|
||||
_select_file_cb (GtkFileDialog *dialog, GAsyncResult *result, AdwActionRow *action_row)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GFile *file = gtk_file_dialog_open_finish (dialog, result, &error);
|
||||
|
||||
_on_select_file_dir_finish (file, action_row, error);
|
||||
}
|
||||
|
||||
static void
|
||||
_select_dir_cb (GtkFileDialog *dialog, GAsyncResult *result, AdwActionRow *action_row)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GFile *file = gtk_file_dialog_select_folder_finish (dialog, result, &error);
|
||||
|
||||
_on_select_file_dir_finish (file, action_row, error);
|
||||
}
|
||||
|
||||
static void
|
||||
_dialog_add_mime_types (GtkFileDialog *dialog, const gchar *filter_name,
|
||||
const gchar *const *mime_types)
|
||||
@@ -144,3 +181,35 @@ clapper_app_file_dialog_open_subtitles (GtkApplication *gtk_app, ClapperMediaIte
|
||||
|
||||
g_object_unref (dialog);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_app_file_dialog_select_prefs_file (GtkApplication *gtk_app, AdwActionRow *action_row)
|
||||
{
|
||||
GtkWindow *window = gtk_application_get_active_window (gtk_app);
|
||||
GtkFileDialog *dialog = gtk_file_dialog_new ();
|
||||
|
||||
gtk_file_dialog_set_modal (dialog, TRUE);
|
||||
gtk_file_dialog_set_title (dialog, "Select File");
|
||||
|
||||
gtk_file_dialog_open (dialog, window, NULL,
|
||||
(GAsyncReadyCallback) _select_file_cb,
|
||||
g_object_ref (action_row));
|
||||
|
||||
g_object_unref (dialog);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_app_file_dialog_select_prefs_dir (GtkApplication *gtk_app, AdwActionRow *action_row)
|
||||
{
|
||||
GtkWindow *window = gtk_application_get_active_window (gtk_app);
|
||||
GtkFileDialog *dialog = gtk_file_dialog_new ();
|
||||
|
||||
gtk_file_dialog_set_modal (dialog, TRUE);
|
||||
gtk_file_dialog_set_title (dialog, "Select Folder");
|
||||
|
||||
gtk_file_dialog_select_folder (dialog, window, NULL,
|
||||
(GAsyncReadyCallback) _select_dir_cb,
|
||||
g_object_ref (action_row));
|
||||
|
||||
g_object_unref (dialog);
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <glib.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <adwaita.h>
|
||||
#include <clapper/clapper.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
@@ -29,4 +30,10 @@ void clapper_app_file_dialog_open_files (GtkApplication *gtk_app);
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_app_file_dialog_open_subtitles (GtkApplication *gtk_app, ClapperMediaItem *item);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_app_file_dialog_select_prefs_file (GtkApplication *gtk_app, AdwActionRow *action_row);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_app_file_dialog_select_prefs_dir (GtkApplication *gtk_app, AdwActionRow *action_row);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "clapper-app-preferences-window.h"
|
||||
#include "clapper-app-application.h"
|
||||
#include "clapper-app-file-dialog.h"
|
||||
#include "clapper-app-utils.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_app_preferences_window_debug
|
||||
@@ -41,6 +42,14 @@ struct _ClapperAppPreferencesWindow
|
||||
AdwSpinRow *subtitle_offset_spin_row;
|
||||
GtkFontDialogButton *font_dialog_button;
|
||||
|
||||
GtkStack *enhancers_stack;
|
||||
GtkWidget *browse_enhancers_page;
|
||||
GtkWidget *no_enhancers_page;
|
||||
|
||||
AdwNavigationPage *enhancers_subpage;
|
||||
AdwComboRow *enhancers_combo_row;
|
||||
AdwPreferencesGroup *enhancer_config_group;
|
||||
|
||||
AdwNavigationPage *plugins_subpage;
|
||||
AdwComboRow *plugins_combo_row;
|
||||
AdwComboRow *features_combo_row;
|
||||
@@ -48,6 +57,8 @@ struct _ClapperAppPreferencesWindow
|
||||
|
||||
GSettings *settings;
|
||||
|
||||
GList *enhancer_pspec_rows;
|
||||
|
||||
GList *features;
|
||||
GtkStringList *plugins_list;
|
||||
|
||||
@@ -67,6 +78,19 @@ typedef struct
|
||||
gboolean updated;
|
||||
} ClapperAppPreferencesIterRanksData;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GSettings *settings;
|
||||
const gchar *key;
|
||||
} ClapperAppPreferencesResetData;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GSettings *settings;
|
||||
GParamSpec *pspec;
|
||||
guint flag;
|
||||
} ClapperAppPreferencesFlagMapData;
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
@@ -76,6 +100,347 @@ enum
|
||||
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static void
|
||||
_flag_map_data_free (ClapperAppPreferencesFlagMapData *data)
|
||||
{
|
||||
GST_TRACE ("Destroying flag map data: %p", data);
|
||||
g_object_unref (data->settings);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
_reset_button_closure (ClapperAppPreferencesResetData *data, GClosure *closure)
|
||||
{
|
||||
GST_TRACE ("Destroying reset button data: %p", data);
|
||||
g_object_unref (data->settings);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
_reset_button_clicked_cb (GtkButton *button, ClapperAppPreferencesResetData *data)
|
||||
{
|
||||
g_settings_reset (data->settings, data->key);
|
||||
}
|
||||
|
||||
static void
|
||||
file_selection_row_activated_cb (AdwActionRow *action_row, GParamSpec *pspec)
|
||||
{
|
||||
GtkApplication *gtk_app;
|
||||
GtkWidget *window;
|
||||
|
||||
if (!(window = gtk_widget_get_ancestor (GTK_WIDGET (action_row), GTK_TYPE_WINDOW))) {
|
||||
GST_ERROR ("Could not get a hold of parent window");
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_app = gtk_window_get_application (GTK_WINDOW (window));
|
||||
|
||||
if (pspec->flags & CLAPPER_ENHANCER_PARAM_FILEPATH)
|
||||
clapper_app_file_dialog_select_prefs_file (gtk_app, action_row);
|
||||
else
|
||||
clapper_app_file_dialog_select_prefs_dir (gtk_app, action_row);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_get_enum_mapping (GValue *value, GVariant *variant, GParamSpec *pspec)
|
||||
{
|
||||
GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
|
||||
const gchar *selected_str = g_variant_get_string (variant, NULL);
|
||||
guint i, selected = 0;
|
||||
|
||||
for (i = 0; i < enum_class->n_values; ++i) {
|
||||
if (g_strcmp0 (selected_str, enum_class->values[i].value_nick) == 0) {
|
||||
selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_value_set_uint (value, selected);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
_set_enum_mapping (GValue *value, GVariantType *exp_type, GParamSpec *pspec)
|
||||
{
|
||||
GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
|
||||
guint selected = g_value_get_uint (value);
|
||||
|
||||
if (G_UNLIKELY (selected == GTK_INVALID_LIST_POSITION))
|
||||
selected = 0;
|
||||
|
||||
return g_variant_new_string (enum_class->values[selected].value_nick);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_get_flag_mapping (GValue *value, GVariant *variant, ClapperAppPreferencesFlagMapData *data)
|
||||
{
|
||||
guint flags = g_settings_get_flags (data->settings, data->pspec->name);
|
||||
|
||||
g_value_set_boolean (value, (flags & data->flag));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
_set_flag_mapping (GValue *value, GVariantType *exp_type, ClapperAppPreferencesFlagMapData *data)
|
||||
{
|
||||
GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (data->pspec->value_type));
|
||||
GStrvBuilder *builder;
|
||||
GVariant *variant;
|
||||
gchar **strv;
|
||||
gboolean active = g_value_get_boolean (value);
|
||||
guint i, flags = g_settings_get_flags (data->settings, data->pspec->name);
|
||||
|
||||
if (active)
|
||||
flags |= data->flag;
|
||||
else
|
||||
flags &= ~(data->flag);
|
||||
|
||||
builder = g_strv_builder_new ();
|
||||
|
||||
for (i = 0; i < flags_class->n_values; ++i) {
|
||||
if (flags & flags_class->values[i].value)
|
||||
g_strv_builder_add (builder, flags_class->values[i].value_nick);
|
||||
}
|
||||
|
||||
strv = g_strv_builder_end (builder);
|
||||
g_strv_builder_unref (builder);
|
||||
|
||||
variant = g_variant_new_strv ((const gchar *const *) strv, -1);
|
||||
g_strfreev (strv);
|
||||
|
||||
return variant;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_add_enhancer_config_row (ClapperAppPreferencesWindow *self, GParamSpec *pspec,
|
||||
GSettings *enhancer_settings)
|
||||
{
|
||||
GtkWidget *row = NULL, *reset_button;
|
||||
ClapperAppPreferencesResetData *reset_data;
|
||||
const gchar *bind_prop = NULL;
|
||||
gboolean is_enum = FALSE, is_flags = FALSE;
|
||||
|
||||
switch (pspec->value_type) {
|
||||
case G_TYPE_BOOLEAN:{
|
||||
row = adw_switch_row_new ();
|
||||
break;
|
||||
}
|
||||
case G_TYPE_INT:{
|
||||
GParamSpecInt *p = (GParamSpecInt *) pspec;
|
||||
row = adw_spin_row_new_with_range (p->minimum, p->maximum, 1);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_UINT:{
|
||||
GParamSpecUInt *p = (GParamSpecUInt *) pspec;
|
||||
row = adw_spin_row_new_with_range (p->minimum, p->maximum, 1);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_DOUBLE:{
|
||||
GParamSpecDouble *p = (GParamSpecDouble *) pspec;
|
||||
row = adw_spin_row_new_with_range (p->minimum, p->maximum, 0.25);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_STRING:{
|
||||
if (pspec->flags & (CLAPPER_ENHANCER_PARAM_FILEPATH | CLAPPER_ENHANCER_PARAM_DIRPATH)) {
|
||||
GtkWidget *image;
|
||||
|
||||
image = gtk_image_new_from_icon_name ("document-open-symbolic");
|
||||
gtk_widget_set_margin_end (image, 10); // matches other rows
|
||||
|
||||
row = adw_action_row_new ();
|
||||
adw_action_row_add_suffix (ADW_ACTION_ROW (row), image);
|
||||
adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), image);
|
||||
|
||||
g_signal_connect (row, "activated",
|
||||
G_CALLBACK (file_selection_row_activated_cb), pspec);
|
||||
} else {
|
||||
row = adw_entry_row_new ();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:{
|
||||
if ((is_enum = G_IS_PARAM_SPEC_ENUM (pspec))) {
|
||||
GtkExpression *expression;
|
||||
AdwEnumListModel *enum_model;
|
||||
|
||||
row = adw_combo_row_new ();
|
||||
expression = gtk_property_expression_new (ADW_TYPE_ENUM_LIST_ITEM, NULL, "nick");
|
||||
adw_combo_row_set_expression (ADW_COMBO_ROW (row), expression);
|
||||
|
||||
enum_model = adw_enum_list_model_new (pspec->value_type);
|
||||
adw_combo_row_set_model (ADW_COMBO_ROW (row), G_LIST_MODEL (enum_model));
|
||||
|
||||
gtk_expression_unref (expression);
|
||||
g_object_unref (enum_model);
|
||||
break;
|
||||
} else if ((is_flags = G_IS_PARAM_SPEC_FLAGS (pspec))) {
|
||||
GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (pspec->value_type));
|
||||
guint i;
|
||||
|
||||
row = adw_expander_row_new ();
|
||||
|
||||
for (i = 0; i < flags_class->n_values; ++i) {
|
||||
GtkWidget *flag_row = adw_switch_row_new ();
|
||||
ClapperAppPreferencesFlagMapData *fm_data;
|
||||
|
||||
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (flag_row),
|
||||
flags_class->values[i].value_nick);
|
||||
|
||||
fm_data = g_new (ClapperAppPreferencesFlagMapData, 1);
|
||||
fm_data->settings = g_object_ref (enhancer_settings);
|
||||
fm_data->pspec = pspec;
|
||||
fm_data->flag = flags_class->values[i].value;
|
||||
|
||||
GST_TRACE ("Created flag map data: %p", fm_data);
|
||||
|
||||
g_settings_bind_with_mapping (enhancer_settings, pspec->name, flag_row,
|
||||
"active", G_SETTINGS_BIND_DEFAULT,
|
||||
(GSettingsBindGetMapping) _get_flag_mapping,
|
||||
(GSettingsBindSetMapping) _set_flag_mapping,
|
||||
fm_data, (GDestroyNotify) _flag_map_data_free);
|
||||
|
||||
adw_expander_row_add_row (ADW_EXPANDER_ROW (row), flag_row);
|
||||
}
|
||||
break;
|
||||
}
|
||||
g_warning ("Unsupported enhancer \"%s\" property type: %s",
|
||||
pspec->name, g_type_name (pspec->value_type));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
reset_button = gtk_button_new_from_icon_name ("view-refresh-symbolic");
|
||||
gtk_widget_set_tooltip_text (reset_button, _("Restore default"));
|
||||
|
||||
gtk_widget_set_halign (reset_button, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_valign (reset_button, GTK_ALIGN_CENTER);
|
||||
gtk_widget_add_css_class (reset_button, "circular");
|
||||
|
||||
gtk_widget_set_tooltip_text (row, g_param_spec_get_blurb (pspec));
|
||||
|
||||
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), g_param_spec_get_nick (pspec));
|
||||
|
||||
if (ADW_IS_SWITCH_ROW (row)) {
|
||||
bind_prop = "active";
|
||||
} else if (ADW_IS_SPIN_ROW (row)) {
|
||||
bind_prop = "value";
|
||||
adw_spin_row_set_numeric (ADW_SPIN_ROW (row), TRUE);
|
||||
} else if (ADW_IS_ENTRY_ROW (row)) {
|
||||
bind_prop = "text";
|
||||
} else if (ADW_IS_COMBO_ROW (row)) {
|
||||
bind_prop = "selected";
|
||||
} else if (ADW_IS_ACTION_ROW (row)) {
|
||||
bind_prop = "subtitle";
|
||||
} else if (!is_flags) { // In case of flags we bind individual widgets
|
||||
g_assert_not_reached ();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (ADW_IS_ENTRY_ROW (row))
|
||||
adw_entry_row_add_prefix (ADW_ENTRY_ROW (row), reset_button);
|
||||
else if (ADW_IS_ACTION_ROW (row))
|
||||
adw_action_row_add_prefix (ADW_ACTION_ROW (row), reset_button);
|
||||
else if (ADW_IS_EXPANDER_ROW (row))
|
||||
adw_expander_row_add_prefix (ADW_EXPANDER_ROW (row), reset_button);
|
||||
|
||||
if (is_enum) {
|
||||
g_settings_bind_with_mapping (enhancer_settings, pspec->name, row,
|
||||
bind_prop, G_SETTINGS_BIND_DEFAULT,
|
||||
(GSettingsBindGetMapping) _get_enum_mapping,
|
||||
(GSettingsBindSetMapping) _set_enum_mapping,
|
||||
pspec, NULL);
|
||||
} else if (!is_flags) {
|
||||
g_settings_bind (enhancer_settings, pspec->name, row,
|
||||
bind_prop, G_SETTINGS_BIND_DEFAULT);
|
||||
}
|
||||
|
||||
reset_data = g_new (ClapperAppPreferencesResetData, 1);
|
||||
reset_data->settings = g_object_ref (enhancer_settings);
|
||||
reset_data->key = pspec->name;
|
||||
|
||||
GST_TRACE ("Created reset button data: %p", reset_data);
|
||||
|
||||
g_signal_connect_data (reset_button, "clicked",
|
||||
G_CALLBACK (_reset_button_clicked_cb), reset_data,
|
||||
(GClosureNotify) _reset_button_closure, G_CONNECT_DEFAULT);
|
||||
|
||||
adw_preferences_group_add (self->enhancer_config_group, row);
|
||||
self->enhancer_pspec_rows = g_list_append (self->enhancer_pspec_rows, row);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
selected_enhancer_changed_cb (AdwComboRow *combo_row,
|
||||
GParamSpec *pspec G_GNUC_UNUSED, ClapperAppPreferencesWindow *self)
|
||||
{
|
||||
guint selected = adw_combo_row_get_selected (combo_row);
|
||||
|
||||
/* Remove old rows */
|
||||
if (self->enhancer_pspec_rows) {
|
||||
GList *el;
|
||||
|
||||
for (el = self->enhancer_pspec_rows; el; el = g_list_next (el))
|
||||
adw_preferences_group_remove (self->enhancer_config_group, GTK_WIDGET (el->data));
|
||||
|
||||
g_clear_list (&self->enhancer_pspec_rows, NULL);
|
||||
}
|
||||
|
||||
/* Add new rows */
|
||||
if (selected != GTK_INVALID_LIST_POSITION) {
|
||||
ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies ();
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, selected);
|
||||
GParamSpec **pspecs;
|
||||
guint n_pspecs;
|
||||
gboolean has_props = FALSE;
|
||||
|
||||
if ((pspecs = clapper_enhancer_proxy_get_target_properties (proxy, &n_pspecs))) {
|
||||
GSettings *enhancer_settings = NULL;
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < n_pspecs; ++i) {
|
||||
if (pspecs[i]->flags & CLAPPER_ENHANCER_PARAM_GLOBAL) {
|
||||
if (!enhancer_settings)
|
||||
enhancer_settings = clapper_enhancer_proxy_get_settings (proxy);
|
||||
if (enhancer_settings)
|
||||
has_props |= _add_enhancer_config_row (self, pspecs[i], enhancer_settings);
|
||||
}
|
||||
}
|
||||
|
||||
g_clear_object (&enhancer_settings);
|
||||
}
|
||||
|
||||
if (!has_props) {
|
||||
GtkWidget *row = adw_action_row_new ();
|
||||
|
||||
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _("No configurable properties"));
|
||||
adw_preferences_group_add (self->enhancer_config_group, row);
|
||||
self->enhancer_pspec_rows = g_list_append (self->enhancer_pspec_rows, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
enhancers_config_activated_cb (AdwActionRow *action_row, ClapperAppPreferencesWindow *self)
|
||||
{
|
||||
/* If no model set yet */
|
||||
if (!adw_combo_row_get_model (self->enhancers_combo_row)) {
|
||||
ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies ();
|
||||
|
||||
adw_combo_row_set_model (self->enhancers_combo_row, G_LIST_MODEL (proxies));
|
||||
adw_combo_row_set_selected (self->enhancers_combo_row, GTK_INVALID_LIST_POSITION);
|
||||
|
||||
GST_DEBUG ("Populated names combo row in enhancers subpage");
|
||||
|
||||
if (clapper_enhancer_proxy_list_get_n_proxies (proxies) > 0)
|
||||
gtk_stack_set_visible_child (self->enhancers_stack, self->browse_enhancers_page);
|
||||
else
|
||||
gtk_stack_set_visible_child (self->enhancers_stack, self->no_enhancers_page);
|
||||
}
|
||||
|
||||
adw_preferences_window_push_subpage (ADW_PREFERENCES_WINDOW (self), self->enhancers_subpage);
|
||||
}
|
||||
|
||||
/* Sort by plugin name and if the same, sort by element name */
|
||||
static gint
|
||||
_compare_plugins_cb (gconstpointer ptr_a, gconstpointer ptr_b)
|
||||
@@ -301,6 +666,12 @@ _make_plugin_features_string_list (ClapperAppPreferencesWindow *self, const gcha
|
||||
return features_list;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
list_has_selection_closure (ClapperAppPreferencesWindow *self, guint selected)
|
||||
{
|
||||
return (selected != GTK_INVALID_LIST_POSITION);
|
||||
}
|
||||
|
||||
static GtkStringList *
|
||||
ranking_features_model_closure (ClapperAppPreferencesWindow *self, GtkStringObject *string_obj)
|
||||
{
|
||||
@@ -543,6 +914,7 @@ clapper_app_preferences_window_finalize (GObject *object)
|
||||
|
||||
g_object_unref (self->settings);
|
||||
|
||||
g_clear_list (&self->enhancer_pspec_rows, NULL);
|
||||
g_clear_object (&self->plugins_list);
|
||||
|
||||
if (self->features)
|
||||
@@ -598,6 +970,14 @@ clapper_app_preferences_window_class_init (ClapperAppPreferencesWindowClass *kla
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, subtitle_offset_spin_row);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, font_dialog_button);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_stack);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, browse_enhancers_page);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, no_enhancers_page);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_subpage);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_combo_row);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancer_config_group);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, plugins_subpage);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, plugins_combo_row);
|
||||
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, features_combo_row);
|
||||
@@ -605,9 +985,13 @@ clapper_app_preferences_window_class_init (ClapperAppPreferencesWindowClass *kla
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, seek_method_name_closure);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, enhancers_config_activated_cb);
|
||||
gtk_widget_class_bind_template_callback (widget_class, selected_enhancer_changed_cb);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, plugin_ranking_activated_cb);
|
||||
gtk_widget_class_bind_template_callback (widget_class, plugin_ranking_unrealize_cb);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, list_has_selection_closure);
|
||||
gtk_widget_class_bind_template_callback (widget_class, ranking_features_model_closure);
|
||||
gtk_widget_class_bind_template_callback (widget_class, add_override_button_sensitive_closure);
|
||||
gtk_widget_class_bind_template_callback (widget_class, add_override_button_clicked_cb);
|
||||
|
@@ -86,9 +86,7 @@ typedef struct
|
||||
gint64 last_tick;
|
||||
} ClapperAppWindowResizeData;
|
||||
|
||||
#if CLAPPER_HAVE_MPRIS
|
||||
static guint16 instance_count = 0;
|
||||
#endif
|
||||
|
||||
static inline GQuark
|
||||
clapper_app_window_extra_options_get_quark (void)
|
||||
@@ -1252,16 +1250,28 @@ clapper_app_window_constructed (GObject *object)
|
||||
#if (CLAPPER_HAVE_MPRIS || CLAPPER_HAVE_SERVER || CLAPPER_HAVE_DISCOVERER)
|
||||
ClapperFeature *feature = NULL;
|
||||
#endif
|
||||
#if CLAPPER_HAVE_MPRIS
|
||||
#if (!CLAPPER_HAVE_MPRIS || !CLAPPER_HAVE_SERVER || !CLAPPER_HAVE_DISCOVERER)
|
||||
ClapperEnhancerProxyList *proxies = clapper_player_get_enhancer_proxies (player);
|
||||
ClapperEnhancerProxy *proxy;
|
||||
#endif
|
||||
|
||||
gchar mpris_name[45];
|
||||
g_snprintf (mpris_name, sizeof (mpris_name),
|
||||
"org.mpris.MediaPlayer2.Clapper.instance%" G_GUINT16_FORMAT, instance_count++);
|
||||
#endif
|
||||
|
||||
self->settings = g_settings_new (CLAPPER_APP_ID);
|
||||
self->last_volume = PERCENTAGE_ROUND (g_settings_get_double (self->settings, "volume"));
|
||||
|
||||
#if CLAPPER_HAVE_MPRIS
|
||||
#if !CLAPPER_HAVE_MPRIS
|
||||
if ((proxy = clapper_enhancer_proxy_list_get_proxy_by_module (proxies, "clapper-mpris"))) {
|
||||
clapper_enhancer_proxy_set_locally (proxy,
|
||||
"own-name", mpris_name,
|
||||
"identity", CLAPPER_APP_NAME,
|
||||
"desktop-entry", CLAPPER_APP_ID,
|
||||
"queue-controllable", TRUE, NULL);
|
||||
gst_object_unref (proxy);
|
||||
}
|
||||
#else
|
||||
feature = CLAPPER_FEATURE (clapper_mpris_new (
|
||||
mpris_name, CLAPPER_APP_NAME, CLAPPER_APP_ID));
|
||||
clapper_mpris_set_queue_controllable (CLAPPER_MPRIS (feature), TRUE);
|
||||
|
@@ -21,10 +21,10 @@ window.info .subcontent streamlist preferencesgroup {
|
||||
window.preferences .subcontent {
|
||||
margin: 16px;
|
||||
}
|
||||
window.preferences .pluginssubpage .subcontent popover {
|
||||
window.preferences .configsubpage .subcontent popover {
|
||||
min-width: 264px;
|
||||
}
|
||||
window.preferences .pluginssubpage .subcontent button.pill {
|
||||
window.preferences .configsubpage .subcontent button.pill {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
@@ -1,10 +1,11 @@
|
||||
appstream_util = find_program('appstream-util', required: false)
|
||||
if appstream_util.found()
|
||||
appstream_cli = find_program('appstreamcli', required: false)
|
||||
if appstream_cli.found()
|
||||
test('Validate appstream file',
|
||||
appstream_util,
|
||||
appstream_cli,
|
||||
args: [
|
||||
'validate-relax',
|
||||
'--nonet',
|
||||
'validate',
|
||||
'--no-net',
|
||||
'--explain',
|
||||
join_paths(meson.current_source_dir(), 'metainfo', 'com.github.rafostar.Clapper.metainfo.xml'),
|
||||
]
|
||||
)
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
|
||||
"PO-Revision-Date: 2025-06-14 17:10\n"
|
||||
"PO-Revision-Date: 2025-02-15 12:10\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Arabic\n"
|
||||
"Language: ar_SA\n"
|
||||
@@ -20,30 +20,30 @@ msgstr ""
|
||||
#. Translators: Do NOT translate app name!
|
||||
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4
|
||||
msgid "Clapper"
|
||||
msgstr "كلابر"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5
|
||||
msgid "Multimedia Player"
|
||||
msgstr "مشغل وسائط متعددة"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6
|
||||
msgid "Play videos and music"
|
||||
msgstr "شغِّل الفيديوهات والموسيقى"
|
||||
msgstr ""
|
||||
|
||||
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
|
||||
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
|
||||
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
|
||||
msgstr "الفيديوهات;الفيلم;الأفلام;المقطع;المقاطع;المسلسلات;المشغلات;قائمة تشغيل;DVD;دي في دي;التلفاز;القرص;Clapper;"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
|
||||
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
|
||||
msgid "New Window"
|
||||
msgstr "نافذة جديدة"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:14
|
||||
#: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:14
|
||||
msgid "Codec"
|
||||
msgstr "المرماز"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24
|
||||
msgid "Channels"
|
||||
@@ -65,23 +65,23 @@ msgstr ""
|
||||
#: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70
|
||||
#: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24
|
||||
msgid "Language"
|
||||
msgstr "اللغة"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189
|
||||
#: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80
|
||||
msgid "Preferences"
|
||||
msgstr "التفضيلات"
|
||||
msgstr "الإعدادات"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193
|
||||
#: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84
|
||||
msgid "Keyboard Shortcuts"
|
||||
msgstr "اختصارات لوحة المفاتيح"
|
||||
msgstr ""
|
||||
|
||||
#. TRANSLATORS: Please do not translate application name
|
||||
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200
|
||||
#: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91
|
||||
msgid "About Clapper"
|
||||
msgstr "عَنْ «كلابر»"
|
||||
msgstr "حول Clapper"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11
|
||||
@@ -90,19 +90,19 @@ msgstr "عام"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13
|
||||
msgid "New window"
|
||||
msgstr "نافذة جديدة"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19
|
||||
msgid "Open preferences"
|
||||
msgstr "افتح التفضيلات"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25
|
||||
msgid "Show shortcuts"
|
||||
msgstr "أظهِر الاختصارات"
|
||||
msgstr "إظهار الاختصارات"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31
|
||||
msgid "Toggle fullscreen"
|
||||
msgstr "بدِّل ملء الشاشة"
|
||||
msgstr "تبديل ملء الشاشة"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32
|
||||
msgid "Double tap | Double click"
|
||||
@@ -122,7 +122,7 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51
|
||||
msgid "Quit"
|
||||
msgstr "اخرج"
|
||||
msgstr "خروج"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59
|
||||
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:32
|
||||
@@ -133,13 +133,13 @@ msgstr "الوسائط"
|
||||
#: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55
|
||||
#: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178
|
||||
msgid "Add Files…"
|
||||
msgstr "أضِف ملفات…"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68
|
||||
#: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65
|
||||
#: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182
|
||||
msgid "Add URI…"
|
||||
msgstr "أضِف URI…"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76
|
||||
msgid "Queue"
|
||||
@@ -148,12 +148,12 @@ msgstr ""
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87
|
||||
msgid "Next item"
|
||||
msgstr "العنصر التالي"
|
||||
msgstr "المحتوى التالي"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101
|
||||
msgid "Previous item"
|
||||
msgstr "العنصر السابق"
|
||||
msgstr "المحتوى السابق"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107
|
||||
msgid "Change progression mode"
|
||||
@@ -162,11 +162,11 @@ msgstr ""
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76
|
||||
msgid "Playback"
|
||||
msgstr "التشغيل"
|
||||
msgstr "المشغل"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118
|
||||
msgid "Toggle play"
|
||||
msgstr "بدِّل التشغيل"
|
||||
msgstr "بَدْءّ / إيقاف"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119
|
||||
msgid "Tap | Left click"
|
||||
@@ -175,7 +175,7 @@ msgstr ""
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134
|
||||
msgid "Seek forward"
|
||||
msgstr "تقدَم للأمام"
|
||||
msgstr "التقدم للأمام"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151
|
||||
@@ -190,11 +190,11 @@ msgstr ""
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150
|
||||
msgid "Seek backward"
|
||||
msgstr "عُد للوراء"
|
||||
msgstr "الرجوع للوراء"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157
|
||||
msgid "Volume up"
|
||||
msgstr "ارفع شدة الصوت"
|
||||
msgstr "رفع مستوى الصوت"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158
|
||||
msgid "Scroll up"
|
||||
@@ -202,7 +202,7 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164
|
||||
msgid "Volume down"
|
||||
msgstr "اخفض شدة الصوت"
|
||||
msgstr "خفض مستوى الصوت"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165
|
||||
msgid "Scroll down"
|
||||
@@ -210,7 +210,7 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171
|
||||
msgid "Toggle mute"
|
||||
msgstr "بدِّل الكتم"
|
||||
msgstr "كتم الصوت"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177
|
||||
msgid "Speed up"
|
||||
@@ -232,7 +232,7 @@ msgstr "الفصل السابق"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:13
|
||||
msgid "Info"
|
||||
msgstr "معلومات"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:35
|
||||
#: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14
|
||||
@@ -254,7 +254,7 @@ msgstr ""
|
||||
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:84
|
||||
#: src/bin/clapper-app/clapper-app-list-item-utils.c:36
|
||||
msgid "Video"
|
||||
msgstr "الفيديو"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:107
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80
|
||||
@@ -266,7 +266,7 @@ msgstr "الصوت"
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100
|
||||
#: src/bin/clapper-app/clapper-app-list-item-utils.c:42
|
||||
msgid "Subtitles"
|
||||
msgstr "الترجَمة"
|
||||
msgstr "التَّرْجَمَةً"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:155
|
||||
msgid "Video Playback"
|
||||
@@ -309,11 +309,11 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15
|
||||
msgid "Seeking"
|
||||
msgstr "السعي"
|
||||
msgstr "الوضع"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18
|
||||
msgid "Method"
|
||||
msgstr "الطريقة"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19
|
||||
msgid "A preferred method used for seeking"
|
||||
@@ -349,15 +349,15 @@ msgstr "النسبة المئوية"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63
|
||||
msgid "Features"
|
||||
msgstr "الميزات"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66
|
||||
msgid "Server"
|
||||
msgstr "الخادوم"
|
||||
msgstr "الخادم"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67
|
||||
msgid "Control player remotely"
|
||||
msgstr "يتحكم بالوسائط عن بعد"
|
||||
msgstr "التحكم بالوسائط عن بعد"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103
|
||||
@@ -374,7 +374,7 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118
|
||||
msgid "Default font"
|
||||
msgstr "الخط المبدئي"
|
||||
msgstr "الخط الافتراضي"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119
|
||||
msgid "Text font used for subtitles when media does not explicitly specify one"
|
||||
@@ -382,15 +382,15 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137
|
||||
msgid "Tweaks"
|
||||
msgstr "التطويعات"
|
||||
msgstr "تعديلات"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144
|
||||
msgid "Plugin ranking"
|
||||
msgstr "منازل الملحقات"
|
||||
msgstr "أعدادات الإضافات"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145
|
||||
msgid "Alter default ranks of GStreamer plugins"
|
||||
msgstr "يغيِّر المنازل المبدئية لملحقات GStreamer"
|
||||
msgstr "تغيير الأعدادات الافتراضية للأضافات GStreamer"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164
|
||||
msgid "Plugin Ranking"
|
||||
@@ -443,11 +443,11 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18
|
||||
msgid "Enter or drop URI here"
|
||||
msgstr "أدخِل أو أسقِط URI هنا"
|
||||
msgstr "أدخل أو الصق URI هنا"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22
|
||||
msgid "Cancel"
|
||||
msgstr "ألغِ"
|
||||
msgstr "إلغاء"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24
|
||||
msgid "Resolution"
|
||||
@@ -464,8 +464,7 @@ msgstr ""
|
||||
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
|
||||
#: src/bin/clapper-app/clapper-app-about-window.c:47
|
||||
msgid "translator-credits"
|
||||
msgstr "Yousef Fawaz\n"
|
||||
"أحمد النجماوي <iramosu@protonmail.com>"
|
||||
msgstr "Yousef Fawaz"
|
||||
|
||||
#: src/bin/clapper-app/clapper-app-application.c:696
|
||||
msgid "Create a new window"
|
||||
@@ -513,7 +512,7 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/clapper-app-info-window.c:86
|
||||
msgid "Hardware"
|
||||
msgstr "العتاد"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/clapper-app-info-window.c:86
|
||||
msgid "Software"
|
||||
@@ -521,7 +520,7 @@ msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/clapper-app-preferences-window.c:441
|
||||
msgid "Accurate"
|
||||
msgstr "دقيق"
|
||||
msgstr "دَقيق"
|
||||
|
||||
#: src/bin/clapper-app/clapper-app-preferences-window.c:443
|
||||
msgid "Normal"
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
|
||||
"PO-Revision-Date: 2025-05-18 14:52\n"
|
||||
"PO-Revision-Date: 2025-02-15 13:14\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Persian\n"
|
||||
"Language: fa_IR\n"
|
||||
@@ -305,7 +305,7 @@ msgstr "جریانهای زیرنویس"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46
|
||||
msgid "Start by adding media to playback queue"
|
||||
msgstr "آغاز با افزودن رسانه به صف پخش"
|
||||
msgstr "آغزا با افزودن رسانه به صف پخش"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15
|
||||
msgid "Seeking"
|
||||
@@ -402,7 +402,7 @@ msgstr "افزایههای موجود"
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183
|
||||
msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred."
|
||||
msgstr "گزینش افزایه و ویژگیش برای پایمالی رتبه دهیش. هنگامی که چندین عنصر قابلیتهای مشابهی دارند، آنی که بیشترین رتبه را دارد ترجیح داده میشود."
|
||||
msgstr "گزینش افزایه و ویژگیش برای پایمالی رتبه دهیش. هنگامی که چندین عنصر قابلیتّای مشابهی دارند، آنی که بیشترین رتبه را دارد ترجیح داده میشود."
|
||||
|
||||
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186
|
||||
msgid "Plugin"
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
|
||||
"PO-Revision-Date: 2025-05-01 13:52\n"
|
||||
"PO-Revision-Date: 2025-02-15 12:11\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -33,7 +33,7 @@ msgstr "播放视频和音乐"
|
||||
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
|
||||
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
|
||||
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
|
||||
msgstr "视频;电影;影片;短片;连续剧;播放器;播放列表;DVD;电视;唱片;"
|
||||
msgstr ""
|
||||
|
||||
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
|
||||
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
|
||||
|
@@ -136,6 +136,24 @@
|
||||
<object class="AdwPreferencesPage">
|
||||
<property name="title" translatable="yes">Tweaks</property>
|
||||
<property name="icon-name">applications-engineering-symbolic</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="no">Clapper</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Enhancers</property>
|
||||
<property name="subtitle" translatable="yes">Browse and configure properties of available enhancers</property>
|
||||
<property name="activatable">true</property>
|
||||
<signal name="activated" handler="enhancers_config_activated_cb"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon_name">go-next-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="no">GStreamer</property>
|
||||
@@ -160,6 +178,124 @@
|
||||
<class name="preferences"/>
|
||||
</style>
|
||||
</template>
|
||||
<object class="AdwNavigationPage" id="enhancers_subpage">
|
||||
<property name="title" translatable="yes">Clapper Enhancers</property>
|
||||
<property name="child">
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar"/>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="GtkScrolledWindow">
|
||||
<child>
|
||||
<object class="AdwClamp">
|
||||
<style>
|
||||
<class name="subcontent"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkStack" id="enhancers_stack">
|
||||
<child>
|
||||
<object class="GtkBox" id="browse_enhancers_page">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Available enhancers</property>
|
||||
<property name="description" translatable="yes">Select an enhancer plugin to view its information and properties to configure.</property>
|
||||
<child>
|
||||
<object class="AdwComboRow" id="enhancers_combo_row">
|
||||
<property name="title" translatable="yes">Enhancer</property>
|
||||
<property name="enable-search">true</property>
|
||||
<property name="expression">
|
||||
<lookup type="ClapperEnhancerProxy" name="friendly-name"/>
|
||||
</property>
|
||||
<signal name="notify::selected" handler="selected_enhancer_changed_cb"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<binding name="visible">
|
||||
<closure type="gboolean" function="list_has_selection_closure">
|
||||
<lookup name="selected">enhancers_combo_row</lookup>
|
||||
</closure>
|
||||
</binding>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Information</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Module</property>
|
||||
<binding name="subtitle">
|
||||
<lookup name="module-name" type="ClapperEnhancerProxy">
|
||||
<lookup name="selected-item">enhancers_combo_row</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<style>
|
||||
<class name="property"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<binding name="subtitle">
|
||||
<lookup name="description" type="ClapperEnhancerProxy">
|
||||
<lookup name="selected-item">enhancers_combo_row</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<style>
|
||||
<class name="property"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Version</property>
|
||||
<binding name="subtitle">
|
||||
<lookup name="version" type="ClapperEnhancerProxy">
|
||||
<lookup name="selected-item">enhancers_combo_row</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<style>
|
||||
<class name="property"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="enhancer_config_group">
|
||||
<property name="title" translatable="yes">Properties</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwStatusPage" id="no_enhancers_page">
|
||||
<property name="vexpand">true</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="icon-name">edit-find-symbolic</property>
|
||||
<property name="title" translatable="yes">No Clapper Enhancers Found</property>
|
||||
<property name="description" translatable="yes">Install some to add more cool functionalities to the player!</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
<signal name="unrealize" handler="plugin_ranking_unrealize_cb"/>
|
||||
<style>
|
||||
<class name="configsubpage"/>
|
||||
</style>
|
||||
</object>
|
||||
<object class="AdwNavigationPage" id="plugins_subpage">
|
||||
<property name="title" translatable="yes">Plugin Ranking</property>
|
||||
<property name="child">
|
||||
@@ -240,7 +376,7 @@
|
||||
</property>
|
||||
<signal name="unrealize" handler="plugin_ranking_unrealize_cb"/>
|
||||
<style>
|
||||
<class name="pluginssubpage"/>
|
||||
<class name="configsubpage"/>
|
||||
</style>
|
||||
</object>
|
||||
</interface>
|
||||
|
@@ -176,7 +176,6 @@ if build_gir
|
||||
clappergtk_enums,
|
||||
],
|
||||
extra_args: [
|
||||
gir_init_section,
|
||||
'--quiet',
|
||||
'--warn-all',
|
||||
'-DCLAPPER_GTK_COMPILATION',
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-07 19:03+0100\n"
|
||||
"PO-Revision-Date: 2025-06-14 15:48\n"
|
||||
"PO-Revision-Date: 2025-01-07 18:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Arabic\n"
|
||||
"Language: ar_SA\n"
|
||||
@@ -20,7 +20,7 @@ msgstr ""
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9
|
||||
#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228
|
||||
msgid "Video"
|
||||
msgstr "الفيديو"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17
|
||||
#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229
|
||||
@@ -30,50 +30,50 @@ msgstr "الصوت"
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25
|
||||
#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230
|
||||
msgid "Subtitles"
|
||||
msgstr "الترجَمة"
|
||||
msgstr "التَّرْجَمَةً"
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28
|
||||
msgid "Show Subtitles"
|
||||
msgstr "اعرض الترجَمة"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32
|
||||
msgid "Open…"
|
||||
msgstr "افتح…"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75
|
||||
msgid "Mute"
|
||||
msgstr "اكتم"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119
|
||||
msgid "Reset"
|
||||
msgstr "صفِّر"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25
|
||||
msgid "External video output"
|
||||
msgstr "مخرج الفيديو الخارجي"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37
|
||||
msgid "Used video sink cannot be embedded within application window"
|
||||
msgstr "لا يمكن تضمين بالوعة الفيديو المستخدمة في نافذة التطبيق"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/clapper-gtk-status.c:79
|
||||
msgid "Unplayable Content"
|
||||
msgstr "محتوى غير قابل للتشغيل"
|
||||
msgstr ""
|
||||
|
||||
#. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name).
|
||||
#: src/lib/clapper-gtk/clapper-gtk-status.c:89
|
||||
#, c-format
|
||||
msgid "Your GStreamer installation is missing a plugin: %s"
|
||||
msgstr "يفتقد تنصيبك لـGStreamer ملحقًا: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/clapper-gtk-status.c:90
|
||||
msgid "Missing Plugin"
|
||||
msgstr "ملحق مفقود"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82
|
||||
#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126
|
||||
msgid "Undetermined"
|
||||
msgstr "غير محدَّد"
|
||||
msgstr "غير محدّد"
|
||||
|
||||
#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82
|
||||
msgid "Channels"
|
||||
@@ -81,9 +81,9 @@ msgstr "قنوات"
|
||||
|
||||
#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91
|
||||
msgid "No media"
|
||||
msgstr "لا وسائط"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103
|
||||
msgid "Unknown title"
|
||||
msgstr "عنوان مجهول"
|
||||
msgstr ""
|
||||
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-07 19:03+0100\n"
|
||||
"PO-Revision-Date: 2025-05-08 07:56\n"
|
||||
"PO-Revision-Date: 2025-01-07 18:32\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -50,11 +50,11 @@ msgstr "重置"
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25
|
||||
msgid "External video output"
|
||||
msgstr "外部视频输出"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37
|
||||
msgid "Used video sink cannot be embedded within application window"
|
||||
msgstr "已使用的视频接受器不能嵌入到应用程序窗口"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/clapper-gtk/clapper-gtk-status.c:79
|
||||
msgid "Unplayable Content"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@@ -22,17 +22,23 @@
|
||||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include "clapper.h"
|
||||
#include "clapper-basic-functions.h"
|
||||
#include "clapper-cache-private.h"
|
||||
#include "clapper-utils-private.h"
|
||||
#include "clapper-playbin-bus-private.h"
|
||||
#include "clapper-app-bus-private.h"
|
||||
#include "clapper-features-bus-private.h"
|
||||
#include "clapper-enhancer-proxy-list-private.h"
|
||||
#include "clapper-reactables-manager-private.h"
|
||||
#include "gst/clapper-plugin-private.h"
|
||||
|
||||
#include "clapper-functionalities-availability.h"
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
#include "clapper-enhancers-loader-private.h"
|
||||
#endif
|
||||
|
||||
static ClapperEnhancerProxyList *_proxies = NULL;
|
||||
static gboolean is_initialized = FALSE;
|
||||
static GMutex init_lock;
|
||||
|
||||
@@ -46,13 +52,17 @@ clapper_init_check_internal (int *argc, char **argv[])
|
||||
|
||||
gst_pb_utils_init ();
|
||||
|
||||
clapper_cache_initialize ();
|
||||
clapper_utils_initialize ();
|
||||
clapper_playbin_bus_initialize ();
|
||||
clapper_app_bus_initialize ();
|
||||
clapper_features_bus_initialize ();
|
||||
clapper_reactables_manager_initialize ();
|
||||
|
||||
_proxies = clapper_enhancer_proxy_list_new_named ("global-proxy-list");
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
clapper_enhancers_loader_initialize ();
|
||||
clapper_enhancers_loader_initialize (_proxies);
|
||||
#endif
|
||||
|
||||
gst_plugin_register_static (
|
||||
@@ -149,18 +159,75 @@ clapper_init_check (int *argc, char **argv[])
|
||||
* Returns: whether a plausible enhancer was found.
|
||||
*
|
||||
* Since: 0.8
|
||||
*
|
||||
* Deprecated: 0.10: Use list of enhancer proxies from [func@Clapper.get_global_enhancer_proxies] or
|
||||
* [property@Clapper.Player:enhancer-proxies] and check if any proxy matches your search criteria.
|
||||
*/
|
||||
gboolean
|
||||
clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name)
|
||||
{
|
||||
gboolean success = FALSE;
|
||||
gboolean is_https;
|
||||
guint i, n_proxies;
|
||||
|
||||
g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_type), FALSE);
|
||||
g_return_val_if_fail (scheme != NULL, FALSE);
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
success = clapper_enhancers_loader_check (iface_type, scheme, host, name);
|
||||
#endif
|
||||
if (host) {
|
||||
/* Strip common subdomains, so plugins do not
|
||||
* have to list all combinations */
|
||||
if (g_str_has_prefix (host, "www."))
|
||||
host += 4;
|
||||
else if (g_str_has_prefix (host, "m."))
|
||||
host += 2;
|
||||
}
|
||||
|
||||
return success;
|
||||
/* Whether "http(s)" scheme is used */
|
||||
is_https = (g_str_has_prefix (scheme, "http")
|
||||
&& (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0')));
|
||||
|
||||
if (!host && is_https)
|
||||
return FALSE;
|
||||
|
||||
n_proxies = clapper_enhancer_proxy_list_get_n_proxies (_proxies);
|
||||
for (i = 0; i < n_proxies; ++i) {
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (_proxies, i);
|
||||
|
||||
if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type)
|
||||
&& clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme)
|
||||
&& (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) {
|
||||
if (name)
|
||||
*name = clapper_enhancer_proxy_get_friendly_name (proxy);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_get_global_enhancer_proxies:
|
||||
*
|
||||
* Get a list of available enhancers in the form of [class@Clapper.EnhancerProxy] objects.
|
||||
*
|
||||
* This returns a global list of enhancer proxy objects. You can use it to inspect
|
||||
* available enhancers without creating a new player instance.
|
||||
*
|
||||
* Remember to initialize Clapper library before using this function.
|
||||
*
|
||||
* Only enhancer properties with [flags@Clapper.EnhancerParamFlags.GLOBAL] flag can be
|
||||
* set on proxies in this list. These are meant to be set ONLY by users, not applications
|
||||
* as they carry over to all player instances (possibly including other apps). Applications
|
||||
* should instead be changing properties with [flags@Clapper.EnhancerParamFlags.LOCAL] flag
|
||||
* set from individual proxy lists from [property@Clapper.Player:enhancer-proxies] which
|
||||
* will affect only that single player instance given list belongs to.
|
||||
*
|
||||
* Returns: (transfer none): a global #ClapperEnhancerProxyList of enhancer proxies.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
ClapperEnhancerProxyList *
|
||||
clapper_get_global_enhancer_proxies (void)
|
||||
{
|
||||
return _proxies;
|
||||
}
|
46
src/lib/clapper/clapper-basic-functions.h
Normal file
46
src/lib/clapper/clapper-basic-functions.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
|
||||
#error "Only <clapper/clapper.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include <clapper/clapper-visibility.h>
|
||||
#include <clapper/clapper-enhancer-proxy-list.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_init (int *argc, char **argv[]);
|
||||
|
||||
CLAPPER_API
|
||||
gboolean clapper_init_check (int *argc, char **argv[]);
|
||||
|
||||
CLAPPER_DEPRECATED
|
||||
gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name);
|
||||
|
||||
CLAPPER_API
|
||||
ClapperEnhancerProxyList * clapper_get_global_enhancer_proxies (void);
|
||||
|
||||
G_END_DECLS
|
108
src/lib/clapper/clapper-cache-private.h
Normal file
108
src/lib/clapper/clapper-cache-private.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_initialize (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_cache_is_disabled (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GMappedFile * clapper_cache_open (const gchar *filename, const gchar **data, GError **error);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_cache_read_boolean (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gint clapper_cache_read_int (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
guint clapper_cache_read_uint (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gint64 clapper_cache_read_int64 (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gdouble clapper_cache_read_double (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
const gchar * clapper_cache_read_string (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
const guint8 * clapper_cache_read_data (const gchar **data, gsize *size);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GType clapper_cache_read_enum (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GType clapper_cache_read_flags (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GType clapper_cache_read_iface (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GParamSpec * clapper_cache_read_pspec (const gchar **data);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GByteArray * clapper_cache_create (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_boolean (GByteArray *bytes, gboolean val);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_int (GByteArray *bytes, gint val);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_uint (GByteArray *bytes, guint val);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_int64 (GByteArray *bytes, gint64 val);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_double (GByteArray *bytes, gdouble val);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_string (GByteArray *bytes, const gchar *val);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_data (GByteArray *bytes, const guint8 *val, gsize val_size);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_enum (GByteArray *bytes, GType enum_type);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_cache_store_flags (GByteArray *bytes, GType flags_type);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_cache_store_iface (GByteArray *bytes, GType iface);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_cache_store_pspec (GByteArray *bytes, GParamSpec *pspec);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_cache_write (const gchar *filename, GByteArray *bytes, GError **error);
|
||||
|
||||
G_END_DECLS
|
542
src/lib/clapper/clapper-cache.c
Normal file
542
src/lib/clapper/clapper-cache.c
Normal file
@@ -0,0 +1,542 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "clapper-cache-private.h"
|
||||
#include "clapper-version.h"
|
||||
#include "clapper-extractable.h"
|
||||
#include "clapper-reactable.h"
|
||||
|
||||
#define CLAPPER_CACHE_HEADER "CLAPPER"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CLAPPER_CACHE_IFACE_EXTRACTABLE = 1,
|
||||
CLAPPER_CACHE_IFACE_REACTABLE,
|
||||
} ClapperCacheIfaces;
|
||||
|
||||
static GArray *enum_registry = NULL;
|
||||
static GArray *flags_registry = NULL;
|
||||
static gboolean cache_disabled = FALSE;
|
||||
|
||||
void
|
||||
clapper_cache_initialize (void)
|
||||
{
|
||||
const gchar *env = g_getenv ("CLAPPER_DISABLE_CACHE");
|
||||
|
||||
if (G_LIKELY (!env || !g_str_has_prefix (env, "1"))) {
|
||||
enum_registry = g_array_new (FALSE, TRUE, sizeof (GEnumValue *));
|
||||
flags_registry = g_array_new (FALSE, TRUE, sizeof (GFlagsValue *));
|
||||
} else {
|
||||
cache_disabled = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_cache_is_disabled (void)
|
||||
{
|
||||
return cache_disabled;
|
||||
}
|
||||
|
||||
GMappedFile *
|
||||
clapper_cache_open (const gchar *filename, const gchar **data, GError **error)
|
||||
{
|
||||
GMappedFile *file;
|
||||
|
||||
if (G_UNLIKELY (cache_disabled))
|
||||
return NULL;
|
||||
|
||||
if (!(file = g_mapped_file_new (filename, FALSE, error)))
|
||||
return NULL;
|
||||
|
||||
if (G_UNLIKELY (g_mapped_file_get_length (file) == 0)) {
|
||||
g_mapped_file_unref (file);
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"File is empty");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*data = g_mapped_file_get_contents (file);
|
||||
|
||||
/* Header name check */
|
||||
if (G_UNLIKELY (g_strcmp0 (*data, CLAPPER_CACHE_HEADER) != 0)) {
|
||||
g_mapped_file_unref (file);
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Invalid file header");
|
||||
return NULL;
|
||||
}
|
||||
*data += strlen (*data) + 1;
|
||||
|
||||
/* Header version check */
|
||||
if (clapper_cache_read_uint (data) != CLAPPER_VERSION_HEX) {
|
||||
g_mapped_file_unref (file);
|
||||
/* Just different version, so no error set */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
inline gboolean
|
||||
clapper_cache_read_boolean (const gchar **data)
|
||||
{
|
||||
gboolean val = *(const gboolean *) *data;
|
||||
*data += sizeof (gboolean);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
inline gint
|
||||
clapper_cache_read_int (const gchar **data)
|
||||
{
|
||||
gint val = *(const gint *) *data;
|
||||
*data += sizeof (gint);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
inline guint
|
||||
clapper_cache_read_uint (const gchar **data)
|
||||
{
|
||||
guint val = *(const guint *) *data;
|
||||
*data += sizeof (guint);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
inline gint64
|
||||
clapper_cache_read_int64 (const gchar **data)
|
||||
{
|
||||
gint64 val = *(const gint64 *) *data;
|
||||
*data += sizeof (gint64);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
inline gdouble
|
||||
clapper_cache_read_double (const gchar **data)
|
||||
{
|
||||
gdouble val = *(const gdouble *) *data;
|
||||
*data += sizeof (gdouble);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
inline const gchar *
|
||||
clapper_cache_read_string (const gchar **data)
|
||||
{
|
||||
const gboolean is_null = clapper_cache_read_boolean (data);
|
||||
const gchar *str = NULL;
|
||||
|
||||
if (!is_null) {
|
||||
str = *data;
|
||||
*data += strlen (str) + 1;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
inline const guint8 *
|
||||
clapper_cache_read_data (const gchar **data, gsize *size)
|
||||
{
|
||||
const guint8 *val = NULL;
|
||||
|
||||
*size = *(const gsize *) *data;
|
||||
*data += sizeof (gsize);
|
||||
|
||||
if (G_LIKELY (*size > 0)) {
|
||||
val = (const guint8 *) *data;
|
||||
*data += *size;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
inline GType
|
||||
clapper_cache_read_enum (const gchar **data)
|
||||
{
|
||||
GType type;
|
||||
const gchar *enum_name;
|
||||
guint i, n_values;
|
||||
|
||||
enum_name = clapper_cache_read_string (data);
|
||||
n_values = clapper_cache_read_uint (data);
|
||||
|
||||
/* If not registered yet */
|
||||
if ((type = g_type_from_name (enum_name)) == 0) {
|
||||
GEnumValue *values = g_new0 (GEnumValue, n_values + 1);
|
||||
|
||||
for (i = 0; i < n_values; ++i) {
|
||||
values[i].value = clapper_cache_read_int (data);
|
||||
values[i].value_name = g_intern_string (clapper_cache_read_string (data));
|
||||
values[i].value_nick = g_intern_string (clapper_cache_read_string (data));
|
||||
}
|
||||
g_array_append_val (enum_registry, values); // store statically
|
||||
|
||||
type = g_enum_register_static (g_intern_string (enum_name),
|
||||
g_array_index (enum_registry, GEnumValue *, enum_registry->len - 1));
|
||||
} else {
|
||||
/* Skip over data */
|
||||
for (i = 0; i < n_values; ++i) {
|
||||
clapper_cache_read_int (data); // value
|
||||
clapper_cache_read_string (data); // value_name
|
||||
clapper_cache_read_string (data); // value_nick
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
inline GType
|
||||
clapper_cache_read_flags (const gchar **data)
|
||||
{
|
||||
GType type;
|
||||
const gchar *flags_name;
|
||||
guint i, n_values;
|
||||
|
||||
flags_name = clapper_cache_read_string (data);
|
||||
n_values = clapper_cache_read_uint (data);
|
||||
|
||||
/* If not registered yet */
|
||||
if ((type = g_type_from_name (flags_name)) == 0) {
|
||||
GFlagsValue *values = g_new0 (GFlagsValue, n_values + 1);
|
||||
|
||||
for (i = 0; i < n_values; ++i) {
|
||||
values[i].value = clapper_cache_read_int (data);
|
||||
values[i].value_name = g_intern_string (clapper_cache_read_string (data));
|
||||
values[i].value_nick = g_intern_string (clapper_cache_read_string (data));
|
||||
}
|
||||
g_array_append_val (flags_registry, values); // store statically
|
||||
|
||||
type = g_flags_register_static (g_intern_string (flags_name),
|
||||
g_array_index (flags_registry, GFlagsValue *, flags_registry->len - 1));
|
||||
} else {
|
||||
/* Skip over data */
|
||||
for (i = 0; i < n_values; ++i) {
|
||||
clapper_cache_read_int (data); // value
|
||||
clapper_cache_read_string (data); // value_name
|
||||
clapper_cache_read_string (data); // value_nick
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
GType
|
||||
clapper_cache_read_iface (const gchar **data)
|
||||
{
|
||||
gint iface_id = clapper_cache_read_int (data);
|
||||
|
||||
switch (iface_id) {
|
||||
case CLAPPER_CACHE_IFACE_EXTRACTABLE:
|
||||
return CLAPPER_TYPE_EXTRACTABLE;
|
||||
case CLAPPER_CACHE_IFACE_REACTABLE:
|
||||
return CLAPPER_TYPE_REACTABLE;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
GParamSpec *
|
||||
clapper_cache_read_pspec (const gchar **data)
|
||||
{
|
||||
GParamSpec *pspec;
|
||||
GType value_type;
|
||||
const gchar *name, *nick, *blurb;
|
||||
GParamFlags flags;
|
||||
|
||||
value_type = *(const GType *) *data;
|
||||
*data += sizeof (GType);
|
||||
|
||||
name = clapper_cache_read_string (data);
|
||||
nick = clapper_cache_read_string (data);
|
||||
blurb = clapper_cache_read_string (data);
|
||||
|
||||
flags = *(const GParamFlags *) *data;
|
||||
*data += sizeof (GParamFlags);
|
||||
|
||||
/* NOTE: C does not guarantee order in which function arguments
|
||||
* are evaluated, so read into variables and then create pspec */
|
||||
|
||||
switch (value_type) {
|
||||
case G_TYPE_BOOLEAN:
|
||||
pspec = g_param_spec_boolean (name, nick, blurb,
|
||||
clapper_cache_read_boolean (data), flags);
|
||||
break;
|
||||
case G_TYPE_INT:{
|
||||
gint minimum = clapper_cache_read_int (data);
|
||||
gint maximum = clapper_cache_read_int (data);
|
||||
gint default_value = clapper_cache_read_int (data);
|
||||
|
||||
pspec = g_param_spec_int (name, nick, blurb,
|
||||
minimum, maximum, default_value, flags);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_UINT:{
|
||||
guint minimum = clapper_cache_read_uint (data);
|
||||
guint maximum = clapper_cache_read_uint (data);
|
||||
guint default_value = clapper_cache_read_uint (data);
|
||||
|
||||
pspec = g_param_spec_uint (name, nick, blurb,
|
||||
minimum, maximum, default_value, flags);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_DOUBLE:{
|
||||
gdouble minimum = clapper_cache_read_double (data);
|
||||
gdouble maximum = clapper_cache_read_double (data);
|
||||
gdouble default_value = clapper_cache_read_double (data);
|
||||
|
||||
pspec = g_param_spec_double (name, nick, blurb,
|
||||
minimum, maximum, default_value, flags);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_STRING:
|
||||
pspec = g_param_spec_string (name, nick, blurb,
|
||||
clapper_cache_read_string (data), flags);
|
||||
break;
|
||||
case G_TYPE_ENUM:{
|
||||
GType enum_type = clapper_cache_read_enum (data);
|
||||
gint default_value = clapper_cache_read_int (data);
|
||||
|
||||
pspec = g_param_spec_enum (name, nick, blurb,
|
||||
enum_type, default_value, flags);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_FLAGS:{
|
||||
GType flags_type = clapper_cache_read_flags (data);
|
||||
guint default_value = clapper_cache_read_uint (data);
|
||||
|
||||
pspec = g_param_spec_flags (name, nick, blurb,
|
||||
flags_type, default_value, flags);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_param_spec_ref_sink (pspec);
|
||||
}
|
||||
|
||||
GByteArray *
|
||||
clapper_cache_create (void)
|
||||
{
|
||||
GByteArray *bytes;
|
||||
|
||||
if (G_UNLIKELY (cache_disabled))
|
||||
return NULL;
|
||||
|
||||
bytes = g_byte_array_new ();
|
||||
|
||||
/* NOTE: We do not store whether string is NULL here, since it never is */
|
||||
g_byte_array_append (bytes, (const guint8 *) CLAPPER_CACHE_HEADER, 8); // 7 + 1
|
||||
clapper_cache_store_uint (bytes, CLAPPER_VERSION_HEX);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_boolean (GByteArray *bytes, gboolean val)
|
||||
{
|
||||
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gboolean));
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_int (GByteArray *bytes, gint val)
|
||||
{
|
||||
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gint));
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_uint (GByteArray *bytes, guint val)
|
||||
{
|
||||
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (guint));
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_int64 (GByteArray *bytes, gint64 val)
|
||||
{
|
||||
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gint64));
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_double (GByteArray *bytes, gdouble val)
|
||||
{
|
||||
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gdouble));
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_string (GByteArray *bytes, const gchar *val)
|
||||
{
|
||||
/* Distinguish empty string from NULL */
|
||||
const gboolean is_null = (val == NULL);
|
||||
|
||||
clapper_cache_store_boolean (bytes, is_null);
|
||||
if (!is_null)
|
||||
g_byte_array_append (bytes, (const guint8 *) val, strlen (val) + 1);
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_data (GByteArray *bytes, const guint8 *val, gsize val_size)
|
||||
{
|
||||
g_byte_array_append (bytes, (const guint8 *) &val_size, sizeof (gsize));
|
||||
if (G_LIKELY (val_size > 0))
|
||||
g_byte_array_append (bytes, val, val_size);
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_enum (GByteArray *bytes, GType enum_type)
|
||||
{
|
||||
GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (enum_type));
|
||||
guint i;
|
||||
|
||||
clapper_cache_store_string (bytes, g_type_name (enum_type));
|
||||
clapper_cache_store_uint (bytes, enum_class->n_values);
|
||||
|
||||
for (i = 0; i < enum_class->n_values; ++i) {
|
||||
clapper_cache_store_int (bytes, enum_class->values[i].value);
|
||||
clapper_cache_store_string (bytes, enum_class->values[i].value_name);
|
||||
clapper_cache_store_string (bytes, enum_class->values[i].value_nick);
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
clapper_cache_store_flags (GByteArray *bytes, GType flags_type)
|
||||
{
|
||||
GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (flags_type));
|
||||
guint i;
|
||||
|
||||
clapper_cache_store_string (bytes, g_type_name (flags_type));
|
||||
clapper_cache_store_uint (bytes, flags_class->n_values);
|
||||
|
||||
for (i = 0; i < flags_class->n_values; ++i) {
|
||||
clapper_cache_store_int (bytes, flags_class->values[i].value);
|
||||
clapper_cache_store_string (bytes, flags_class->values[i].value_name);
|
||||
clapper_cache_store_string (bytes, flags_class->values[i].value_nick);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_cache_store_iface (GByteArray *bytes, GType iface)
|
||||
{
|
||||
gint iface_id = 0;
|
||||
|
||||
if (iface == CLAPPER_TYPE_EXTRACTABLE)
|
||||
iface_id = CLAPPER_CACHE_IFACE_EXTRACTABLE;
|
||||
else if (iface == CLAPPER_TYPE_REACTABLE)
|
||||
iface_id = CLAPPER_CACHE_IFACE_REACTABLE;
|
||||
else
|
||||
return FALSE;
|
||||
|
||||
clapper_cache_store_int (bytes, iface_id);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_cache_store_pspec (GByteArray *bytes, GParamSpec *pspec)
|
||||
{
|
||||
GParamFlags flags;
|
||||
const gboolean is_enum = G_IS_PARAM_SPEC_ENUM (pspec);
|
||||
const gboolean is_flags = (!is_enum && G_IS_PARAM_SPEC_FLAGS (pspec));
|
||||
|
||||
if (is_enum) {
|
||||
GType enum_type = G_TYPE_ENUM;
|
||||
g_byte_array_append (bytes, (const guint8 *) &enum_type, sizeof (GType));
|
||||
} else if (is_flags) {
|
||||
GType flags_type = G_TYPE_FLAGS;
|
||||
g_byte_array_append (bytes, (const guint8 *) &flags_type, sizeof (GType));
|
||||
} else {
|
||||
g_byte_array_append (bytes, (const guint8 *) &pspec->value_type, sizeof (GType));
|
||||
}
|
||||
|
||||
clapper_cache_store_string (bytes, g_param_spec_get_name (pspec));
|
||||
clapper_cache_store_string (bytes, g_param_spec_get_nick (pspec));
|
||||
clapper_cache_store_string (bytes, g_param_spec_get_blurb (pspec));
|
||||
|
||||
flags = pspec->flags;
|
||||
flags &= ~G_PARAM_STATIC_STRINGS; // Data read from cache is never static
|
||||
g_byte_array_append (bytes, (const guint8 *) &flags, sizeof (GParamFlags));
|
||||
|
||||
switch (pspec->value_type) {
|
||||
case G_TYPE_BOOLEAN:{
|
||||
GParamSpecBoolean *p = (GParamSpecBoolean *) pspec;
|
||||
clapper_cache_store_boolean (bytes, p->default_value);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_INT:{
|
||||
GParamSpecInt *p = (GParamSpecInt *) pspec;
|
||||
clapper_cache_store_int (bytes, p->minimum);
|
||||
clapper_cache_store_int (bytes, p->maximum);
|
||||
clapper_cache_store_int (bytes, p->default_value);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_UINT:{
|
||||
GParamSpecUInt *p = (GParamSpecUInt *) pspec;
|
||||
clapper_cache_store_uint (bytes, p->minimum);
|
||||
clapper_cache_store_uint (bytes, p->maximum);
|
||||
clapper_cache_store_uint (bytes, p->default_value);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_DOUBLE:{
|
||||
GParamSpecDouble *p = (GParamSpecDouble *) pspec;
|
||||
clapper_cache_store_double (bytes, p->minimum);
|
||||
clapper_cache_store_double (bytes, p->maximum);
|
||||
clapper_cache_store_double (bytes, p->default_value);
|
||||
break;
|
||||
}
|
||||
case G_TYPE_STRING:{
|
||||
GParamSpecString *p = (GParamSpecString *) pspec;
|
||||
clapper_cache_store_string (bytes, p->default_value);
|
||||
break;
|
||||
}
|
||||
default:{
|
||||
if (is_enum) {
|
||||
GParamSpecEnum *p = (GParamSpecEnum *) pspec;
|
||||
clapper_cache_store_enum (bytes, pspec->value_type);
|
||||
clapper_cache_store_int (bytes, p->default_value);
|
||||
break;
|
||||
} else if (is_flags) {
|
||||
GParamSpecFlags *p = (GParamSpecFlags *) pspec;
|
||||
clapper_cache_store_flags (bytes, pspec->value_type);
|
||||
clapper_cache_store_uint (bytes, p->default_value);
|
||||
break;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_cache_write (const gchar *filename, GByteArray *bytes, GError **error)
|
||||
{
|
||||
gchar *dirname = g_path_get_dirname (filename);
|
||||
gboolean has_dir;
|
||||
|
||||
has_dir = (g_mkdir_with_parents (dirname, 0755) == 0);
|
||||
g_free (dirname);
|
||||
|
||||
if (!has_dir) {
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Could not create directory to store cache content");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Using "g_file_set_contents" to replace file atomically */
|
||||
return g_file_set_contents (filename, (const gchar *) bytes->data, bytes->len, error);
|
||||
}
|
45
src/lib/clapper/clapper-enhancer-proxy-list-private.h
Normal file
45
src/lib/clapper/clapper-enhancer-proxy-list-private.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "clapper-enhancer-proxy-list.h"
|
||||
#include "clapper-enhancer-proxy.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
ClapperEnhancerProxyList * clapper_enhancer_proxy_list_new_named (const gchar *name);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_enhancer_proxy_list_take_proxy (ClapperEnhancerProxyList *list, ClapperEnhancerProxy *proxy);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyList *list);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *list);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_enhancer_proxy_list_has_proxy_with_interface (ClapperEnhancerProxyList *list, GType iface_type);
|
||||
|
||||
G_END_DECLS
|
352
src/lib/clapper/clapper-enhancer-proxy-list.c
Normal file
352
src/lib/clapper/clapper-enhancer-proxy-list.c
Normal file
@@ -0,0 +1,352 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ClapperEnhancerProxyList:
|
||||
*
|
||||
* A list of enhancer proxies.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "clapper-basic-functions.h"
|
||||
#include "clapper-enhancer-proxy-list-private.h"
|
||||
#include "clapper-enhancer-proxy-private.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_enhancer_proxy_list_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _ClapperEnhancerProxyList
|
||||
{
|
||||
GstObject parent;
|
||||
|
||||
GPtrArray *proxies;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_N_PROXIES,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static void clapper_enhancer_proxy_list_model_iface_init (GListModelInterface *iface);
|
||||
|
||||
#define parent_class clapper_enhancer_proxy_list_parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (ClapperEnhancerProxyList, clapper_enhancer_proxy_list, GST_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_enhancer_proxy_list_model_iface_init));
|
||||
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static GType
|
||||
clapper_enhancer_proxy_list_model_get_item_type (GListModel *model)
|
||||
{
|
||||
return CLAPPER_TYPE_ENHANCER_PROXY;
|
||||
}
|
||||
|
||||
static guint
|
||||
clapper_enhancer_proxy_list_model_get_n_items (GListModel *model)
|
||||
{
|
||||
return CLAPPER_ENHANCER_PROXY_LIST_CAST (model)->proxies->len;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
clapper_enhancer_proxy_list_model_get_item (GListModel *model, guint index)
|
||||
{
|
||||
ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (model);
|
||||
ClapperEnhancerProxy *proxy = NULL;
|
||||
|
||||
if (G_LIKELY (index < self->proxies->len))
|
||||
proxy = gst_object_ref (g_ptr_array_index (self->proxies, index));
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_proxy_list_model_iface_init (GListModelInterface *iface)
|
||||
{
|
||||
iface->get_item_type = clapper_enhancer_proxy_list_model_get_item_type;
|
||||
iface->get_n_items = clapper_enhancer_proxy_list_model_get_n_items;
|
||||
iface->get_item = clapper_enhancer_proxy_list_model_get_item;
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancer_proxy_list_new_named:
|
||||
* @name: (nullable): name of the #GstObject
|
||||
*
|
||||
* Returns: (transfer full): a new #ClapperEnhancerProxyList instance
|
||||
*/
|
||||
ClapperEnhancerProxyList *
|
||||
clapper_enhancer_proxy_list_new_named (const gchar *name)
|
||||
{
|
||||
ClapperEnhancerProxyList *list;
|
||||
|
||||
list = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY_LIST,
|
||||
"name", name, NULL);
|
||||
gst_object_ref_sink (list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void
|
||||
clapper_enhancer_proxy_list_take_proxy (ClapperEnhancerProxyList *self, ClapperEnhancerProxy *proxy)
|
||||
{
|
||||
g_ptr_array_add (self->proxies, proxy);
|
||||
gst_object_set_parent (GST_OBJECT_CAST (proxy), GST_OBJECT_CAST (self));
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancer_proxy_list_fill_from_global_proxies:
|
||||
*
|
||||
* Fill list with unconfigured proxies from global proxies list.
|
||||
*/
|
||||
void
|
||||
clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyList *self)
|
||||
{
|
||||
ClapperEnhancerProxyList *global_list = clapper_get_global_enhancer_proxies ();
|
||||
static guint _list_id = 0;
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < global_list->proxies->len; ++i) {
|
||||
ClapperEnhancerProxy *proxy, *proxy_copy;
|
||||
gchar obj_name[64];
|
||||
|
||||
proxy = clapper_enhancer_proxy_list_peek_proxy (global_list, i);
|
||||
|
||||
/* Name newly created proxy, very useful for debugging. Keep index per
|
||||
* list, so it will be the same as the player that proxy belongs to. */
|
||||
g_snprintf (obj_name, sizeof (obj_name), "%s-proxy%u",
|
||||
clapper_enhancer_proxy_get_friendly_name (proxy), _list_id);
|
||||
proxy_copy = clapper_enhancer_proxy_copy (proxy, obj_name);
|
||||
|
||||
clapper_enhancer_proxy_list_take_proxy (self, proxy_copy);
|
||||
}
|
||||
_list_id++;
|
||||
}
|
||||
|
||||
static gint
|
||||
_sort_values_by_name (ClapperEnhancerProxy *proxy_a, ClapperEnhancerProxy *proxy_b)
|
||||
{
|
||||
return g_ascii_strcasecmp (
|
||||
clapper_enhancer_proxy_get_friendly_name (proxy_a),
|
||||
clapper_enhancer_proxy_get_friendly_name (proxy_b));
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancer_proxy_list_sort:
|
||||
*
|
||||
* Sort all list elements by enhancer friendly name.
|
||||
*/
|
||||
void
|
||||
clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *self)
|
||||
{
|
||||
g_ptr_array_sort_values (self->proxies, (GCompareFunc) _sort_values_by_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancer_proxy_list_has_proxy_with_interface:
|
||||
* @iface_type: an interface #GType
|
||||
*
|
||||
* Check if any enhancer implementing given interface type is available.
|
||||
*
|
||||
* Returns: whether any enhancer proxy was found.
|
||||
*/
|
||||
gboolean
|
||||
clapper_enhancer_proxy_list_has_proxy_with_interface (ClapperEnhancerProxyList *self, GType iface_type)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < self->proxies->len; ++i) {
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (self, i);
|
||||
|
||||
if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_enhancer_proxy_list_get_proxy:
|
||||
* @list: a #ClapperEnhancerProxyList
|
||||
* @index: an enhancer proxy index
|
||||
*
|
||||
* Get the #ClapperEnhancerProxy at index.
|
||||
*
|
||||
* This behaves the same as [method@Gio.ListModel.get_item], and is here
|
||||
* for code uniformity and convenience to avoid type casting by user.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): The #ClapperEnhancerProxy at @index.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
ClapperEnhancerProxy *
|
||||
clapper_enhancer_proxy_list_get_proxy (ClapperEnhancerProxyList *self, guint index)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL);
|
||||
|
||||
return g_list_model_get_item (G_LIST_MODEL (self), index);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_enhancer_proxy_list_peek_proxy: (skip)
|
||||
* @list: a #ClapperEnhancerProxyList
|
||||
* @index: an enhancer proxy index
|
||||
*
|
||||
* Get the #ClapperEnhancerProxy at index.
|
||||
*
|
||||
* Similar to [method@Clapper.EnhancerProxyList.get_proxy], but does not take
|
||||
* a new reference on proxy.
|
||||
*
|
||||
* Proxies in a list are only removed when a [class@Clapper.Player] instance
|
||||
* they originate from is destroyed, so do not use returned object afterwards
|
||||
* unless you take an additional reference on it.
|
||||
*
|
||||
* Returns: (transfer none) (nullable): The #ClapperEnhancerProxy at @index.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
ClapperEnhancerProxy *
|
||||
clapper_enhancer_proxy_list_peek_proxy (ClapperEnhancerProxyList *self, guint index)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL);
|
||||
|
||||
return g_ptr_array_index (self->proxies, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_enhancer_proxy_list_get_proxy_by_module:
|
||||
* @list: a #ClapperEnhancerProxyList
|
||||
* @module_name: an enhancer module name
|
||||
*
|
||||
* Get the #ClapperEnhancerProxy by module name as defined in its plugin file.
|
||||
*
|
||||
* A convenience function to find a #ClapperEnhancerProxy by its unique
|
||||
* module name in the list.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): The #ClapperEnhancerProxy with requested module name.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
ClapperEnhancerProxy *
|
||||
clapper_enhancer_proxy_list_get_proxy_by_module (ClapperEnhancerProxyList *self, const gchar *module_name)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL);
|
||||
g_return_val_if_fail (module_name != NULL, NULL);
|
||||
|
||||
for (i = 0; i < self->proxies->len; ++i) {
|
||||
ClapperEnhancerProxy *proxy = g_ptr_array_index (self->proxies, i);
|
||||
|
||||
if (strcmp (clapper_enhancer_proxy_get_module_name (proxy), module_name) == 0)
|
||||
return gst_object_ref (proxy);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_enhancer_proxy_list_get_n_proxies:
|
||||
* @list: a #ClapperEnhancerProxyList
|
||||
*
|
||||
* Get the number of proxies in #ClapperEnhancerProxyList.
|
||||
*
|
||||
* This behaves the same as [method@Gio.ListModel.get_n_items], and is here
|
||||
* for code uniformity and convenience to avoid type casting by user.
|
||||
*
|
||||
* Returns: The number of proxies in #ClapperEnhancerProxyList.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
guint
|
||||
clapper_enhancer_proxy_list_get_n_proxies (ClapperEnhancerProxyList *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), 0);
|
||||
|
||||
return g_list_model_get_n_items (G_LIST_MODEL (self));
|
||||
}
|
||||
|
||||
static void
|
||||
_proxy_remove_func (ClapperEnhancerProxy *proxy)
|
||||
{
|
||||
gst_object_unparent (GST_OBJECT_CAST (proxy));
|
||||
gst_object_unref (proxy);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_proxy_list_init (ClapperEnhancerProxyList *self)
|
||||
{
|
||||
self->proxies = g_ptr_array_new_with_free_func ((GDestroyNotify) _proxy_remove_func);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_proxy_list_finalize (GObject *object)
|
||||
{
|
||||
ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Finalize");
|
||||
|
||||
g_ptr_array_unref (self->proxies);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_proxy_list_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_N_PROXIES:
|
||||
g_value_set_uint (value, clapper_enhancer_proxy_list_get_n_proxies (self));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_proxy_list_class_init (ClapperEnhancerProxyListClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancerproxylist", 0,
|
||||
"Clapper Enhancer Proxy List");
|
||||
|
||||
gobject_class->get_property = clapper_enhancer_proxy_list_get_property;
|
||||
gobject_class->finalize = clapper_enhancer_proxy_list_finalize;
|
||||
|
||||
/**
|
||||
* ClapperEnhancerProxyList:n-proxies:
|
||||
*
|
||||
* Number of proxies in the list.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
param_specs[PROP_N_PROXIES] = g_param_spec_uint ("n-proxies",
|
||||
NULL, NULL, 0, G_MAXUINT, 0,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
||||
}
|
53
src/lib/clapper/clapper-enhancer-proxy-list.h
Normal file
53
src/lib/clapper/clapper-enhancer-proxy-list.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
|
||||
#error "Only <clapper/clapper.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <clapper/clapper-visibility.h>
|
||||
#include <clapper/clapper-enhancer-proxy.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_TYPE_ENHANCER_PROXY_LIST (clapper_enhancer_proxy_list_get_type())
|
||||
#define CLAPPER_ENHANCER_PROXY_LIST_CAST(obj) ((ClapperEnhancerProxyList *)(obj))
|
||||
|
||||
CLAPPER_API
|
||||
G_DECLARE_FINAL_TYPE (ClapperEnhancerProxyList, clapper_enhancer_proxy_list, CLAPPER, ENHANCER_PROXY_LIST, GstObject)
|
||||
|
||||
CLAPPER_API
|
||||
ClapperEnhancerProxy * clapper_enhancer_proxy_list_get_proxy (ClapperEnhancerProxyList *list, guint index);
|
||||
|
||||
CLAPPER_API
|
||||
ClapperEnhancerProxy * clapper_enhancer_proxy_list_peek_proxy (ClapperEnhancerProxyList *list, guint index);
|
||||
|
||||
CLAPPER_API
|
||||
ClapperEnhancerProxy * clapper_enhancer_proxy_list_get_proxy_by_module (ClapperEnhancerProxyList *list, const gchar *module_name);
|
||||
|
||||
CLAPPER_API
|
||||
guint clapper_enhancer_proxy_list_get_n_proxies (ClapperEnhancerProxyList *list);
|
||||
|
||||
G_END_DECLS
|
57
src/lib/clapper/clapper-enhancer-proxy-private.h
Normal file
57
src/lib/clapper/clapper-enhancer-proxy-private.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "clapper-enhancer-proxy.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
ClapperEnhancerProxy * clapper_enhancer_proxy_new_global_take (GObject *peas_info); // Using parent type for building without libpeas
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
ClapperEnhancerProxy * clapper_enhancer_proxy_copy (ClapperEnhancerProxy *src_proxy, const gchar *copy_name);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_enhancer_proxy_fill_from_cache (ClapperEnhancerProxy *proxy);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *proxy, GObject *enhancer);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_enhancer_proxy_export_to_cache (ClapperEnhancerProxy *proxy);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GObject * clapper_enhancer_proxy_get_peas_info (ClapperEnhancerProxy *proxy);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_enhancer_proxy_has_locally_set (ClapperEnhancerProxy *proxy, const gchar *property_name);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GstStructure * clapper_enhancer_proxy_make_current_config (ClapperEnhancerProxy *proxy);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_enhancer_proxy_apply_config_to_enhancer (ClapperEnhancerProxy *proxy, const GstStructure *config, GObject *enhancer);
|
||||
|
||||
G_END_DECLS
|
1214
src/lib/clapper/clapper-enhancer-proxy.c
Normal file
1214
src/lib/clapper/clapper-enhancer-proxy.c
Normal file
File diff suppressed because it is too large
Load Diff
80
src/lib/clapper/clapper-enhancer-proxy.h
Normal file
80
src/lib/clapper/clapper-enhancer-proxy.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
|
||||
#error "Only <clapper/clapper.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gio/gio.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <clapper/clapper-visibility.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_TYPE_ENHANCER_PROXY (clapper_enhancer_proxy_get_type())
|
||||
#define CLAPPER_ENHANCER_PROXY_CAST(obj) ((ClapperEnhancerProxy *)(obj))
|
||||
|
||||
CLAPPER_API
|
||||
G_DECLARE_FINAL_TYPE (ClapperEnhancerProxy, clapper_enhancer_proxy, CLAPPER, ENHANCER_PROXY, GstObject)
|
||||
|
||||
CLAPPER_API
|
||||
const gchar * clapper_enhancer_proxy_get_friendly_name (ClapperEnhancerProxy *proxy);
|
||||
|
||||
CLAPPER_API
|
||||
const gchar * clapper_enhancer_proxy_get_module_name (ClapperEnhancerProxy *proxy);
|
||||
|
||||
CLAPPER_API
|
||||
const gchar * clapper_enhancer_proxy_get_module_dir (ClapperEnhancerProxy *proxy);
|
||||
|
||||
CLAPPER_API
|
||||
const gchar * clapper_enhancer_proxy_get_description (ClapperEnhancerProxy *proxy);
|
||||
|
||||
CLAPPER_API
|
||||
const gchar * clapper_enhancer_proxy_get_version (ClapperEnhancerProxy *proxy);
|
||||
|
||||
CLAPPER_API
|
||||
const gchar * clapper_enhancer_proxy_get_extra_data (ClapperEnhancerProxy *proxy, const gchar *key);
|
||||
|
||||
CLAPPER_API
|
||||
gboolean clapper_enhancer_proxy_extra_data_lists_value (ClapperEnhancerProxy *proxy, const gchar *key, const gchar *value);
|
||||
|
||||
CLAPPER_API
|
||||
GType * clapper_enhancer_proxy_get_target_interfaces (ClapperEnhancerProxy *proxy, guint *n_interfaces);
|
||||
|
||||
CLAPPER_API
|
||||
gboolean clapper_enhancer_proxy_target_has_interface (ClapperEnhancerProxy *proxy, GType iface_type);
|
||||
|
||||
CLAPPER_API
|
||||
GParamSpec ** clapper_enhancer_proxy_get_target_properties (ClapperEnhancerProxy *proxy, guint *n_properties);
|
||||
|
||||
CLAPPER_API
|
||||
GSettings * clapper_enhancer_proxy_get_settings (ClapperEnhancerProxy *proxy);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_enhancer_proxy_set_locally (ClapperEnhancerProxy *proxy, const gchar *first_property_name, ...) G_GNUC_NULL_TERMINATED;
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_enhancer_proxy_set_locally_with_table (ClapperEnhancerProxy *proxy, GHashTable *table);
|
||||
|
||||
G_END_DECLS
|
@@ -22,21 +22,15 @@
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "clapper-enhancer-proxy-list.h"
|
||||
#include "clapper-enhancer-proxy.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_enhancers_loader_initialize (void);
|
||||
void clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_enhancers_loader_has_enhancers (GType iface_type);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gchar ** clapper_enhancers_loader_get_schemes (GType iface_type);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_enhancers_loader_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GObject * clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri);
|
||||
GObject * clapper_enhancers_loader_create_enhancer (ClapperEnhancerProxy *proxy, GType iface_type);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -28,11 +28,14 @@ static HMODULE _enhancers_dll_handle = NULL;
|
||||
#endif
|
||||
|
||||
#include "clapper-enhancers-loader-private.h"
|
||||
#include "clapper-enhancer-proxy-list-private.h"
|
||||
#include "clapper-enhancer-proxy-private.h"
|
||||
|
||||
#define ENHANCER_INTERFACES "X-Interfaces"
|
||||
#define ENHANCER_SCHEMES "X-Schemes"
|
||||
#define ENHANCER_HOSTS "X-Hosts"
|
||||
#define ENHANCER_IFACE_NAME_FROM_TYPE(type) (g_type_name (type) + 7) // strip "Clapper" prefix
|
||||
// Supported interfaces
|
||||
#include "clapper-extractable.h"
|
||||
#include "clapper-reactable.h"
|
||||
|
||||
#include <clapper-functionalities-availability.h>
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_enhancers_loader_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
@@ -58,10 +61,11 @@ _import_enhancers (const gchar *enhancers_path)
|
||||
* Initializes #PeasEngine with directories that store enhancers.
|
||||
*/
|
||||
void
|
||||
clapper_enhancers_loader_initialize (void)
|
||||
clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies)
|
||||
{
|
||||
const gchar *enhancers_path;
|
||||
gchar *custom_path = NULL;
|
||||
guint i, n_items;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersloader", 0,
|
||||
"Clapper Enhancer Loader");
|
||||
@@ -74,9 +78,8 @@ clapper_enhancers_loader_initialize (void)
|
||||
|
||||
win_base_dir = g_win32_get_package_installation_directory_of_module (
|
||||
_enhancers_dll_handle);
|
||||
/* FIXME: Avoid hardcoded major version */
|
||||
custom_path = g_build_filename (win_base_dir,
|
||||
"lib", "clapper-0.0", "enhancers", NULL);
|
||||
"lib", CLAPPER_API_NAME, "enhancers", NULL);
|
||||
enhancers_path = custom_path; // assign temporarily
|
||||
|
||||
g_free (win_base_dir);
|
||||
@@ -104,321 +107,115 @@ clapper_enhancers_loader_initialize (void)
|
||||
_import_enhancers (enhancers_path);
|
||||
}
|
||||
|
||||
if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_INFO) {
|
||||
GListModel *list = (GListModel *) _engine;
|
||||
guint i, n_items = g_list_model_get_n_items (list);
|
||||
n_items = g_list_model_get_n_items ((GListModel *) _engine);
|
||||
for (i = 0; i < n_items; ++i) {
|
||||
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item ((GListModel *) _engine, i);
|
||||
ClapperEnhancerProxy *proxy;
|
||||
gboolean filled;
|
||||
|
||||
for (i = 0; i < n_items; ++i) {
|
||||
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i);
|
||||
GST_INFO ("Found enhancer: %s (%s)", peas_plugin_info_get_name (info),
|
||||
peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES));
|
||||
g_object_unref (info);
|
||||
/* FIXME: 1.0: Remove together with features code and manager.
|
||||
* These would clash with each other, so avoid loading these
|
||||
* as enhancers when also compiled as part of the library. */
|
||||
#if (CLAPPER_HAVE_MPRIS || CLAPPER_HAVE_DISCOVERER || CLAPPER_HAVE_SERVER)
|
||||
guint f_index;
|
||||
const gchar *module_name = peas_plugin_info_get_module_name (info);
|
||||
const gchar *ported_features[] = {
|
||||
#if CLAPPER_HAVE_MPRIS
|
||||
"clapper-mpris",
|
||||
#endif
|
||||
#if CLAPPER_HAVE_DISCOVERER
|
||||
"clapper-discoverer",
|
||||
#endif
|
||||
#if CLAPPER_HAVE_SERVER
|
||||
"clapper-server",
|
||||
#endif
|
||||
};
|
||||
|
||||
for (f_index = 0; f_index < G_N_ELEMENTS (ported_features); ++f_index) {
|
||||
if (strcmp (module_name, ported_features[f_index]) == 0) {
|
||||
GST_INFO ("Skipped \"%s\" enhancer module, since its"
|
||||
" loaded from deprecated feature object", module_name);
|
||||
g_clear_object (&info);
|
||||
}
|
||||
}
|
||||
|
||||
GST_INFO ("Clapper enhancers initialized, found: %u", n_items);
|
||||
if (!info) // cleared when exists as feature
|
||||
continue;
|
||||
#endif
|
||||
|
||||
/* Clapper supports only 1 proxy per plugin. Each plugin can
|
||||
* ship 1 class, but it can implement more than 1 interface. */
|
||||
proxy = clapper_enhancer_proxy_new_global_take ((GObject *) info);
|
||||
|
||||
/* Try to fill missing data from cache (fast).
|
||||
* Otherwise make an instance and fill missing data from it (slow). */
|
||||
if (!(filled = clapper_enhancer_proxy_fill_from_cache (proxy))) {
|
||||
GObject *enhancer;
|
||||
const GType main_types[] = { CLAPPER_TYPE_EXTRACTABLE, CLAPPER_TYPE_REACTABLE };
|
||||
guint j;
|
||||
|
||||
/* We cannot ask libpeas for "any" of our main interfaces, so try each one until found */
|
||||
for (j = 0; j < G_N_ELEMENTS (main_types); ++j) {
|
||||
if ((enhancer = clapper_enhancers_loader_create_enhancer (proxy, main_types[j]))) {
|
||||
filled = clapper_enhancer_proxy_fill_from_instance (proxy, enhancer);
|
||||
g_object_unref (enhancer);
|
||||
|
||||
clapper_enhancer_proxy_export_to_cache (proxy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (G_LIKELY (filled)) {
|
||||
GST_INFO ("Found enhancer: \"%s\" (%s)",
|
||||
clapper_enhancer_proxy_get_friendly_name (proxy),
|
||||
clapper_enhancer_proxy_get_module_name (proxy));
|
||||
clapper_enhancer_proxy_list_take_proxy (proxies, proxy);
|
||||
} else {
|
||||
GST_WARNING ("Enhancer init failed: \"%s\" (%s)",
|
||||
clapper_enhancer_proxy_get_friendly_name (proxy),
|
||||
clapper_enhancer_proxy_get_module_name (proxy));
|
||||
gst_object_unref (proxy);
|
||||
}
|
||||
}
|
||||
|
||||
clapper_enhancer_proxy_list_sort (proxies);
|
||||
|
||||
GST_INFO ("Clapper enhancers initialized, found: %u",
|
||||
clapper_enhancer_proxy_list_get_n_proxies (proxies));
|
||||
|
||||
g_free (custom_path);
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
_is_name_listed (const gchar *name, const gchar *list_str)
|
||||
{
|
||||
gsize name_len = strlen (name);
|
||||
guint i = 0;
|
||||
|
||||
while (list_str[i] != '\0') {
|
||||
guint end = i;
|
||||
|
||||
while (list_str[end] != ';' && list_str[end] != '\0')
|
||||
++end;
|
||||
|
||||
/* Compare letters count until separator and prefix of whole string */
|
||||
if (end - i == name_len && g_str_has_prefix (list_str + i, name))
|
||||
return TRUE;
|
||||
|
||||
i = end;
|
||||
|
||||
/* Move to the next letter after ';' */
|
||||
if (list_str[i] != '\0')
|
||||
++i;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancers_loader_get_info:
|
||||
* @iface_type: an interface #GType
|
||||
* @scheme: an URI scheme
|
||||
* @host: (nullable): an URI host
|
||||
*
|
||||
* Returns: (transfer full) (nullable): available #PeasPluginInfo or %NULL.
|
||||
*/
|
||||
static PeasPluginInfo *
|
||||
clapper_enhancers_loader_get_info (GType iface_type, const gchar *scheme, const gchar *host)
|
||||
{
|
||||
GListModel *list = (GListModel *) _engine;
|
||||
PeasPluginInfo *found_info = NULL;
|
||||
guint i, n_plugins = g_list_model_get_n_items (list);
|
||||
const gchar *iface_name;
|
||||
gboolean is_https;
|
||||
|
||||
if (n_plugins == 0) {
|
||||
GST_INFO ("No Clapper enhancers found");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type);
|
||||
|
||||
/* Strip common subdomains, so plugins do not
|
||||
* have to list all combinations */
|
||||
if (host) {
|
||||
if (g_str_has_prefix (host, "www."))
|
||||
host += 4;
|
||||
else if (g_str_has_prefix (host, "m."))
|
||||
host += 2;
|
||||
}
|
||||
|
||||
GST_INFO ("Enhancer check, iface: %s, scheme: %s, host: %s",
|
||||
iface_name, scheme, GST_STR_NULL (host));
|
||||
|
||||
is_https = (g_str_has_prefix (scheme, "http")
|
||||
&& (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0')));
|
||||
|
||||
if (!host && is_https)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < n_plugins; ++i) {
|
||||
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i);
|
||||
const gchar *iface_names, *schemes, *hosts;
|
||||
|
||||
if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) {
|
||||
GST_DEBUG ("Skipping enhancer without interfaces: %s", peas_plugin_info_get_name (info));
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
if (!_is_name_listed (iface_name, iface_names)) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) {
|
||||
GST_DEBUG ("Skipping enhancer without schemes: %s", peas_plugin_info_get_name (info));
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
if (!_is_name_listed (scheme, schemes)) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
if (is_https) {
|
||||
if (!(hosts = peas_plugin_info_get_external_data (info, ENHANCER_HOSTS))) {
|
||||
GST_DEBUG ("Skipping enhancer without hosts: %s", peas_plugin_info_get_name (info));
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
if (!_is_name_listed (host, hosts)) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
found_info = info;
|
||||
break;
|
||||
}
|
||||
|
||||
return found_info;
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancers_loader_has_enhancers:
|
||||
* @iface_type: an interface #GType
|
||||
*
|
||||
* Check if any enhancer implementing given interface type is available.
|
||||
*
|
||||
* Returns: whether any valid enhancer was found.
|
||||
*/
|
||||
gboolean
|
||||
clapper_enhancers_loader_has_enhancers (GType iface_type)
|
||||
{
|
||||
GListModel *list = (GListModel *) _engine;
|
||||
const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type);
|
||||
guint i, n_plugins;
|
||||
|
||||
GST_DEBUG ("Checking for any enhancers of type: \"%s\"", iface_name);
|
||||
|
||||
n_plugins = g_list_model_get_n_items (list);
|
||||
|
||||
for (i = 0; i < n_plugins; ++i) {
|
||||
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i);
|
||||
const gchar *iface_names;
|
||||
|
||||
if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
if (!_is_name_listed (iface_name, iface_names)) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Additional validation */
|
||||
if (!peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES)
|
||||
|| !peas_plugin_info_get_external_data (info, ENHANCER_HOSTS)) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
|
||||
GST_DEBUG ("Found valid enhancers of type: \"%s\"", iface_name);
|
||||
g_object_unref (info);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GST_DEBUG ("No available enhancers of type: \"%s\"", iface_name);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancers_loader_get_schemes:
|
||||
* @iface_type: an interface #GType
|
||||
*
|
||||
* Get all supported schemes for a given interface type.
|
||||
* The returned array consists of unique strings (no duplicates).
|
||||
*
|
||||
* Returns: (transfer full): all supported schemes by enhancers of type.
|
||||
*/
|
||||
gchar **
|
||||
clapper_enhancers_loader_get_schemes (GType iface_type)
|
||||
{
|
||||
GListModel *list = (GListModel *) _engine;
|
||||
GSList *found_schemes = NULL, *fs;
|
||||
const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type);
|
||||
gchar **schemes_strv;
|
||||
guint i, n_plugins, n_schemes;
|
||||
|
||||
GST_DEBUG ("Checking supported URI schemes for \"%s\"", iface_name);
|
||||
|
||||
n_plugins = g_list_model_get_n_items (list);
|
||||
|
||||
for (i = 0; i < n_plugins; ++i) {
|
||||
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i);
|
||||
const gchar *iface_names, *schemes;
|
||||
gchar **tmp_strv;
|
||||
gint j;
|
||||
|
||||
if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
if (!_is_name_listed (iface_name, iface_names)) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) {
|
||||
g_object_unref (info);
|
||||
continue;
|
||||
}
|
||||
|
||||
tmp_strv = g_strsplit (schemes, ";", 0);
|
||||
|
||||
for (j = 0; tmp_strv[j]; ++j) {
|
||||
const gchar *scheme = tmp_strv[j];
|
||||
|
||||
if (!found_schemes || !g_slist_find_custom (found_schemes,
|
||||
scheme, (GCompareFunc) strcmp)) {
|
||||
found_schemes = g_slist_append (found_schemes, g_strdup (scheme));
|
||||
GST_INFO ("Found supported URI scheme: %s", scheme);
|
||||
}
|
||||
}
|
||||
|
||||
g_strfreev (tmp_strv);
|
||||
g_object_unref (info);
|
||||
}
|
||||
|
||||
n_schemes = g_slist_length (found_schemes);
|
||||
schemes_strv = g_new0 (gchar *, n_schemes + 1);
|
||||
|
||||
fs = found_schemes;
|
||||
for (i = 0; i < n_schemes; ++i) {
|
||||
schemes_strv[i] = fs->data;
|
||||
fs = fs->next;
|
||||
}
|
||||
|
||||
GST_DEBUG ("Total found URI schemes: %u", n_schemes);
|
||||
|
||||
/* Since string pointers were taken,
|
||||
* free list without content */
|
||||
g_slist_free (found_schemes);
|
||||
|
||||
return schemes_strv;
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancers_loader_check:
|
||||
* clapper_enhancers_loader_create_enhancer:
|
||||
* @iface_type: a requested #GType
|
||||
* @scheme: an URI scheme
|
||||
* @host: (nullable): an URI host
|
||||
* @name: (out) (optional) (transfer none): return location for found enhancer name
|
||||
* @info: a #PeasPluginInfo
|
||||
*
|
||||
* Checks if any enhancer can handle @uri without initializing loader
|
||||
* or creating enhancer instance, thus this can be used freely from any thread.
|
||||
*
|
||||
* Returns: whether enhancer for given scheme and host is available.
|
||||
*/
|
||||
gboolean
|
||||
clapper_enhancers_loader_check (GType iface_type,
|
||||
const gchar *scheme, const gchar *host, const gchar **name)
|
||||
{
|
||||
PeasPluginInfo *info;
|
||||
|
||||
if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) {
|
||||
if (name)
|
||||
*name = peas_plugin_info_get_name (info);
|
||||
|
||||
g_object_unref (info);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancers_loader_create_enhancer_for_uri:
|
||||
* @iface_type: a requested #GType
|
||||
* @uri: a #GUri
|
||||
*
|
||||
* Creates a new enhancer object for given URI.
|
||||
* Creates a new enhancer object using @info.
|
||||
*
|
||||
* Enhancer should only be created and used within single thread.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): a new enhancer instance.
|
||||
*/
|
||||
GObject *
|
||||
clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri)
|
||||
clapper_enhancers_loader_create_enhancer (ClapperEnhancerProxy *proxy, GType iface_type)
|
||||
{
|
||||
GObject *enhancer = NULL;
|
||||
PeasPluginInfo *info;
|
||||
const gchar *scheme = g_uri_get_scheme (uri);
|
||||
const gchar *host = g_uri_get_host (uri);
|
||||
PeasPluginInfo *info = (PeasPluginInfo *) clapper_enhancer_proxy_get_peas_info (proxy);
|
||||
|
||||
if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) {
|
||||
g_mutex_lock (&load_lock);
|
||||
g_mutex_lock (&load_lock);
|
||||
|
||||
if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) {
|
||||
GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_name (info));
|
||||
} else if (!peas_engine_provides_extension (_engine, info, iface_type)) {
|
||||
GST_ERROR ("No \"%s\" enhancer in plugin: %s", ENHANCER_IFACE_NAME_FROM_TYPE (iface_type),
|
||||
peas_plugin_info_get_name (info));
|
||||
} else {
|
||||
enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL);
|
||||
}
|
||||
|
||||
g_mutex_unlock (&load_lock);
|
||||
g_object_unref (info);
|
||||
if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) {
|
||||
GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_module_name (info));
|
||||
} else if (!peas_engine_provides_extension (_engine, info, iface_type)) {
|
||||
GST_LOG ("No \"%s\" enhancer in module: %s", g_type_name (iface_type),
|
||||
peas_plugin_info_get_module_name (info));
|
||||
} else {
|
||||
enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL);
|
||||
}
|
||||
|
||||
g_mutex_unlock (&load_lock);
|
||||
|
||||
return enhancer;
|
||||
}
|
||||
|
@@ -127,4 +127,47 @@ typedef enum
|
||||
CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT,
|
||||
} ClapperDiscovererDiscoveryMode;
|
||||
|
||||
/* NOTE: GStreamer uses param flags 8-16, so start with 17. */
|
||||
/**
|
||||
* ClapperEnhancerParamFlags:
|
||||
* @CLAPPER_ENHANCER_PARAM_GLOBAL: Use this flag for enhancer properties that should have global access scope.
|
||||
* Such are meant for application `USER` to configure.
|
||||
* @CLAPPER_ENHANCER_PARAM_LOCAL: Use this flag for enhancer properties that should have local access scope.
|
||||
* Such are meant for `APPLICATION` to configure.
|
||||
* @CLAPPER_ENHANCER_PARAM_FILEPATH: Use this flag for enhancer properties that store string with a file path.
|
||||
* Applications can use this as a hint to show file selection instead of a text entry.
|
||||
* @CLAPPER_ENHANCER_PARAM_DIRPATH: Use this flag for enhancer properties that store string with a directory path.
|
||||
* Applications can use this as a hint to show directory selection instead of a text entry.
|
||||
*
|
||||
* Additional [flags@GObject.ParamFlags] to be set in enhancer plugins implementations.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
CLAPPER_ENHANCER_PARAM_GLOBAL = 1 << 17,
|
||||
CLAPPER_ENHANCER_PARAM_LOCAL = 1 << 18,
|
||||
CLAPPER_ENHANCER_PARAM_FILEPATH = 1 << 19,
|
||||
CLAPPER_ENHANCER_PARAM_DIRPATH = 1 << 20,
|
||||
} ClapperEnhancerParamFlags;
|
||||
|
||||
/**
|
||||
* ClapperReactableItemUpdatedFlags:
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_TITLE: Media item title was updated.
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_DURATION: Media item duration was updated.
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_TIMELINE: Media item timeline was updated.
|
||||
* @CLAPPER_REACTABLE_ITEM_UPDATED_TAGS: Media item tags were updated.
|
||||
*
|
||||
* Flags informing which properties were updated within [class@Clapper.MediaItem].
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_TITLE = 1 << 0,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_DURATION = 1 << 1,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_TIMELINE = 1 << 2,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_TAGS = 1 << 3,
|
||||
} ClapperReactableItemUpdatedFlags;
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -30,6 +30,8 @@
|
||||
* virtual functions logic, while for controlling playback implementation
|
||||
* may call [method@Gst.Object.get_parent] to acquire a weak reference on
|
||||
* a parent [class@Clapper.Player] object feature was added to.
|
||||
*
|
||||
* Deprecated: 0.10: Use [iface@Clapper.Reactable] instead.
|
||||
*/
|
||||
|
||||
#include "clapper-feature.h"
|
||||
|
@@ -37,7 +37,7 @@ G_BEGIN_DECLS
|
||||
#define CLAPPER_TYPE_FEATURE (clapper_feature_get_type())
|
||||
#define CLAPPER_FEATURE_CAST(obj) ((ClapperFeature *)(obj))
|
||||
|
||||
CLAPPER_API
|
||||
CLAPPER_DEPRECATED_FOR(ClapperReactable)
|
||||
G_DECLARE_DERIVABLE_TYPE (ClapperFeature, clapper_feature, CLAPPER, FEATURE, GstObject)
|
||||
|
||||
/**
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include <gst/tag/tag.h>
|
||||
|
||||
#include "clapper-harvest.h"
|
||||
#include "clapper-enhancer-proxy.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@@ -32,4 +33,10 @@ ClapperHarvest * clapper_harvest_new (void);
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_harvest_unpack (ClapperHarvest *harvest, GstBuffer **buffer, gsize *buf_size, GstCaps **caps, GstTagList **tags, GstToc **toc, GstStructure **headers);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_harvest_fill_from_cache (ClapperHarvest *harvest, ClapperEnhancerProxy *proxy, const GstStructure *config, GUri *uri);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_harvest_export_to_cache (ClapperHarvest *harvest, ClapperEnhancerProxy *proxy, const GstStructure *config, GUri *uri);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -31,7 +31,11 @@
|
||||
* https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2867
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "clapper-harvest-private.h"
|
||||
#include "clapper-cache-private.h"
|
||||
#include "clapper-utils.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_harvest_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
@@ -50,6 +54,8 @@ struct _ClapperHarvest
|
||||
|
||||
guint16 n_chapters;
|
||||
guint16 n_tracks;
|
||||
|
||||
gint64 exp_epoch;
|
||||
};
|
||||
|
||||
#define parent_class clapper_harvest_parent_class
|
||||
@@ -119,6 +125,302 @@ clapper_harvest_unpack (ClapperHarvest *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Custom implementation due to the lack of TOC serialization in GStreamer */
|
||||
static void
|
||||
_harvest_fill_toc_from_cache (ClapperHarvest *self, const gchar **data)
|
||||
{
|
||||
guint i, n_entries;
|
||||
|
||||
n_entries = clapper_cache_read_uint (data);
|
||||
for (i = 0; i < n_entries; ++i) {
|
||||
guint j, n_subentries;
|
||||
|
||||
n_subentries = clapper_cache_read_uint (data);
|
||||
for (j = 0; j < n_subentries; ++j) {
|
||||
GstTocEntryType type;
|
||||
const gchar *title;
|
||||
gdouble start, end;
|
||||
|
||||
type = (GstTocEntryType) clapper_cache_read_int (data);
|
||||
title = clapper_cache_read_string (data);
|
||||
start = clapper_cache_read_double (data);
|
||||
end = clapper_cache_read_double (data);
|
||||
|
||||
clapper_harvest_toc_add (self, type, title, start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_harvest_store_toc_to_cache (ClapperHarvest *self, GByteArray *bytes)
|
||||
{
|
||||
GList *list = NULL, *el;
|
||||
guint n_entries = 0;
|
||||
|
||||
if (self->toc) {
|
||||
list = gst_toc_get_entries (self->toc);
|
||||
n_entries = g_list_length (list);
|
||||
}
|
||||
clapper_cache_store_uint (bytes, n_entries);
|
||||
|
||||
for (el = list; el; el = g_list_next (el)) {
|
||||
const GstTocEntry *entry = (const GstTocEntry *) el->data;
|
||||
GList *subentries, *sub_el;
|
||||
guint n_subentries;
|
||||
|
||||
subentries = gst_toc_entry_get_sub_entries (entry);
|
||||
n_subentries = g_list_length (subentries);
|
||||
|
||||
clapper_cache_store_uint (bytes, n_subentries);
|
||||
|
||||
for (sub_el = subentries; sub_el; sub_el = g_list_next (sub_el)) {
|
||||
const GstTocEntry *subentry = (const GstTocEntry *) sub_el->data;
|
||||
GstTagList *tags;
|
||||
gint64 start = 0, end = 0;
|
||||
gdouble start_dbl, end_dbl;
|
||||
const gchar *title = NULL;
|
||||
|
||||
clapper_cache_store_int (bytes, (gint) gst_toc_entry_get_entry_type (subentry));
|
||||
|
||||
if ((tags = gst_toc_entry_get_tags (subentry)))
|
||||
gst_tag_list_peek_string_index (tags, GST_TAG_TITLE, 0, &title);
|
||||
|
||||
clapper_cache_store_string (bytes, title);
|
||||
|
||||
gst_toc_entry_get_start_stop_times (subentry, &start, &end);
|
||||
start_dbl = ((gdouble) start) / GST_SECOND;
|
||||
end_dbl = (end >= 0) ? ((gdouble) end) / GST_SECOND : -1;
|
||||
|
||||
clapper_cache_store_double (bytes, start_dbl);
|
||||
clapper_cache_store_double (bytes, end_dbl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline gchar *
|
||||
_build_cache_filename (ClapperEnhancerProxy *proxy, GUri *uri)
|
||||
{
|
||||
gchar *uri_str = g_uri_to_string (uri);
|
||||
gchar name[15];
|
||||
|
||||
g_snprintf (name, sizeof (name), "%u.bin", g_str_hash (uri_str));
|
||||
g_free (uri_str);
|
||||
|
||||
return g_build_filename (g_get_user_cache_dir (), CLAPPER_API_NAME,
|
||||
"enhancers", clapper_enhancer_proxy_get_module_name (proxy),
|
||||
"harvests", name, NULL);
|
||||
}
|
||||
|
||||
/* NOTE: On failure, this function must not modify harvest! */
|
||||
gboolean
|
||||
clapper_harvest_fill_from_cache (ClapperHarvest *self, ClapperEnhancerProxy *proxy,
|
||||
const GstStructure *config, GUri *uri)
|
||||
{
|
||||
GMappedFile *mapped_file;
|
||||
GstStructure *config_cached = NULL;
|
||||
GError *error = NULL;
|
||||
gchar *filename;
|
||||
const gchar *data, *read_str;
|
||||
const guint8 *buf_data;
|
||||
guint8 *buf_copy;
|
||||
gsize buf_size;
|
||||
gint64 epoch_cached, epoch_now = 0;
|
||||
gdouble exp_seconds;
|
||||
gboolean changed, read_ok = FALSE;
|
||||
|
||||
filename = _build_cache_filename (proxy, uri);
|
||||
GST_DEBUG_OBJECT (self, "Importing harvest from cache file: \"%s\"", filename);
|
||||
mapped_file = clapper_cache_open (filename, &data, &error);
|
||||
g_free (filename);
|
||||
|
||||
if (!mapped_file) {
|
||||
/* No error if cache disabled or version mismatch */
|
||||
if (error) {
|
||||
if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT)
|
||||
GST_DEBUG_OBJECT (self, "No cached harvest found");
|
||||
else
|
||||
GST_ERROR_OBJECT (self, "Could not use cached harvest, reason: %s", error->message);
|
||||
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Plugin version check */
|
||||
if (g_strcmp0 (clapper_cache_read_string (&data),
|
||||
clapper_enhancer_proxy_get_version (proxy)) != 0)
|
||||
goto finish; // no error printing here
|
||||
|
||||
if (G_LIKELY ((epoch_cached = clapper_cache_read_int64 (&data)) > 0)) {
|
||||
GDateTime *date = g_date_time_new_now_utc ();
|
||||
epoch_now = g_date_time_to_unix (date);
|
||||
g_date_time_unref (date);
|
||||
}
|
||||
|
||||
/* Check if expired */
|
||||
if ((exp_seconds = (gdouble) (epoch_cached - epoch_now)) <= 0) {
|
||||
GST_DEBUG_OBJECT (self, "Cached harvest expired"); // expiration is not an error
|
||||
goto finish;
|
||||
}
|
||||
GST_DEBUG_OBJECT (self, "Cached harvest expiration in %" CLAPPER_TIME_FORMAT,
|
||||
CLAPPER_TIME_ARGS (exp_seconds));
|
||||
|
||||
/* Read last used config to generate cache data */
|
||||
if ((read_str = clapper_cache_read_string (&data)))
|
||||
config_cached = gst_structure_from_string (read_str, NULL);
|
||||
|
||||
/* Compare used config when cache was generated to the current one */
|
||||
changed = (config_cached && config)
|
||||
? !gst_structure_is_equal (config_cached, config)
|
||||
: (config_cached != config);
|
||||
|
||||
gst_clear_structure (&config_cached);
|
||||
|
||||
if (changed) {
|
||||
GST_DEBUG_OBJECT (self, "Enhancer config differs from the last time");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Read media type */
|
||||
read_str = clapper_cache_read_string (&data);
|
||||
if (G_UNLIKELY (read_str == NULL)) {
|
||||
GST_ERROR_OBJECT (self, "Could not read media type from cache file");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Read buffer data */
|
||||
buf_data = clapper_cache_read_data (&data, &buf_size);
|
||||
if (G_UNLIKELY (buf_data == NULL)) {
|
||||
GST_ERROR_OBJECT (self, "Could not read buffer data from cache");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Fill harvest */
|
||||
buf_copy = g_memdup2 (buf_data, buf_size);
|
||||
if (!clapper_harvest_fill (self, read_str, buf_copy, buf_size))
|
||||
goto finish;
|
||||
|
||||
/* Read tags */
|
||||
read_str = clapper_cache_read_string (&data);
|
||||
if (read_str && (self->tags = gst_tag_list_new_from_string (read_str))) {
|
||||
GST_LOG_OBJECT (self, "Read %s", read_str);
|
||||
gst_tag_list_set_scope (self->tags, GST_TAG_SCOPE_GLOBAL);
|
||||
}
|
||||
|
||||
/* Read TOC */
|
||||
_harvest_fill_toc_from_cache (self, &data);
|
||||
|
||||
/* Read headers */
|
||||
read_str = clapper_cache_read_string (&data);
|
||||
if (read_str && (self->headers = gst_structure_from_string (read_str, NULL)))
|
||||
GST_LOG_OBJECT (self, "Read %s", read_str);
|
||||
|
||||
read_ok = TRUE;
|
||||
|
||||
finish:
|
||||
g_mapped_file_unref (mapped_file);
|
||||
|
||||
if (!read_ok)
|
||||
return FALSE;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Filled harvest from cache");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
clapper_harvest_export_to_cache (ClapperHarvest *self, ClapperEnhancerProxy *proxy,
|
||||
const GstStructure *config, GUri *uri)
|
||||
{
|
||||
GByteArray *bytes;
|
||||
const GstStructure *caps_structure;
|
||||
gchar *filename, *temp_str = NULL;
|
||||
gboolean data_ok = TRUE;
|
||||
|
||||
/* No caching if no expiration date set */
|
||||
if (self->exp_epoch <= 0)
|
||||
return;
|
||||
|
||||
/* Might happen if extractor extract function implementation
|
||||
* returns %TRUE without filling harvest properly */
|
||||
if (G_UNLIKELY (self->caps == NULL || self->buffer == NULL))
|
||||
return; // no data to cache
|
||||
|
||||
bytes = clapper_cache_create ();
|
||||
|
||||
/* If cache disabled */
|
||||
if (G_UNLIKELY (bytes == NULL))
|
||||
return;
|
||||
|
||||
filename = _build_cache_filename (proxy, uri);
|
||||
GST_DEBUG_OBJECT (self, "Exporting harvest to cache file: \"%s\"", filename);
|
||||
|
||||
/* Store enhancer version that generated harvest */
|
||||
clapper_cache_store_string (bytes, clapper_enhancer_proxy_get_version (proxy));
|
||||
|
||||
/* Store expiration date */
|
||||
clapper_cache_store_int64 (bytes, self->exp_epoch);
|
||||
|
||||
/* Store config used to generate harvest */
|
||||
if (config)
|
||||
temp_str = gst_structure_to_string (config);
|
||||
clapper_cache_store_string (bytes, temp_str); // NULL when no config
|
||||
g_clear_pointer (&temp_str, g_free);
|
||||
|
||||
/* Store media type */
|
||||
caps_structure = gst_caps_get_structure (self->caps, 0);
|
||||
if (G_LIKELY (caps_structure != NULL)) {
|
||||
clapper_cache_store_string (bytes, gst_structure_get_name (caps_structure));
|
||||
} else {
|
||||
GST_ERROR_OBJECT (self, "Cannot cache empty caps");
|
||||
data_ok = FALSE;
|
||||
}
|
||||
|
||||
if (G_LIKELY (data_ok)) {
|
||||
GstMemory *mem;
|
||||
GstMapInfo map_info;
|
||||
|
||||
/* Store buffer data */
|
||||
mem = gst_buffer_peek_memory (self->buffer, 0);
|
||||
if (G_LIKELY (gst_memory_map (mem, &map_info, GST_MAP_READ))) {
|
||||
clapper_cache_store_data (bytes, map_info.data, map_info.size);
|
||||
gst_memory_unmap (mem, &map_info);
|
||||
} else {
|
||||
GST_ERROR_OBJECT (self, "Could not map harvest buffer for reading");
|
||||
data_ok = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (G_LIKELY (data_ok)) {
|
||||
GError *error = NULL;
|
||||
|
||||
/* Store tags */
|
||||
if (self->tags)
|
||||
temp_str = gst_tag_list_to_string (self->tags);
|
||||
clapper_cache_store_string (bytes, temp_str);
|
||||
g_clear_pointer (&temp_str, g_free);
|
||||
|
||||
/* Store TOC */
|
||||
_harvest_store_toc_to_cache (self, bytes);
|
||||
|
||||
/* Store headers */
|
||||
if (self->headers)
|
||||
temp_str = gst_structure_to_string (self->headers);
|
||||
clapper_cache_store_string (bytes, temp_str);
|
||||
g_clear_pointer (&temp_str, g_free);
|
||||
|
||||
if (clapper_cache_write (filename, bytes, &error)) {
|
||||
GST_DEBUG_OBJECT (self, "Successfully exported harvest to cache file");
|
||||
} else if (error) {
|
||||
GST_ERROR_OBJECT (self, "Could not cache harvest, reason: %s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (filename);
|
||||
g_byte_array_free (bytes, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_harvest_fill:
|
||||
* @harvest: a #ClapperHarvest
|
||||
@@ -155,7 +457,7 @@ clapper_harvest_fill (ClapperHarvest *self, const gchar *media_type, gpointer da
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG) {
|
||||
if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_LOG) {
|
||||
gboolean is_printable = (strcmp (media_type, "application/dash+xml") == 0)
|
||||
|| (strcmp (media_type, "application/x-hls") == 0)
|
||||
|| (strcmp (media_type, "text/uri-list") == 0);
|
||||
@@ -166,7 +468,7 @@ clapper_harvest_fill (ClapperHarvest *self, const gchar *media_type, gpointer da
|
||||
data_str = g_new0 (gchar, size + 1);
|
||||
memcpy (data_str, data, size);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Filled with data:\n%s", data_str);
|
||||
GST_LOG_OBJECT (self, "Filled with data:\n%s", data_str);
|
||||
|
||||
g_free (data_str);
|
||||
}
|
||||
@@ -328,7 +630,7 @@ clapper_harvest_toc_add (ClapperHarvest *self, GstTocEntryType type,
|
||||
g_snprintf (edition, sizeof (edition), "0%i", type);
|
||||
g_snprintf (id, sizeof (id), "%s.%" G_GUINT16_FORMAT, id_prefix, nth_entry);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Inserting TOC %s: \"%s\""
|
||||
GST_LOG_OBJECT (self, "Inserting TOC %s: \"%s\""
|
||||
" (%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ")",
|
||||
id, title, start_time, end_time);
|
||||
|
||||
@@ -380,7 +682,7 @@ clapper_harvest_headers_set (ClapperHarvest *self, const gchar *key, ...)
|
||||
|
||||
while (key != NULL) {
|
||||
const gchar *val = va_arg (args, const gchar *);
|
||||
GST_DEBUG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, val);
|
||||
GST_LOG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, val);
|
||||
gst_structure_set (self->headers, key, G_TYPE_STRING, val, NULL);
|
||||
key = va_arg (args, const gchar *);
|
||||
}
|
||||
@@ -409,10 +711,70 @@ clapper_harvest_headers_set_value (ClapperHarvest *self, const gchar *key, const
|
||||
|
||||
_ensure_headers (self);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, g_value_get_string (value));
|
||||
GST_LOG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, g_value_get_string (value));
|
||||
gst_structure_set_value (self->headers, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_harvest_set_expiration_date_utc:
|
||||
* @harvest: a #ClapperHarvest
|
||||
* @date_utc: a #GDateTime in UTC time
|
||||
*
|
||||
* Set date in UTC time until harvested content is expected
|
||||
* to stay alive.
|
||||
*
|
||||
* This is used for harvest caching, so next time user requests to
|
||||
* play the same URI, recently harvested data can be reused without
|
||||
* the need to run [vfunc@Clapper.Extractable.extract] again.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void
|
||||
clapper_harvest_set_expiration_date_utc (ClapperHarvest *self, GDateTime *date_utc)
|
||||
{
|
||||
g_return_if_fail (CLAPPER_IS_HARVEST (self));
|
||||
g_return_if_fail (date_utc != NULL);
|
||||
|
||||
self->exp_epoch = g_date_time_to_unix (date_utc);
|
||||
GST_LOG_OBJECT (self, "Expiration epoch: %" G_GINT64_FORMAT, self->exp_epoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_harvest_set_expiration_seconds:
|
||||
* @harvest: a #ClapperHarvest
|
||||
* @seconds: time in seconds until expiration
|
||||
*
|
||||
* Set amount of seconds for how long harvested content is
|
||||
* expected to stay alive.
|
||||
*
|
||||
* Alternative function to [method@Clapper.Harvest.set_expiration_date_utc],
|
||||
* but takes time as number in seconds from now.
|
||||
*
|
||||
* It is safe to pass zero or negative number to this function in
|
||||
* case when calculating time manually and it already expired.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void
|
||||
clapper_harvest_set_expiration_seconds (ClapperHarvest *self, gdouble seconds)
|
||||
{
|
||||
GDateTime *date, *date_epoch;
|
||||
|
||||
g_return_if_fail (CLAPPER_IS_HARVEST (self));
|
||||
|
||||
GST_LOG_OBJECT (self, "Set expiration in %" CLAPPER_TIME_FORMAT,
|
||||
CLAPPER_TIME_ARGS (seconds));
|
||||
|
||||
date = g_date_time_new_now_utc ();
|
||||
date_epoch = g_date_time_add_seconds (date, seconds);
|
||||
g_date_time_unref (date);
|
||||
|
||||
self->exp_epoch = g_date_time_to_unix (date_epoch);
|
||||
g_date_time_unref (date_epoch);
|
||||
|
||||
GST_LOG_OBJECT (self, "Expiration epoch: %" G_GINT64_FORMAT, self->exp_epoch);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_harvest_init (ClapperHarvest *self)
|
||||
{
|
||||
|
@@ -61,4 +61,10 @@ void clapper_harvest_headers_set (ClapperHarvest *harvest, const gchar *key, ...
|
||||
CLAPPER_API
|
||||
void clapper_harvest_headers_set_value (ClapperHarvest *harvest, const gchar *key, const GValue *value);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_harvest_set_expiration_date_utc (ClapperHarvest *harvest, GDateTime *date_utc);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_harvest_set_expiration_seconds (ClapperHarvest *harvest, gdouble seconds);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include "clapper-media-item.h"
|
||||
#include "clapper-player-private.h"
|
||||
#include "clapper-player.h"
|
||||
#include "clapper-app-bus-private.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
@@ -30,6 +30,7 @@
|
||||
#include "clapper-timeline-private.h"
|
||||
#include "clapper-player-private.h"
|
||||
#include "clapper-playbin-bus-private.h"
|
||||
#include "clapper-reactables-manager-private.h"
|
||||
#include "clapper-features-manager-private.h"
|
||||
#include "clapper-utils-private.h"
|
||||
|
||||
@@ -43,6 +44,7 @@ struct _ClapperMediaItem
|
||||
gchar *uri;
|
||||
gchar *suburi;
|
||||
|
||||
GstTagList *tags;
|
||||
ClapperTimeline *timeline;
|
||||
|
||||
guint id;
|
||||
@@ -56,6 +58,13 @@ struct _ClapperMediaItem
|
||||
gboolean used;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ClapperMediaItem *item;
|
||||
gboolean changed;
|
||||
gboolean from_user;
|
||||
} ClapperMediaItemTagIterData;
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
@@ -63,6 +72,7 @@ enum
|
||||
PROP_URI,
|
||||
PROP_SUBURI,
|
||||
PROP_CACHE_LOCATION,
|
||||
PROP_TAGS,
|
||||
PROP_TITLE,
|
||||
PROP_CONTAINER_FORMAT,
|
||||
PROP_DURATION,
|
||||
@@ -111,8 +121,8 @@ clapper_media_item_new (const gchar *uri)
|
||||
|
||||
/* FIXME: Set initial container format from file extension parsing */
|
||||
|
||||
GST_TRACE_OBJECT (item, "New media item, ID: %u, URI: %s, title: %s",
|
||||
item->id, item->uri, item->title);
|
||||
GST_TRACE_OBJECT (item, "New media item, ID: %u, URI: \"%s\", title: \"%s\"",
|
||||
item->id, item->uri, GST_STR_NULL (item->title));
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -258,27 +268,6 @@ clapper_media_item_get_suburi (ClapperMediaItem *self)
|
||||
return suburi;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_media_item_take_title (ClapperMediaItem *self, gchar *title,
|
||||
ClapperAppBus *app_bus)
|
||||
{
|
||||
gboolean changed;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
if ((changed = g_strcmp0 (self->title, title) != 0)) {
|
||||
g_free (self->title);
|
||||
self->title = title;
|
||||
}
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (changed)
|
||||
clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_TITLE]);
|
||||
else
|
||||
g_free (title);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_media_item_get_title:
|
||||
* @item: a #ClapperMediaItem
|
||||
@@ -305,25 +294,24 @@ clapper_media_item_get_title (ClapperMediaItem *self)
|
||||
return title;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_media_item_take_container_format (ClapperMediaItem *self, gchar *container_format,
|
||||
ClapperAppBus *app_bus)
|
||||
static inline gboolean
|
||||
_refresh_tag_prop_unlocked (ClapperMediaItem *self, const gchar *tag,
|
||||
gboolean from_user, gchar **tag_ptr)
|
||||
{
|
||||
gboolean changed;
|
||||
const gchar *string;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
if ((changed = g_strcmp0 (self->container_format, container_format) != 0)) {
|
||||
g_free (self->container_format);
|
||||
self->container_format = container_format;
|
||||
}
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
if ((*tag_ptr && from_user) // if already set, user cannot modify it
|
||||
|| !gst_tag_list_peek_string_index (self->tags, tag, 0, &string) // guarantees non-empty string
|
||||
|| (g_strcmp0 (*tag_ptr, string) == 0))
|
||||
return FALSE;
|
||||
|
||||
if (changed)
|
||||
clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CONTAINER_FORMAT]);
|
||||
else
|
||||
g_free (container_format);
|
||||
GST_LOG_OBJECT (self, "Tag prop \"%s\" update: \"%s\" -> \"%s\"",
|
||||
tag, GST_STR_NULL (*tag_ptr), string);
|
||||
|
||||
return changed;
|
||||
g_free (*tag_ptr);
|
||||
*tag_ptr = g_strdup (string);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,6 +321,8 @@ clapper_media_item_take_container_format (ClapperMediaItem *self, gchar *contain
|
||||
* Get media item container format.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): media container format.
|
||||
*
|
||||
* Deprecated: 0.10: Get `container-format` from [property@Clapper.MediaItem:tags] instead.
|
||||
*/
|
||||
gchar *
|
||||
clapper_media_item_get_container_format (ClapperMediaItem *self)
|
||||
@@ -389,11 +379,214 @@ clapper_media_item_get_duration (ClapperMediaItem *self)
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_media_item_get_tags:
|
||||
* @item: a #ClapperMediaItem
|
||||
*
|
||||
* Get readable list of tags stored in media item.
|
||||
*
|
||||
* Returns: (transfer full): a #GstTagList.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
GstTagList *
|
||||
clapper_media_item_get_tags (ClapperMediaItem *self)
|
||||
{
|
||||
GstTagList *tags = NULL;
|
||||
|
||||
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
tags = gst_tag_list_ref (self->tags);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
static void
|
||||
_tags_replace_func (const GstTagList *tags, const gchar *tag, ClapperMediaItemTagIterData *data)
|
||||
{
|
||||
ClapperMediaItem *self = data->item;
|
||||
guint index = 0;
|
||||
gboolean replace = FALSE;
|
||||
|
||||
while (TRUE) {
|
||||
const GValue *old_value = gst_tag_list_get_value_index (self->tags, tag, index);
|
||||
const GValue *new_value = gst_tag_list_get_value_index (tags, tag, index);
|
||||
|
||||
/* Number of old values is the same or greater and
|
||||
* all values until this iteration were the same */
|
||||
if (!new_value)
|
||||
break;
|
||||
|
||||
/* A wild new tag appeared */
|
||||
if (!old_value) {
|
||||
replace = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Users can only set non-existing tags */
|
||||
if (data->from_user)
|
||||
break;
|
||||
|
||||
/* Check with tolerance for doubles */
|
||||
if (G_VALUE_TYPE (old_value) == G_TYPE_DOUBLE
|
||||
&& G_VALUE_TYPE (new_value) == G_TYPE_DOUBLE) {
|
||||
gdouble old_dbl, new_dbl;
|
||||
|
||||
old_dbl = g_value_get_double (old_value);
|
||||
new_dbl = g_value_get_double (new_value);
|
||||
|
||||
if ((replace = !G_APPROX_VALUE (old_dbl, new_dbl, FLT_EPSILON)))
|
||||
break;
|
||||
} else if (gst_value_compare (old_value, new_value) != GST_VALUE_EQUAL) {
|
||||
replace = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
if (replace) {
|
||||
const GValue *value;
|
||||
index = 0;
|
||||
|
||||
GST_LOG_OBJECT (self, "Replacing \"%s\" tag value", tag);
|
||||
|
||||
/* Ensure writable, but only when replacing something */
|
||||
if (!data->changed) {
|
||||
self->tags = gst_tag_list_make_writable (self->tags);
|
||||
data->changed = TRUE;
|
||||
}
|
||||
|
||||
/* Replace first tag value (so it becomes sole member) */
|
||||
value = gst_tag_list_get_value_index (tags, tag, index);
|
||||
gst_tag_list_add_value (self->tags, GST_TAG_MERGE_REPLACE, tag, value);
|
||||
|
||||
/* Append any remaining tags (so next time we iterate indexes will match) */
|
||||
while ((value = gst_tag_list_get_value_index (tags, tag, ++index)))
|
||||
gst_tag_list_add_value (self->tags, GST_TAG_MERGE_APPEND, tag, value);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_media_item_insert_tags_internal (ClapperMediaItem *self, const GstTagList *tags,
|
||||
ClapperAppBus *app_bus, gboolean from_user, ClapperReactableItemUpdatedFlags *flags)
|
||||
{
|
||||
ClapperMediaItemTagIterData data;
|
||||
gboolean title_changed = FALSE, cont_changed = FALSE;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
data.item = self;
|
||||
data.changed = FALSE;
|
||||
data.from_user = from_user;
|
||||
|
||||
if (G_LIKELY (tags != self->tags))
|
||||
gst_tag_list_foreach (tags, (GstTagForeachFunc) _tags_replace_func, &data);
|
||||
|
||||
if (data.changed) {
|
||||
*flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TAGS;
|
||||
|
||||
if ((title_changed = _refresh_tag_prop_unlocked (self, GST_TAG_TITLE,
|
||||
from_user, &self->title))) {
|
||||
*flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TITLE;
|
||||
}
|
||||
cont_changed = _refresh_tag_prop_unlocked (self, GST_TAG_CONTAINER_FORMAT,
|
||||
from_user, &self->container_format);
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (!data.changed)
|
||||
return FALSE;
|
||||
|
||||
if (app_bus) {
|
||||
GstObject *src = GST_OBJECT_CAST (self);
|
||||
|
||||
clapper_app_bus_post_prop_notify (app_bus, src, param_specs[PROP_TAGS]);
|
||||
|
||||
if (title_changed)
|
||||
clapper_app_bus_post_prop_notify (app_bus, src, param_specs[PROP_TITLE]);
|
||||
if (cont_changed)
|
||||
clapper_app_bus_post_prop_notify (app_bus, src, param_specs[PROP_CONTAINER_FORMAT]);
|
||||
} else {
|
||||
GObject *src = G_OBJECT (self);
|
||||
|
||||
clapper_utils_prop_notify_on_main_sync (src, param_specs[PROP_TAGS]);
|
||||
|
||||
if (title_changed)
|
||||
clapper_utils_prop_notify_on_main_sync (src, param_specs[PROP_TITLE]);
|
||||
if (cont_changed)
|
||||
clapper_utils_prop_notify_on_main_sync (src, param_specs[PROP_CONTAINER_FORMAT]);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_media_item_populate_tags:
|
||||
* @item: a #ClapperMediaItem
|
||||
* @tags: a #GstTagList of GLOBAL scope
|
||||
*
|
||||
* Populate non-existing tags in @item tag list.
|
||||
*
|
||||
* Passed @tags must use [enum@Gst.TagScope.GLOBAL] scope.
|
||||
*
|
||||
* Note that tags are automatically determined during media playback
|
||||
* and those take precedence. This function can be useful if an app can
|
||||
* determine some tags that are not in media metadata or for filling
|
||||
* item with some initial/cached tags to display in UI before playback.
|
||||
*
|
||||
* When a tag already exists in the tag list (was populated) this
|
||||
* function will not overwrite it. If you really need to permanently
|
||||
* override some tags in media, you can use `taginject` element as
|
||||
* player video/audio filter instead.
|
||||
*
|
||||
* Returns: whether at least one tag got updated.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
gboolean
|
||||
clapper_media_item_populate_tags (ClapperMediaItem *self, const GstTagList *tags)
|
||||
{
|
||||
ClapperPlayer *player;
|
||||
ClapperAppBus *app_bus = NULL;
|
||||
ClapperReactableItemUpdatedFlags flags = 0;
|
||||
gboolean changed;
|
||||
|
||||
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), FALSE);
|
||||
g_return_val_if_fail (tags != NULL, FALSE);
|
||||
|
||||
if (G_UNLIKELY (gst_tag_list_get_scope (tags) != GST_TAG_SCOPE_GLOBAL)) {
|
||||
g_warning ("Cannot populate media item tags using a list with non-global tag scope");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self))))
|
||||
app_bus = player->app_bus;
|
||||
|
||||
changed = clapper_media_item_insert_tags_internal (self, tags, app_bus, TRUE, &flags);
|
||||
|
||||
if (changed && player) {
|
||||
ClapperFeaturesManager *features_manager;
|
||||
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags);
|
||||
if ((features_manager = clapper_player_get_features_manager (player)))
|
||||
clapper_features_manager_trigger_item_updated (features_manager, self);
|
||||
}
|
||||
|
||||
gst_clear_object (&player);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_media_item_get_timeline:
|
||||
* @item: a #ClapperMediaItem
|
||||
*
|
||||
* Get the [class@Clapper.Timeline] assosiated with @item.
|
||||
* Get the [class@Clapper.Timeline] associated with @item.
|
||||
*
|
||||
* Returns: (transfer none): a #ClapperTimeline of item.
|
||||
*/
|
||||
@@ -405,21 +598,6 @@ clapper_media_item_get_timeline (ClapperMediaItem *self)
|
||||
return self->timeline;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_media_item_update_from_container_tags (ClapperMediaItem *self, const GstTagList *tags,
|
||||
ClapperAppBus *app_bus)
|
||||
{
|
||||
gchar *string = NULL;
|
||||
gboolean changed = FALSE;
|
||||
|
||||
if (gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &string))
|
||||
changed |= clapper_media_item_take_container_format (self, string, app_bus);
|
||||
if (gst_tag_list_get_string (tags, GST_TAG_TITLE, &string))
|
||||
changed |= clapper_media_item_take_title (self, string, app_bus);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void
|
||||
clapper_media_item_update_from_tag_list (ClapperMediaItem *self, const GstTagList *tags,
|
||||
ClapperPlayer *player)
|
||||
@@ -427,11 +605,14 @@ clapper_media_item_update_from_tag_list (ClapperMediaItem *self, const GstTagLis
|
||||
GstTagScope scope = gst_tag_list_get_scope (tags);
|
||||
|
||||
if (scope == GST_TAG_SCOPE_GLOBAL) {
|
||||
gboolean changed = clapper_media_item_update_from_container_tags (self, tags, player->app_bus);
|
||||
ClapperReactableItemUpdatedFlags flags = 0;
|
||||
gboolean changed = clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE, &flags);
|
||||
|
||||
if (changed) {
|
||||
ClapperFeaturesManager *features_manager;
|
||||
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags);
|
||||
if ((features_manager = clapper_player_get_features_manager (player)))
|
||||
clapper_features_manager_trigger_item_updated (features_manager, self);
|
||||
}
|
||||
@@ -444,6 +625,7 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco
|
||||
ClapperPlayer *player;
|
||||
GstDiscovererStreamInfo *sinfo;
|
||||
GstClockTime duration;
|
||||
ClapperReactableItemUpdatedFlags flags = 0;
|
||||
gdouble val_dbl;
|
||||
gboolean changed = FALSE;
|
||||
|
||||
@@ -459,7 +641,7 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco
|
||||
GstDiscovererContainerInfo *cinfo = (GstDiscovererContainerInfo *) sinfo;
|
||||
|
||||
if ((tags = gst_discoverer_container_info_get_tags (cinfo)))
|
||||
changed |= clapper_media_item_update_from_container_tags (self, tags, player->app_bus);
|
||||
changed |= clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE, &flags);
|
||||
}
|
||||
gst_discoverer_stream_info_unref (sinfo);
|
||||
}
|
||||
@@ -470,11 +652,16 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco
|
||||
duration = 0;
|
||||
|
||||
val_dbl = (gdouble) duration / GST_SECOND;
|
||||
changed |= clapper_media_item_set_duration (self, val_dbl, player->app_bus);
|
||||
if (clapper_media_item_set_duration (self, val_dbl, player->app_bus)) {
|
||||
changed = TRUE;
|
||||
flags |= CLAPPER_REACTABLE_ITEM_UPDATED_DURATION;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
ClapperFeaturesManager *features_manager;
|
||||
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags);
|
||||
if ((features_manager = clapper_player_get_features_manager (player)))
|
||||
clapper_features_manager_trigger_item_updated (features_manager, self);
|
||||
}
|
||||
@@ -542,6 +729,9 @@ clapper_media_item_get_used (ClapperMediaItem *self)
|
||||
static void
|
||||
clapper_media_item_init (ClapperMediaItem *self)
|
||||
{
|
||||
self->tags = gst_tag_list_new_empty ();
|
||||
gst_tag_list_set_scope (self->tags, GST_TAG_SCOPE_GLOBAL);
|
||||
|
||||
self->timeline = clapper_timeline_new ();
|
||||
gst_object_set_parent (GST_OBJECT_CAST (self->timeline), GST_OBJECT_CAST (self));
|
||||
}
|
||||
@@ -571,6 +761,8 @@ clapper_media_item_finalize (GObject *object)
|
||||
g_free (self->title);
|
||||
g_free (self->container_format);
|
||||
|
||||
gst_tag_list_unref (self->tags);
|
||||
|
||||
gst_object_unparent (GST_OBJECT_CAST (self->timeline));
|
||||
gst_object_unref (self->timeline);
|
||||
|
||||
@@ -617,6 +809,9 @@ clapper_media_item_get_property (GObject *object, guint prop_id,
|
||||
case PROP_SUBURI:
|
||||
g_value_take_string (value, clapper_media_item_get_suburi (self));
|
||||
break;
|
||||
case PROP_TAGS:
|
||||
g_value_take_boxed (value, clapper_media_item_get_tags (self));
|
||||
break;
|
||||
case PROP_TITLE:
|
||||
g_value_take_string (value, clapper_media_item_get_title (self));
|
||||
break;
|
||||
@@ -686,10 +881,27 @@ clapper_media_item_class_init (ClapperMediaItemClass *klass)
|
||||
NULL, NULL, NULL,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperMediaItem:tags:
|
||||
*
|
||||
* A readable list of tags stored in media item.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
param_specs[PROP_TAGS] = g_param_spec_boxed ("tags",
|
||||
NULL, NULL, GST_TYPE_TAG_LIST,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/* FIXME: 1.0: Consider rename to e.g. "(menu/display)-title"
|
||||
* and also make it non-nullable (return URI as final fallback) */
|
||||
/**
|
||||
* ClapperMediaItem:title:
|
||||
*
|
||||
* Media title.
|
||||
*
|
||||
* This might be a different string compared to `title` from
|
||||
* [property@Clapper.MediaItem:tags], as this gives parsed
|
||||
* title from file name/URI as fallback when no `title` tag.
|
||||
*/
|
||||
param_specs[PROP_TITLE] = g_param_spec_string ("title",
|
||||
NULL, NULL, NULL,
|
||||
@@ -699,15 +911,21 @@ clapper_media_item_class_init (ClapperMediaItemClass *klass)
|
||||
* ClapperMediaItem:container-format:
|
||||
*
|
||||
* Media container format.
|
||||
*
|
||||
* Deprecated: 0.10: Get `container-format` from [property@Clapper.MediaItem:tags] instead.
|
||||
*/
|
||||
param_specs[PROP_CONTAINER_FORMAT] = g_param_spec_string ("container-format",
|
||||
NULL, NULL, NULL,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED);
|
||||
|
||||
/**
|
||||
* ClapperMediaItem:duration:
|
||||
*
|
||||
* Media duration as a decimal number in seconds.
|
||||
*
|
||||
* This might be a different value compared to `duration` from
|
||||
* [property@Clapper.MediaItem:tags], as this value is updated
|
||||
* during decoding instead of being a fixed value from metadata.
|
||||
*/
|
||||
param_specs[PROP_DURATION] = g_param_spec_double ("duration",
|
||||
NULL, NULL, 0, G_MAXDOUBLE, 0,
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include <glib-object.h>
|
||||
#include <gio/gio.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/tag/tag.h>
|
||||
|
||||
#include <clapper/clapper-visibility.h>
|
||||
#include <clapper/clapper-timeline.h>
|
||||
@@ -63,12 +64,18 @@ gchar * clapper_media_item_get_suburi (ClapperMediaItem *item);
|
||||
CLAPPER_API
|
||||
gchar * clapper_media_item_get_title (ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
CLAPPER_DEPRECATED_FOR(clapper_media_item_get_tags)
|
||||
gchar * clapper_media_item_get_container_format (ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
gdouble clapper_media_item_get_duration (ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
GstTagList * clapper_media_item_get_tags (ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
gboolean clapper_media_item_populate_tags (ClapperMediaItem *item, const GstTagList *tags);
|
||||
|
||||
CLAPPER_API
|
||||
ClapperTimeline * clapper_media_item_get_timeline (ClapperMediaItem *item);
|
||||
|
||||
|
@@ -29,12 +29,7 @@
|
||||
#include "clapper-timeline-private.h"
|
||||
#include "clapper-stream-private.h"
|
||||
#include "clapper-stream-list-private.h"
|
||||
|
||||
#include "clapper-functionalities-availability.h"
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
#include "gst/clapper-enhancer-src-private.h"
|
||||
#endif
|
||||
#include "gst/clapper-extractable-src-private.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_playbin_bus_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
@@ -178,6 +173,10 @@ _update_current_duration (ClapperPlayer *player)
|
||||
if (clapper_media_item_set_duration (player->played_item, duration_dbl, player->app_bus)) {
|
||||
ClapperFeaturesManager *features_manager;
|
||||
|
||||
if (player->reactables_manager) {
|
||||
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, player->played_item,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_DURATION);
|
||||
}
|
||||
if ((features_manager = clapper_player_get_features_manager (player)))
|
||||
clapper_features_manager_trigger_item_updated (features_manager, player->played_item);
|
||||
}
|
||||
@@ -875,7 +874,6 @@ _handle_tag_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
{
|
||||
GstObject *src = GST_MESSAGE_SRC (msg);
|
||||
GstTagList *tags = NULL;
|
||||
gboolean from_enhancer_src;
|
||||
|
||||
/* Tag messages should only be posted by sink elements */
|
||||
if (G_UNLIKELY (!src))
|
||||
@@ -886,14 +884,8 @@ _handle_tag_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
GST_LOG_OBJECT (player, "Got tags from element: %s: %" GST_PTR_FORMAT,
|
||||
GST_OBJECT_NAME (src), tags);
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
from_enhancer_src = CLAPPER_IS_ENHANCER_SRC (src);
|
||||
#else
|
||||
from_enhancer_src = FALSE;
|
||||
#endif
|
||||
|
||||
/* ClapperEnhancerSrc determines tags before stream start */
|
||||
if (from_enhancer_src) {
|
||||
/* ClapperExtractableSrc determines tags before stream start */
|
||||
if (CLAPPER_IS_EXTRACTABLE_SRC (src)) {
|
||||
if (player->pending_tags) {
|
||||
gst_tag_list_unref (player->pending_tags);
|
||||
}
|
||||
@@ -910,7 +902,7 @@ _handle_toc_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
{
|
||||
GstObject *src = GST_MESSAGE_SRC (msg);
|
||||
GstToc *toc = NULL;
|
||||
gboolean from_enhancer_src, updated = FALSE;
|
||||
gboolean updated = FALSE;
|
||||
|
||||
/* TOC messages should only be posted by sink elements */
|
||||
if (G_UNLIKELY (!src))
|
||||
@@ -923,14 +915,8 @@ _handle_toc_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
" from element: %s, updated: %s",
|
||||
toc, GST_OBJECT_NAME (src), (updated) ? "yes" : "no");
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
from_enhancer_src = CLAPPER_IS_ENHANCER_SRC (src);
|
||||
#else
|
||||
from_enhancer_src = FALSE;
|
||||
#endif
|
||||
|
||||
/* ClapperEnhancerSrc determines TOC before stream start */
|
||||
if (from_enhancer_src) {
|
||||
/* ClapperExtractableSrc determines TOC before stream start */
|
||||
if (CLAPPER_IS_EXTRACTABLE_SRC (src)) {
|
||||
if (player->pending_toc) {
|
||||
gst_toc_unref (player->pending_toc);
|
||||
}
|
||||
@@ -1064,6 +1050,8 @@ _handle_stream_start_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
if (G_LIKELY (changed)) {
|
||||
clapper_queue_handle_played_item_changed (player->queue, player->played_item, player->app_bus);
|
||||
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_played_item_changed (player->reactables_manager, player->played_item);
|
||||
if (clapper_player_get_have_features (player))
|
||||
clapper_features_manager_trigger_played_item_changed (player->features_manager, player->played_item);
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "clapper-app-bus-private.h"
|
||||
#include "clapper-features-manager-private.h"
|
||||
#include "clapper-reactables-manager-private.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@@ -47,6 +48,10 @@ struct _ClapperPlayer
|
||||
ClapperFeaturesManager *features_manager;
|
||||
gint have_features; // atomic integer
|
||||
|
||||
ClapperReactablesManager *reactables_manager;
|
||||
|
||||
ClapperEnhancerProxyList *enhancer_proxies;
|
||||
|
||||
/* This is different from queue current item as it is used/changed only
|
||||
* on player thread, so we can always update correct item without lock */
|
||||
ClapperMediaItem *played_item;
|
||||
|
@@ -49,6 +49,8 @@
|
||||
#include "clapper-video-stream-private.h"
|
||||
#include "clapper-audio-stream-private.h"
|
||||
#include "clapper-subtitle-stream-private.h"
|
||||
#include "clapper-enhancer-proxy-list-private.h"
|
||||
#include "clapper-reactable.h"
|
||||
#include "clapper-enums-private.h"
|
||||
#include "clapper-utils-private.h"
|
||||
#include "../shared/clapper-shared-utils-private.h"
|
||||
@@ -77,6 +79,7 @@ enum
|
||||
PROP_VIDEO_STREAMS,
|
||||
PROP_AUDIO_STREAMS,
|
||||
PROP_SUBTITLE_STREAMS,
|
||||
PROP_ENHANCER_PROXIES,
|
||||
PROP_AUTOPLAY,
|
||||
PROP_POSITION,
|
||||
PROP_SPEED,
|
||||
@@ -157,6 +160,8 @@ clapper_player_refresh_position (ClapperPlayer *self)
|
||||
|
||||
clapper_app_bus_post_prop_notify (self->app_bus,
|
||||
GST_OBJECT_CAST (self), param_specs[PROP_POSITION]);
|
||||
if (self->reactables_manager)
|
||||
clapper_reactables_manager_trigger_position_changed (self->reactables_manager, position_dbl);
|
||||
if (clapper_player_get_have_features (self))
|
||||
clapper_features_manager_trigger_position_changed (self->features_manager, position_dbl);
|
||||
}
|
||||
@@ -223,6 +228,8 @@ clapper_player_handle_playbin_state_changed (ClapperPlayer *self)
|
||||
|
||||
clapper_app_bus_post_prop_notify (self->app_bus,
|
||||
GST_OBJECT_CAST (self), param_specs[PROP_STATE]);
|
||||
if (self->reactables_manager)
|
||||
clapper_reactables_manager_trigger_state_changed (self->reactables_manager, state);
|
||||
if (clapper_player_get_have_features (self))
|
||||
clapper_features_manager_trigger_state_changed (self->features_manager, state);
|
||||
}
|
||||
@@ -254,6 +261,8 @@ clapper_player_handle_playbin_volume_changed (ClapperPlayer *self, const GValue
|
||||
|
||||
clapper_app_bus_post_prop_notify (self->app_bus,
|
||||
GST_OBJECT_CAST (self), param_specs[PROP_VOLUME]);
|
||||
if (self->reactables_manager)
|
||||
clapper_reactables_manager_trigger_volume_changed (self->reactables_manager, volume);
|
||||
if (clapper_player_get_have_features (self))
|
||||
clapper_features_manager_trigger_volume_changed (self->features_manager, volume);
|
||||
}
|
||||
@@ -278,6 +287,8 @@ clapper_player_handle_playbin_mute_changed (ClapperPlayer *self, const GValue *v
|
||||
|
||||
clapper_app_bus_post_prop_notify (self->app_bus,
|
||||
GST_OBJECT_CAST (self), param_specs[PROP_MUTE]);
|
||||
if (self->reactables_manager)
|
||||
clapper_reactables_manager_trigger_mute_changed (self->reactables_manager, mute);
|
||||
if (clapper_player_get_have_features (self))
|
||||
clapper_features_manager_trigger_mute_changed (self->features_manager, mute);
|
||||
}
|
||||
@@ -398,6 +409,8 @@ clapper_player_handle_playbin_rate_changed (ClapperPlayer *self, gdouble speed)
|
||||
|
||||
clapper_app_bus_post_prop_notify (self->app_bus,
|
||||
GST_OBJECT_CAST (self), param_specs[PROP_SPEED]);
|
||||
if (self->reactables_manager)
|
||||
clapper_reactables_manager_trigger_speed_changed (self->reactables_manager, speed);
|
||||
if (clapper_player_get_have_features (self))
|
||||
clapper_features_manager_trigger_speed_changed (self->features_manager, speed);
|
||||
}
|
||||
@@ -814,7 +827,11 @@ _element_setup_cb (GstElement *playbin, GstElement *element, ClapperPlayer *self
|
||||
factory_name = g_intern_static_string (GST_OBJECT_NAME (factory));
|
||||
GST_INFO_OBJECT (self, "Element setup: %s", factory_name);
|
||||
|
||||
if (factory_name == g_intern_static_string ("downloadbuffer")) {
|
||||
if (factory_name == g_intern_static_string ("clapperextractablesrc")) {
|
||||
g_object_set (element,
|
||||
"enhancer-proxies", self->enhancer_proxies,
|
||||
NULL);
|
||||
} else if (factory_name == g_intern_static_string ("downloadbuffer")) {
|
||||
gchar *download_template;
|
||||
|
||||
/* Only set props if we have download template */
|
||||
@@ -1129,6 +1146,24 @@ clapper_player_get_subtitle_streams (ClapperPlayer *self)
|
||||
return self->subtitle_streams;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_player_get_enhancer_proxies:
|
||||
* @player: a #ClapperPlayer
|
||||
*
|
||||
* Get a list of available enhancers in the form of [class@Clapper.EnhancerProxy] objects.
|
||||
*
|
||||
* Returns: (transfer none): a #ClapperEnhancerProxyList of enhancer proxies.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
ClapperEnhancerProxyList *
|
||||
clapper_player_get_enhancer_proxies (ClapperPlayer *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL);
|
||||
|
||||
return self->enhancer_proxies;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_player_set_autoplay:
|
||||
* @player: a #ClapperPlayer
|
||||
@@ -2297,6 +2332,18 @@ clapper_player_init (ClapperPlayer *self)
|
||||
self->subtitle_streams = clapper_stream_list_new ();
|
||||
gst_object_set_parent (GST_OBJECT_CAST (self->subtitle_streams), GST_OBJECT_CAST (self));
|
||||
|
||||
self->enhancer_proxies = clapper_enhancer_proxy_list_new_named (NULL);
|
||||
gst_object_set_parent (GST_OBJECT_CAST (self->enhancer_proxies), GST_OBJECT_CAST (self));
|
||||
|
||||
clapper_enhancer_proxy_list_fill_from_global_proxies (self->enhancer_proxies);
|
||||
|
||||
if (clapper_enhancer_proxy_list_has_proxy_with_interface (self->enhancer_proxies, CLAPPER_TYPE_REACTABLE)) {
|
||||
self->reactables_manager = clapper_reactables_manager_new ();
|
||||
gst_object_set_parent (GST_OBJECT_CAST (self->reactables_manager), GST_OBJECT_CAST (self));
|
||||
|
||||
clapper_reactables_manager_trigger_prepare (self->reactables_manager);
|
||||
}
|
||||
|
||||
self->position_query = gst_query_new_position (GST_FORMAT_TIME);
|
||||
|
||||
self->current_state = GST_STATE_NULL;
|
||||
@@ -2363,6 +2410,14 @@ clapper_player_finalize (GObject *object)
|
||||
gst_object_unparent (GST_OBJECT_CAST (self->subtitle_streams));
|
||||
gst_object_unref (self->subtitle_streams);
|
||||
|
||||
if (self->reactables_manager) {
|
||||
gst_object_unparent (GST_OBJECT_CAST (self->reactables_manager));
|
||||
gst_object_unref (self->reactables_manager);
|
||||
}
|
||||
|
||||
gst_object_unparent (GST_OBJECT_CAST (self->enhancer_proxies));
|
||||
gst_object_unref (self->enhancer_proxies);
|
||||
|
||||
gst_query_unref (self->position_query);
|
||||
|
||||
gst_clear_object (&self->collection);
|
||||
@@ -2394,6 +2449,9 @@ clapper_player_get_property (GObject *object, guint prop_id,
|
||||
case PROP_SUBTITLE_STREAMS:
|
||||
g_value_set_object (value, clapper_player_get_subtitle_streams (self));
|
||||
break;
|
||||
case PROP_ENHANCER_PROXIES:
|
||||
g_value_set_object (value, clapper_player_get_enhancer_proxies (self));
|
||||
break;
|
||||
case PROP_AUTOPLAY:
|
||||
g_value_set_boolean (value, clapper_player_get_autoplay (self));
|
||||
break;
|
||||
@@ -2593,6 +2651,20 @@ clapper_player_class_init (ClapperPlayerClass *klass)
|
||||
NULL, NULL, CLAPPER_TYPE_STREAM_LIST,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperPlayer:enhancer-proxies:
|
||||
*
|
||||
* List of available enhancers in the form of [class@Clapper.EnhancerProxy] objects.
|
||||
*
|
||||
* Use these to inspect available enhancers on the system and configure
|
||||
* their properties on a per player instance basis.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies",
|
||||
NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperPlayer:autoplay:
|
||||
*
|
||||
@@ -2784,7 +2856,7 @@ clapper_player_class_init (ClapperPlayerClass *klass)
|
||||
* An initial bitrate (bits/s) to select during
|
||||
* starting adaptive streaming such as DASH or HLS.
|
||||
*
|
||||
* If value is higher than lowest available bitrate in streaming
|
||||
* If value is lower than the lowest available bitrate in streaming
|
||||
* manifest, then lowest possible bitrate will be selected.
|
||||
*
|
||||
* Since: 0.8
|
||||
|
@@ -31,6 +31,7 @@
|
||||
#include <clapper/clapper-threaded-object.h>
|
||||
#include <clapper/clapper-queue.h>
|
||||
#include <clapper/clapper-stream-list.h>
|
||||
#include <clapper/clapper-enhancer-proxy-list.h>
|
||||
#include <clapper/clapper-feature.h>
|
||||
#include <clapper/clapper-enums.h>
|
||||
|
||||
@@ -57,6 +58,9 @@ ClapperStreamList * clapper_player_get_audio_streams (ClapperPlayer *player);
|
||||
CLAPPER_API
|
||||
ClapperStreamList * clapper_player_get_subtitle_streams (ClapperPlayer *player);
|
||||
|
||||
CLAPPER_API
|
||||
ClapperEnhancerProxyList * clapper_player_get_enhancer_proxies (ClapperPlayer *player);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_player_set_autoplay (ClapperPlayer *player, gboolean enabled);
|
||||
|
||||
|
@@ -29,6 +29,8 @@
|
||||
#include "clapper-media-item-private.h"
|
||||
#include "clapper-player-private.h"
|
||||
#include "clapper-playbin-bus-private.h"
|
||||
#include "clapper-reactables-manager-private.h"
|
||||
#include "clapper-features-manager-private.h"
|
||||
|
||||
#define CLAPPER_QUEUE_GET_REC_LOCK(obj) (&CLAPPER_QUEUE_CAST(obj)->rec_lock)
|
||||
#define CLAPPER_QUEUE_REC_LOCK(obj) g_rec_mutex_lock (CLAPPER_QUEUE_GET_REC_LOCK(obj))
|
||||
@@ -132,15 +134,27 @@ _announce_model_update (ClapperQueue *self, guint index, guint removed, guint ad
|
||||
if (removed != added) {
|
||||
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
||||
|
||||
if (player && clapper_player_get_have_features (player)) {
|
||||
if (added == 1) // addition
|
||||
clapper_features_manager_trigger_queue_item_added (player->features_manager, changed_item, index);
|
||||
else if (removed == 1) // removal
|
||||
clapper_features_manager_trigger_queue_item_removed (player->features_manager, changed_item, index);
|
||||
else if (removed > 1 && added == 0) // queue cleared
|
||||
clapper_features_manager_trigger_queue_cleared (player->features_manager);
|
||||
else
|
||||
if (player) {
|
||||
gboolean have_features = clapper_player_get_have_features (player);
|
||||
|
||||
if (added == 1) { // addition
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_queue_item_added (player->reactables_manager, changed_item, index);
|
||||
if (have_features)
|
||||
clapper_features_manager_trigger_queue_item_added (player->features_manager, changed_item, index);
|
||||
} else if (removed == 1) { // removal
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_queue_item_removed (player->reactables_manager, changed_item, index);
|
||||
if (have_features)
|
||||
clapper_features_manager_trigger_queue_item_removed (player->features_manager, changed_item, index);
|
||||
} else if (removed > 1 && added == 0) { // queue cleared
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_queue_cleared (player->reactables_manager);
|
||||
if (have_features)
|
||||
clapper_features_manager_trigger_queue_cleared (player->features_manager);
|
||||
} else {
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
gst_clear_object (&player);
|
||||
@@ -160,6 +174,8 @@ _announce_reposition (ClapperQueue *self, guint before, guint after)
|
||||
GST_DEBUG_OBJECT (self, "Announcing item reposition: %u -> %u", before, after);
|
||||
|
||||
if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) {
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_queue_item_repositioned (player->reactables_manager, before, after);
|
||||
if (clapper_player_get_have_features (player))
|
||||
clapper_features_manager_trigger_queue_item_repositioned (player->features_manager, before, after);
|
||||
|
||||
@@ -989,6 +1005,8 @@ clapper_queue_set_progression_mode (ClapperQueue *self, ClapperQueueProgressionM
|
||||
|
||||
clapper_app_bus_post_prop_notify (player->app_bus,
|
||||
GST_OBJECT_CAST (self), param_specs[PROP_PROGRESSION_MODE]);
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_queue_progression_changed (player->reactables_manager, mode);
|
||||
if (clapper_player_get_have_features (player))
|
||||
clapper_features_manager_trigger_queue_progression_changed (player->features_manager, mode);
|
||||
|
||||
|
210
src/lib/clapper/clapper-reactable.c
Normal file
210
src/lib/clapper/clapper-reactable.c
Normal file
@@ -0,0 +1,210 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ClapperReactable:
|
||||
*
|
||||
* An interface for creating enhancers that react to the
|
||||
* playback and/or events that should influence it.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
|
||||
#include "clapper-reactable.h"
|
||||
#include "clapper-utils-private.h"
|
||||
|
||||
#define CLAPPER_REACTABLE_DO_WITH_QUEUE(reactable, _queue_dst, ...) { \
|
||||
ClapperPlayer *_player = clapper_reactable_get_player (reactable); \
|
||||
if (G_LIKELY (_player != NULL)) { \
|
||||
*_queue_dst = clapper_player_get_queue (_player); \
|
||||
__VA_ARGS__ \
|
||||
gst_object_unref (_player); }}
|
||||
|
||||
G_DEFINE_INTERFACE (ClapperReactable, clapper_reactable, GST_TYPE_OBJECT);
|
||||
|
||||
static void
|
||||
clapper_reactable_default_init (ClapperReactableInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_reactable_get_player:
|
||||
* @reactable: a #ClapperReactable
|
||||
*
|
||||
* Get the [class@Clapper.Player] that this reactable is reacting to.
|
||||
*
|
||||
* This is meant to be used in implementations where reaction goes the
|
||||
* other way around (from enhancer plugin to the player). For example
|
||||
* some external event needs to influence parent player object like
|
||||
* changing its state, seeking, etc.
|
||||
*
|
||||
* Note that enhancers are working in a non-main application thread, thus
|
||||
* if you need to do operations on a [class@Clapper.Queue] such as adding/removing
|
||||
* items, you need to switch thread first. Otherwise this will not be thread safe
|
||||
* for applications that use single threaded toolkits such as #GTK. You can do this
|
||||
* manually or use provided reactable convenience functions.
|
||||
*
|
||||
* Due to the threaded nature, you should also avoid comparisons to the current
|
||||
* properties values in the player or its queue. While these are thread safe, there
|
||||
* is no guarantee that values/objects between threads are still the same in both
|
||||
* (or still exist). For example, instead of using [property@Clapper.Queue:current_item],
|
||||
* monitor it with implemented [vfunc@Clapper.Reactable.played_item_changed] instead,
|
||||
* as these functions are all serialized into your implementation thread.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): A reference to the parent #ClapperPlayer.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
ClapperPlayer *
|
||||
clapper_reactable_get_player (ClapperReactable *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_IS_REACTABLE (self), NULL);
|
||||
|
||||
return CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (self)));
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_reactable_queue_append_sync:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @item: a #ClapperMediaItem
|
||||
*
|
||||
* A convenience function that within application main thread synchronously appends
|
||||
* an @item to the playback queue of the player that @reactable belongs to.
|
||||
*
|
||||
* Reactable enhancers should only modify the queue from the application
|
||||
* main thread, switching thread either themselves or using this convenience
|
||||
* function that does so.
|
||||
*
|
||||
* Note that this function will do no operation if called when there is no player
|
||||
* set yet (e.g. inside enhancer construction) or if enhancer outlived the parent
|
||||
* instance somehow. Both cases are considered to be implementation bug.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void
|
||||
clapper_reactable_queue_append_sync (ClapperReactable *self, ClapperMediaItem *item)
|
||||
{
|
||||
ClapperQueue *queue;
|
||||
|
||||
g_return_if_fail (CLAPPER_IS_REACTABLE (self));
|
||||
g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item));
|
||||
|
||||
CLAPPER_REACTABLE_DO_WITH_QUEUE (self, &queue, {
|
||||
clapper_utils_queue_append_on_main_sync (queue, item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_reactable_queue_insert_sync:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @item: a #ClapperMediaItem
|
||||
* @after_item: a #ClapperMediaItem after which to insert or %NULL to prepend
|
||||
*
|
||||
* A convenience function that within application main thread synchronously inserts
|
||||
* an @item to the playback queue position after @after_item of the player that
|
||||
* @reactable belongs to.
|
||||
*
|
||||
* This function uses @after_item instead of position index in order to ensure
|
||||
* desired position does not change during thread switching.
|
||||
*
|
||||
* Reactable enhancers should only modify the queue from the application
|
||||
* main thread, switching thread either themselves or using this convenience
|
||||
* function that does so.
|
||||
*
|
||||
* Note that this function will do no operation if called when there is no player
|
||||
* set yet (e.g. inside enhancer construction) or if enhancer outlived the parent
|
||||
* instance somehow. Both cases are considered to be implementation bug.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void
|
||||
clapper_reactable_queue_insert_sync (ClapperReactable *self,
|
||||
ClapperMediaItem *item, ClapperMediaItem *after_item)
|
||||
{
|
||||
ClapperQueue *queue;
|
||||
|
||||
g_return_if_fail (CLAPPER_IS_REACTABLE (self));
|
||||
g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item));
|
||||
g_return_if_fail (after_item == NULL || CLAPPER_IS_MEDIA_ITEM (after_item));
|
||||
|
||||
CLAPPER_REACTABLE_DO_WITH_QUEUE (self, &queue, {
|
||||
clapper_utils_queue_insert_on_main_sync (queue, item, after_item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_reactable_queue_remove_sync:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @item: a #ClapperMediaItem
|
||||
*
|
||||
* A convenience function that within application main thread synchronously removes
|
||||
* an @item from the playback queue of the player that @reactable belongs to.
|
||||
*
|
||||
* Reactable enhancers should only modify the queue from the application
|
||||
* main thread, switching thread either themselves or using this convenience
|
||||
* function that does so.
|
||||
*
|
||||
* Note that this function will do no operation if called when there is no player
|
||||
* set yet (e.g. inside enhancer construction) or if enhancer outlived the parent
|
||||
* instance somehow. Both cases are considered to be implementation bug.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void
|
||||
clapper_reactable_queue_remove_sync (ClapperReactable *self, ClapperMediaItem *item)
|
||||
{
|
||||
ClapperQueue *queue;
|
||||
|
||||
g_return_if_fail (CLAPPER_IS_REACTABLE (self));
|
||||
g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item));
|
||||
|
||||
CLAPPER_REACTABLE_DO_WITH_QUEUE (self, &queue, {
|
||||
clapper_utils_queue_remove_on_main_sync (queue, item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_reactable_queue_clear_sync:
|
||||
* @reactable: a #ClapperReactable
|
||||
*
|
||||
* A convenience function that within application main thread synchronously clears
|
||||
* the playback queue of the player that @reactable belongs to.
|
||||
*
|
||||
* Reactable enhancers should only modify the queue from the application
|
||||
* main thread, switching thread either themselves or using this convenience
|
||||
* function that does so.
|
||||
*
|
||||
* Note that this function will do no operation if called when there is no player
|
||||
* set yet (e.g. inside enhancer construction) or if enhancer outlived the parent
|
||||
* instance somehow. Both cases are considered to be implementation bug.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void
|
||||
clapper_reactable_queue_clear_sync (ClapperReactable *self)
|
||||
{
|
||||
ClapperQueue *queue;
|
||||
|
||||
g_return_if_fail (CLAPPER_IS_REACTABLE (self));
|
||||
g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item));
|
||||
|
||||
CLAPPER_REACTABLE_DO_WITH_QUEUE (self, &queue, {
|
||||
clapper_utils_queue_clear_on_main_sync (queue);
|
||||
});
|
||||
}
|
229
src/lib/clapper/clapper-reactable.h
Normal file
229
src/lib/clapper/clapper-reactable.h
Normal file
@@ -0,0 +1,229 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
|
||||
#error "Only <clapper/clapper.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <clapper/clapper-visibility.h>
|
||||
#include <clapper/clapper-player.h>
|
||||
#include <clapper/clapper-enums.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_TYPE_REACTABLE (clapper_reactable_get_type())
|
||||
#define CLAPPER_REACTABLE_CAST(obj) ((ClapperReactable *)(obj))
|
||||
|
||||
CLAPPER_API
|
||||
G_DECLARE_INTERFACE (ClapperReactable, clapper_reactable, CLAPPER, REACTABLE, GstObject)
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface:
|
||||
* @parent_iface: The parent interface structure.
|
||||
* @state_changed: Player state changed.
|
||||
* @position_changed: Player position changed.
|
||||
* @speed_changed: Player speed changed.
|
||||
* @volume_changed: Player volume changed.
|
||||
* @mute_changed: Player mute state changed.
|
||||
* @played_item_changed: New media item started playing.
|
||||
* @item_updated: An item in queue got updated.
|
||||
* @queue_item_added: An item was added to the queue.
|
||||
* @queue_item_removed: An item was removed from queue.
|
||||
* @queue_item_repositioned: An item changed position within queue.
|
||||
* @queue_cleared: All items were removed from queue.
|
||||
* @queue_progression_changed: Progression mode of the queue was changed.
|
||||
*/
|
||||
struct _ClapperReactableInterface
|
||||
{
|
||||
GTypeInterface parent_iface;
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::state_changed:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @state: a #ClapperPlayerState
|
||||
*
|
||||
* Player state changed.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* state_changed) (ClapperReactable *reactable, ClapperPlayerState state);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::position_changed:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @position: a decimal number with current position in seconds
|
||||
*
|
||||
* Player position changed.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* position_changed) (ClapperReactable *reactable, gdouble position);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::speed_changed:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @speed: the playback speed multiplier
|
||||
*
|
||||
* Player speed changed.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* speed_changed) (ClapperReactable *reactable, gdouble speed);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::volume_changed:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @volume: the volume level
|
||||
*
|
||||
* Player volume changed.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* volume_changed) (ClapperReactable *reactable, gdouble volume);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::mute_changed:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @mute: %TRUE if player is muted, %FALSE otherwise
|
||||
*
|
||||
* Player mute state changed.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* mute_changed) (ClapperReactable *reactable, gboolean mute);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::played_item_changed:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @item: a #ClapperMediaItem that is now playing
|
||||
*
|
||||
* New media item started playing. All following events (such as position changes)
|
||||
* will be related to this @item from now on.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* played_item_changed) (ClapperReactable *reactable, ClapperMediaItem *item);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::item_updated:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @item: a #ClapperMediaItem that was updated
|
||||
* @flags: flags informing which properties were updated
|
||||
*
|
||||
* An item in queue got updated.
|
||||
*
|
||||
* This might be (or not) currently played item.
|
||||
* Implementations can compare it against the last item from
|
||||
* [vfunc@Clapper.Reactable.played_item_changed] if they
|
||||
* need to know that.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* item_updated) (ClapperReactable *reactable, ClapperMediaItem *item, ClapperReactableItemUpdatedFlags flags);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::queue_item_added:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @item: a #ClapperMediaItem that was added
|
||||
* @index: position at which @item was placed in queue
|
||||
*
|
||||
* An item was added to the queue.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* queue_item_added) (ClapperReactable *reactable, ClapperMediaItem *item, guint index);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::queue_item_removed:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @item: a #ClapperMediaItem that was removed
|
||||
* @index: position from which @item was removed in queue
|
||||
*
|
||||
* An item was removed from queue.
|
||||
*
|
||||
* Implementations that are interested in queue items removal
|
||||
* should also implement [vfunc@Clapper.Reactable.queue_cleared].
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* queue_item_removed) (ClapperReactable *reactable, ClapperMediaItem *item, guint index);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::queue_item_repositioned:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @before: position from which #ClapperMediaItem was removed
|
||||
* @after: position at which #ClapperMediaItem was inserted after removal
|
||||
*
|
||||
* An item changed position within queue.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* queue_item_repositioned) (ClapperReactable *reactable, guint before, guint after);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::queue_cleared:
|
||||
* @reactable: a #ClapperReactable
|
||||
*
|
||||
* All items were removed from queue.
|
||||
*
|
||||
* Note that in such event [vfunc@Clapper.Reactable.queue_item_removed]
|
||||
* will NOT be called for each item for performance reasons. You probably
|
||||
* want to implement this function if you also implemented item removal.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* queue_cleared) (ClapperReactable *reactable);
|
||||
|
||||
/**
|
||||
* ClapperReactableInterface::queue_progression_changed:
|
||||
* @reactable: a #ClapperReactable
|
||||
* @mode: a #ClapperQueueProgressionMode
|
||||
*
|
||||
* Progression mode of the queue was changed.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void (* queue_progression_changed) (ClapperReactable *reactable, ClapperQueueProgressionMode mode);
|
||||
|
||||
/*< private >*/
|
||||
gpointer padding[8];
|
||||
};
|
||||
|
||||
CLAPPER_API
|
||||
ClapperPlayer * clapper_reactable_get_player (ClapperReactable *reactable);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_reactable_queue_append_sync (ClapperReactable *reactable, ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_reactable_queue_insert_sync (ClapperReactable *reactable, ClapperMediaItem *item, ClapperMediaItem *after_item);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_reactable_queue_remove_sync (ClapperReactable *reactable, ClapperMediaItem *item);
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_reactable_queue_clear_sync (ClapperReactable *reactable);
|
||||
|
||||
G_END_DECLS
|
82
src/lib/clapper/clapper-reactables-manager-private.h
Normal file
82
src/lib/clapper/clapper-reactables-manager-private.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "clapper-enums.h"
|
||||
#include "clapper-threaded-object.h"
|
||||
#include "clapper-enhancer-proxy.h"
|
||||
#include "clapper-media-item.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_TYPE_REACTABLES_MANAGER (clapper_reactables_manager_get_type())
|
||||
#define CLAPPER_REACTABLES_MANAGER_CAST(obj) ((ClapperReactablesManager *)(obj))
|
||||
|
||||
G_DECLARE_FINAL_TYPE (ClapperReactablesManager, clapper_reactables_manager, CLAPPER, REACTABLES_MANAGER, ClapperThreadedObject)
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_initialize (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
ClapperReactablesManager * clapper_reactables_manager_new (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_prepare (ClapperReactablesManager *manager);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_configure_take_config (ClapperReactablesManager *manager, ClapperEnhancerProxy *proxy, GstStructure *config);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_state_changed (ClapperReactablesManager *manager, ClapperPlayerState state);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_position_changed (ClapperReactablesManager *manager, gdouble position);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_speed_changed (ClapperReactablesManager *manager, gdouble speed);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_volume_changed (ClapperReactablesManager *manager, gdouble volume);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_mute_changed (ClapperReactablesManager *manager, gboolean mute);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_played_item_changed (ClapperReactablesManager *manager, ClapperMediaItem *item);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_item_updated (ClapperReactablesManager *manager, ClapperMediaItem *item, ClapperReactableItemUpdatedFlags flags);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_queue_item_added (ClapperReactablesManager *manager, ClapperMediaItem *item, guint index);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_queue_item_removed (ClapperReactablesManager *manager, ClapperMediaItem *item, guint index);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_queue_item_repositioned (ClapperReactablesManager *manager, guint before, guint after);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_queue_cleared (ClapperReactablesManager *manager);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_reactables_manager_trigger_queue_progression_changed (ClapperReactablesManager *manager, ClapperQueueProgressionMode mode);
|
||||
|
||||
G_END_DECLS
|
534
src/lib/clapper/clapper-reactables-manager.c
Normal file
534
src/lib/clapper/clapper-reactables-manager.c
Normal file
@@ -0,0 +1,534 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "clapper-reactables-manager-private.h"
|
||||
#include "clapper-reactable.h"
|
||||
#include "clapper-bus-private.h"
|
||||
#include "clapper-player.h"
|
||||
#include "clapper-enhancer-proxy-list.h"
|
||||
#include "clapper-enhancer-proxy-private.h"
|
||||
#include "clapper-utils-private.h"
|
||||
|
||||
#include "clapper-functionalities-availability.h"
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
#include "clapper-enhancers-loader-private.h"
|
||||
#endif
|
||||
|
||||
#define CONFIG_STRUCTURE_NAME "config"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_reactables_manager_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _ClapperReactablesManager
|
||||
{
|
||||
ClapperThreadedObject parent;
|
||||
|
||||
GstBus *bus;
|
||||
GPtrArray *array;
|
||||
};
|
||||
|
||||
#define parent_class clapper_reactables_manager_parent_class
|
||||
G_DEFINE_TYPE (ClapperReactablesManager, clapper_reactables_manager, CLAPPER_TYPE_THREADED_OBJECT);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ClapperReactable *reactable;
|
||||
ClapperEnhancerProxy *proxy;
|
||||
GSettings *settings;
|
||||
} ClapperReactableManagerData;
|
||||
|
||||
enum
|
||||
{
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_INVALID = 0,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_STATE_CHANGED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_POSITION_CHANGED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_SPEED_CHANGED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_VOLUME_CHANGED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_MUTE_CHANGED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_PLAYED_ITEM_CHANGED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_ITEM_UPDATED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_ITEM_ADDED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_ITEM_REMOVED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_ITEM_REPOSITIONED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_CLEARED,
|
||||
CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_PROGRESSION_CHANGED
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_PREPARE = 0,
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_CONFIGURE,
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_EVENT,
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_VALUE,
|
||||
CLAPPER_REACTABLES_MANAGER_QUARK_EXTRA_VALUE
|
||||
};
|
||||
|
||||
static ClapperBusQuark _quarks[] = {
|
||||
{"prepare", 0},
|
||||
{"configure", 0},
|
||||
{"event", 0},
|
||||
{"value", 0},
|
||||
{"extra-value", 0},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
#define _EVENT(e) G_PASTE(CLAPPER_REACTABLES_MANAGER_EVENT_, e)
|
||||
#define _QUARK(q) (_quarks[CLAPPER_REACTABLES_MANAGER_QUARK_##q].quark)
|
||||
|
||||
#define _BUS_POST_EVENT_SINGLE(event_id,lower,type,val) { \
|
||||
GValue _value = G_VALUE_INIT; \
|
||||
g_value_init (&_value, type); \
|
||||
g_value_set_##lower (&_value, val); \
|
||||
_bus_post_event (self, event_id, &_value, NULL); }
|
||||
|
||||
#define _BUS_POST_EVENT_DUAL(event_id,lower1,type1,val1,lower2,type2,val2) { \
|
||||
GValue _value1 = G_VALUE_INIT; \
|
||||
GValue _value2 = G_VALUE_INIT; \
|
||||
g_value_init (&_value1, type1); \
|
||||
g_value_init (&_value2, type2); \
|
||||
g_value_set_##lower1 (&_value1, val1); \
|
||||
g_value_set_##lower2 (&_value2, val2); \
|
||||
_bus_post_event (self, event_id, &_value1, &_value2); }
|
||||
|
||||
void
|
||||
clapper_reactables_manager_initialize (void)
|
||||
{
|
||||
gint i;
|
||||
|
||||
for (i = 0; _quarks[i].name; ++i)
|
||||
_quarks[i].quark = g_quark_from_static_string (_quarks[i].name);
|
||||
}
|
||||
|
||||
static void
|
||||
_settings_changed_cb (GSettings *settings, const gchar *key, ClapperReactableManagerData *data)
|
||||
{
|
||||
GST_DEBUG_OBJECT (data->reactable, "Global setting \"%s\" changed", key);
|
||||
|
||||
/* Local settings are applied through bus events, so all that is
|
||||
* needed here is a check to not overwrite locally set setting */
|
||||
if (!clapper_enhancer_proxy_has_locally_set (data->proxy, key)) {
|
||||
GVariant *variant = g_settings_get_value (settings, key);
|
||||
GValue value = G_VALUE_INIT;
|
||||
|
||||
if (G_LIKELY (clapper_utils_set_value_from_variant (&value, variant))) {
|
||||
g_object_set_property (G_OBJECT (data->reactable), key, &value);
|
||||
g_value_unset (&value);
|
||||
}
|
||||
|
||||
g_variant_unref (variant);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
clapper_reactables_manager_handle_prepare (ClapperReactablesManager *self)
|
||||
{
|
||||
ClapperPlayer *player;
|
||||
|
||||
GST_INFO_OBJECT (self, "Preparing reactable enhancers");
|
||||
player = CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (self)));
|
||||
|
||||
if (G_LIKELY (player != NULL)) {
|
||||
ClapperEnhancerProxyList *proxies = clapper_player_get_enhancer_proxies (player);
|
||||
guint i, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
|
||||
|
||||
for (i = 0; i < n_proxies; ++i) {
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
|
||||
ClapperReactable *reactable = NULL;
|
||||
|
||||
if (!clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_REACTABLE))
|
||||
continue;
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
reactable = CLAPPER_REACTABLE_CAST (
|
||||
clapper_enhancers_loader_create_enhancer (proxy, CLAPPER_TYPE_REACTABLE));
|
||||
#endif
|
||||
|
||||
if (G_LIKELY (reactable != NULL)) {
|
||||
ClapperReactableManagerData *data;
|
||||
GstStructure *config;
|
||||
|
||||
if (g_object_is_floating (reactable))
|
||||
gst_object_ref_sink (reactable);
|
||||
|
||||
data = g_new (ClapperReactableManagerData, 1);
|
||||
data->reactable = reactable;
|
||||
data->proxy = gst_object_ref (proxy);
|
||||
data->settings = clapper_enhancer_proxy_get_settings (proxy);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Created data for reactable: %" GST_PTR_FORMAT, data->reactable);
|
||||
|
||||
/* Settings are stored in data in order for this signal to keep working */
|
||||
if (data->settings)
|
||||
g_signal_connect (data->settings, "changed", G_CALLBACK (_settings_changed_cb), data);
|
||||
|
||||
if ((config = clapper_enhancer_proxy_make_current_config (proxy))) {
|
||||
clapper_enhancer_proxy_apply_config_to_enhancer (proxy, config, (GObject *) reactable);
|
||||
gst_structure_free (config);
|
||||
}
|
||||
|
||||
g_ptr_array_add (self->array, data);
|
||||
gst_object_set_parent (GST_OBJECT_CAST (data->reactable), GST_OBJECT_CAST (player));
|
||||
}
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT (self, "Prepared %i reactable enhancers", self->array->len);
|
||||
gst_object_unref (player);
|
||||
} else {
|
||||
GST_ERROR_OBJECT (self, "Could not prepare reactable enhancers!");
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
clapper_reactables_manager_handle_configure (ClapperReactablesManager *self, const GstStructure *structure)
|
||||
{
|
||||
const GValue *proxy_val, *config_val;
|
||||
ClapperEnhancerProxy *proxy;
|
||||
const GstStructure *config;
|
||||
guint i;
|
||||
|
||||
proxy_val = gst_structure_id_get_value (structure, _QUARK (VALUE));
|
||||
config_val = gst_structure_id_get_value (structure, _QUARK (EXTRA_VALUE));
|
||||
|
||||
proxy = CLAPPER_ENHANCER_PROXY_CAST (g_value_get_object (proxy_val));
|
||||
config = gst_value_get_structure (config_val);
|
||||
|
||||
for (i = 0; i < self->array->len; ++i) {
|
||||
ClapperReactableManagerData *data = g_ptr_array_index (self->array, i);
|
||||
|
||||
if (data->proxy == proxy) {
|
||||
clapper_enhancer_proxy_apply_config_to_enhancer (data->proxy,
|
||||
config, (GObject *) data->reactable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GST_ERROR_OBJECT (self, "Triggered configure, but no matching enhancer proxy found");
|
||||
}
|
||||
|
||||
static inline void
|
||||
clapper_reactables_manager_handle_event (ClapperReactablesManager *self, const GstStructure *structure)
|
||||
{
|
||||
const GValue *value = gst_structure_id_get_value (structure, _QUARK (VALUE));
|
||||
const GValue *extra_value = gst_structure_id_get_value (structure, _QUARK (EXTRA_VALUE));
|
||||
guint i, event_id;
|
||||
|
||||
if (G_UNLIKELY (!gst_structure_id_get (structure,
|
||||
_QUARK (EVENT), G_TYPE_ENUM, &event_id, NULL))) {
|
||||
GST_ERROR_OBJECT (self, "Could not read event ID");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < self->array->len; ++i) {
|
||||
ClapperReactableManagerData *data = g_ptr_array_index (self->array, i);
|
||||
ClapperReactableInterface *reactable_iface = CLAPPER_REACTABLE_GET_IFACE (data->reactable);
|
||||
|
||||
switch (event_id) {
|
||||
case _EVENT (STATE_CHANGED):
|
||||
if (reactable_iface->state_changed)
|
||||
reactable_iface->state_changed (data->reactable, g_value_get_int (value));
|
||||
break;
|
||||
case _EVENT (POSITION_CHANGED):
|
||||
if (reactable_iface->position_changed)
|
||||
reactable_iface->position_changed (data->reactable, g_value_get_double (value));
|
||||
break;
|
||||
case _EVENT (SPEED_CHANGED):
|
||||
if (reactable_iface->speed_changed)
|
||||
reactable_iface->speed_changed (data->reactable, g_value_get_double (value));
|
||||
break;
|
||||
case _EVENT (VOLUME_CHANGED):
|
||||
if (reactable_iface->volume_changed)
|
||||
reactable_iface->volume_changed (data->reactable, g_value_get_double (value));
|
||||
break;
|
||||
case _EVENT (MUTE_CHANGED):
|
||||
if (reactable_iface->mute_changed)
|
||||
reactable_iface->mute_changed (data->reactable, g_value_get_boolean (value));
|
||||
break;
|
||||
case _EVENT (PLAYED_ITEM_CHANGED):
|
||||
if (reactable_iface->played_item_changed) {
|
||||
reactable_iface->played_item_changed (data->reactable,
|
||||
CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)));
|
||||
}
|
||||
break;
|
||||
case _EVENT (ITEM_UPDATED):
|
||||
if (reactable_iface->item_updated) {
|
||||
reactable_iface->item_updated (data->reactable,
|
||||
CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)),
|
||||
g_value_get_flags (extra_value));
|
||||
}
|
||||
break;
|
||||
case _EVENT (QUEUE_ITEM_ADDED):
|
||||
if (reactable_iface->queue_item_added) {
|
||||
reactable_iface->queue_item_added (data->reactable,
|
||||
CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)),
|
||||
g_value_get_uint (extra_value));
|
||||
}
|
||||
break;
|
||||
case _EVENT (QUEUE_ITEM_REMOVED):
|
||||
if (reactable_iface->queue_item_removed) {
|
||||
reactable_iface->queue_item_removed (data->reactable,
|
||||
CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)),
|
||||
g_value_get_uint (extra_value));
|
||||
}
|
||||
break;
|
||||
case _EVENT (QUEUE_ITEM_REPOSITIONED):
|
||||
if (reactable_iface->queue_item_repositioned) {
|
||||
reactable_iface->queue_item_repositioned (data->reactable,
|
||||
g_value_get_uint (value),
|
||||
g_value_get_uint (extra_value));
|
||||
}
|
||||
break;
|
||||
case _EVENT (QUEUE_CLEARED):
|
||||
if (reactable_iface->queue_cleared)
|
||||
reactable_iface->queue_cleared (data->reactable);
|
||||
break;
|
||||
case _EVENT (QUEUE_PROGRESSION_CHANGED):
|
||||
if (reactable_iface->queue_progression_changed)
|
||||
reactable_iface->queue_progression_changed (data->reactable, g_value_get_int (value));
|
||||
break;
|
||||
default:
|
||||
GST_ERROR_OBJECT (self, "Invalid event ID on reactables bus: %u", event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
if (G_LIKELY (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_APPLICATION)) {
|
||||
ClapperReactablesManager *self = CLAPPER_REACTABLES_MANAGER_CAST (GST_MESSAGE_SRC (msg));
|
||||
const GstStructure *structure = gst_message_get_structure (msg);
|
||||
GQuark quark = gst_structure_get_name_id (structure);
|
||||
|
||||
if (quark == _QUARK (EVENT)) {
|
||||
clapper_reactables_manager_handle_event (self, structure);
|
||||
} else if (quark == _QUARK (PREPARE)) {
|
||||
clapper_reactables_manager_handle_prepare (self);
|
||||
} else if (quark == _QUARK (CONFIGURE)) {
|
||||
clapper_reactables_manager_handle_configure (self, structure);
|
||||
} else {
|
||||
GST_ERROR_OBJECT (self, "Received invalid quark on reactables bus!");
|
||||
}
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_bus_post_event (ClapperReactablesManager *self, guint event_id,
|
||||
GValue *value, GValue *extra_value)
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id (_QUARK (EVENT),
|
||||
_QUARK (EVENT), G_TYPE_ENUM, event_id,
|
||||
NULL);
|
||||
|
||||
if (value)
|
||||
gst_structure_id_take_value (structure, _QUARK (VALUE), value);
|
||||
if (extra_value)
|
||||
gst_structure_id_take_value (structure, _QUARK (EXTRA_VALUE), extra_value);
|
||||
|
||||
gst_bus_post (self->bus, gst_message_new_application (
|
||||
GST_OBJECT_CAST (self), structure));
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_reactables_manager_new:
|
||||
*
|
||||
* Returns: (transfer full): a new #ClapperReactablesManager instance.
|
||||
*/
|
||||
ClapperReactablesManager *
|
||||
clapper_reactables_manager_new (void)
|
||||
{
|
||||
ClapperReactablesManager *reactables_manager;
|
||||
|
||||
reactables_manager = g_object_new (CLAPPER_TYPE_REACTABLES_MANAGER, NULL);
|
||||
gst_object_ref_sink (reactables_manager);
|
||||
|
||||
return reactables_manager;
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_prepare (ClapperReactablesManager *self)
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id_empty (_QUARK (PREPARE));
|
||||
|
||||
gst_bus_post (self->bus, gst_message_new_application (
|
||||
GST_OBJECT_CAST (self), structure));
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_configure_take_config (ClapperReactablesManager *self,
|
||||
ClapperEnhancerProxy *proxy, GstStructure *config)
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id (_QUARK (CONFIGURE),
|
||||
_QUARK (VALUE), G_TYPE_OBJECT, proxy, NULL);
|
||||
GValue extra_value = G_VALUE_INIT;
|
||||
|
||||
g_value_init (&extra_value, GST_TYPE_STRUCTURE);
|
||||
g_value_take_boxed (&extra_value, config);
|
||||
|
||||
gst_structure_id_take_value (structure, _QUARK (EXTRA_VALUE), &extra_value);
|
||||
|
||||
gst_bus_post (self->bus, gst_message_new_application (
|
||||
GST_OBJECT_CAST (self), structure));
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_state_changed (ClapperReactablesManager *self, ClapperPlayerState state)
|
||||
{
|
||||
_BUS_POST_EVENT_SINGLE (_EVENT (STATE_CHANGED), int, G_TYPE_INT, state);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_position_changed (ClapperReactablesManager *self, gdouble position)
|
||||
{
|
||||
_BUS_POST_EVENT_SINGLE (_EVENT (POSITION_CHANGED), double, G_TYPE_DOUBLE, position);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_speed_changed (ClapperReactablesManager *self, gdouble speed)
|
||||
{
|
||||
_BUS_POST_EVENT_SINGLE (_EVENT (SPEED_CHANGED), double, G_TYPE_DOUBLE, speed);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_volume_changed (ClapperReactablesManager *self, gdouble volume)
|
||||
{
|
||||
_BUS_POST_EVENT_SINGLE (_EVENT (VOLUME_CHANGED), double, G_TYPE_DOUBLE, volume);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_mute_changed (ClapperReactablesManager *self, gboolean mute)
|
||||
{
|
||||
_BUS_POST_EVENT_SINGLE (_EVENT (MUTE_CHANGED), boolean, G_TYPE_BOOLEAN, mute);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_played_item_changed (ClapperReactablesManager *self, ClapperMediaItem *item)
|
||||
{
|
||||
_BUS_POST_EVENT_SINGLE (_EVENT (PLAYED_ITEM_CHANGED), object, CLAPPER_TYPE_MEDIA_ITEM, item);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_item_updated (ClapperReactablesManager *self, ClapperMediaItem *item, ClapperReactableItemUpdatedFlags _flags)
|
||||
{
|
||||
_BUS_POST_EVENT_DUAL (_EVENT (ITEM_UPDATED), object, CLAPPER_TYPE_MEDIA_ITEM, item, flags, CLAPPER_TYPE_REACTABLE_ITEM_UPDATED_FLAGS, _flags);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_queue_item_added (ClapperReactablesManager *self, ClapperMediaItem *item, guint index)
|
||||
{
|
||||
_BUS_POST_EVENT_DUAL (_EVENT (QUEUE_ITEM_ADDED), object, CLAPPER_TYPE_MEDIA_ITEM, item, uint, G_TYPE_UINT, index);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_queue_item_removed (ClapperReactablesManager *self, ClapperMediaItem *item, guint index)
|
||||
{
|
||||
_BUS_POST_EVENT_DUAL (_EVENT (QUEUE_ITEM_REMOVED), object, CLAPPER_TYPE_MEDIA_ITEM, item, uint, G_TYPE_UINT, index);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_queue_item_repositioned (ClapperReactablesManager *self, guint before, guint after)
|
||||
{
|
||||
_BUS_POST_EVENT_DUAL (_EVENT (QUEUE_ITEM_REPOSITIONED), uint, G_TYPE_UINT, before, uint, G_TYPE_UINT, after);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_queue_cleared (ClapperReactablesManager *self)
|
||||
{
|
||||
_bus_post_event (self, _EVENT (QUEUE_CLEARED), NULL, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_reactables_manager_trigger_queue_progression_changed (ClapperReactablesManager *self, ClapperQueueProgressionMode mode)
|
||||
{
|
||||
_BUS_POST_EVENT_SINGLE (_EVENT (QUEUE_PROGRESSION_CHANGED), int, G_TYPE_INT, mode);
|
||||
}
|
||||
|
||||
static void
|
||||
_data_remove_func (ClapperReactableManagerData *data)
|
||||
{
|
||||
GST_TRACE ("Removing data for reactable: %" GST_PTR_FORMAT, data->reactable);
|
||||
|
||||
g_clear_object (&data->settings);
|
||||
|
||||
gst_object_unparent (GST_OBJECT_CAST (data->reactable));
|
||||
gst_object_unref (data->reactable);
|
||||
|
||||
gst_object_unref (data->proxy);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_reactables_manager_thread_start (ClapperThreadedObject *threaded_object)
|
||||
{
|
||||
ClapperReactablesManager *self = CLAPPER_REACTABLES_MANAGER_CAST (threaded_object);
|
||||
|
||||
GST_TRACE_OBJECT (threaded_object, "Reactables manager thread start");
|
||||
|
||||
self->array = g_ptr_array_new_with_free_func (
|
||||
(GDestroyNotify) _data_remove_func);
|
||||
|
||||
self->bus = gst_bus_new ();
|
||||
gst_bus_add_watch (self->bus, (GstBusFunc) _bus_message_func, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_reactables_manager_thread_stop (ClapperThreadedObject *threaded_object)
|
||||
{
|
||||
ClapperReactablesManager *self = CLAPPER_REACTABLES_MANAGER_CAST (threaded_object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Reactables manager thread stop");
|
||||
|
||||
gst_bus_set_flushing (self->bus, TRUE);
|
||||
gst_bus_remove_watch (self->bus);
|
||||
gst_clear_object (&self->bus);
|
||||
|
||||
g_ptr_array_unref (self->array);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_reactables_manager_init (ClapperReactablesManager *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_reactables_manager_finalize (GObject *object)
|
||||
{
|
||||
GST_TRACE_OBJECT (object, "Finalize");
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_reactables_manager_class_init (ClapperReactablesManagerClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
ClapperThreadedObjectClass *threaded_object = (ClapperThreadedObjectClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperreactablesmanager", 0,
|
||||
"Clapper Reactables Manager");
|
||||
|
||||
gobject_class->finalize = clapper_reactables_manager_finalize;
|
||||
|
||||
threaded_object->thread_start = clapper_reactables_manager_thread_start;
|
||||
threaded_object->thread_stop = clapper_reactables_manager_thread_stop;
|
||||
}
|
@@ -52,7 +52,7 @@ G_DEFINE_TYPE_WITH_PRIVATE (ClapperThreadedObject, clapper_threaded_object, GST_
|
||||
* Useful when you want to invoke object thread to do some
|
||||
* action in it from a different thread.
|
||||
*
|
||||
* Returns: a #GMainContext of the object used thread.
|
||||
* Returns: (transfer none): a #GMainContext of the object used thread.
|
||||
*/
|
||||
GMainContext *
|
||||
clapper_threaded_object_get_context (ClapperThreadedObject *self)
|
||||
|
@@ -29,6 +29,8 @@
|
||||
#include "clapper-timeline-private.h"
|
||||
#include "clapper-marker-private.h"
|
||||
#include "clapper-player-private.h"
|
||||
#include "clapper-reactables-manager-private.h"
|
||||
#include "clapper-features-manager-private.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_timeline_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
@@ -106,15 +108,19 @@ clapper_timeline_post_item_updated (ClapperTimeline *self)
|
||||
ClapperPlayer *player;
|
||||
|
||||
if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) {
|
||||
ClapperFeaturesManager *features_manager;
|
||||
ClapperMediaItem *item;
|
||||
|
||||
if ((features_manager = clapper_player_get_features_manager (player))) {
|
||||
ClapperMediaItem *item;
|
||||
if ((item = CLAPPER_MEDIA_ITEM_CAST (gst_object_get_parent (GST_OBJECT_CAST (self))))) {
|
||||
ClapperFeaturesManager *features_manager;
|
||||
|
||||
if ((item = CLAPPER_MEDIA_ITEM (gst_object_get_parent (GST_OBJECT_CAST (self))))) {
|
||||
clapper_features_manager_trigger_item_updated (features_manager, item);
|
||||
gst_object_unref (item);
|
||||
if (player->reactables_manager) {
|
||||
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, item,
|
||||
CLAPPER_REACTABLE_ITEM_UPDATED_TIMELINE);
|
||||
}
|
||||
if ((features_manager = clapper_player_get_features_manager (player)))
|
||||
clapper_features_manager_trigger_item_updated (features_manager, item);
|
||||
|
||||
gst_object_unref (item);
|
||||
}
|
||||
|
||||
gst_object_unref (player);
|
||||
|
@@ -45,10 +45,16 @@ void clapper_utils_queue_remove_on_main_sync (ClapperQueue *queue, ClapperMediaI
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_utils_prop_notify_on_main_sync (GObject *object, GParamSpec *pspec);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gchar * clapper_utils_uri_from_file (GFile *file);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gchar * clapper_utils_title_from_uri (const gchar *uri);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_utils_set_value_from_variant (GValue *value, GVariant *variant);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -39,6 +39,12 @@ typedef struct
|
||||
ClapperUtilsQueueAlterMethod method;
|
||||
} ClapperUtilsQueueAlterData;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GObject *object;
|
||||
GParamSpec *pspec;
|
||||
} ClapperUtilsPropNotifyData;
|
||||
|
||||
void
|
||||
clapper_utils_initialize (void)
|
||||
{
|
||||
@@ -71,6 +77,27 @@ clapper_utils_queue_alter_data_free (ClapperUtilsQueueAlterData *data)
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static ClapperUtilsPropNotifyData *
|
||||
clapper_utils_prop_notify_data_new (GObject *object, GParamSpec *pspec)
|
||||
{
|
||||
ClapperUtilsPropNotifyData *data = g_new (ClapperUtilsPropNotifyData, 1);
|
||||
|
||||
data->object = object;
|
||||
data->pspec = pspec;
|
||||
|
||||
GST_TRACE ("Created prop notify data: %p", data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_utils_prop_notify_data_free (ClapperUtilsPropNotifyData *data)
|
||||
{
|
||||
GST_TRACE ("Freeing prop notify data: %p", data);
|
||||
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
clapper_utils_queue_alter_on_main (ClapperUtilsQueueAlterData *data)
|
||||
{
|
||||
@@ -110,6 +137,15 @@ clapper_utils_queue_alter_on_main (ClapperUtilsQueueAlterData *data)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
clapper_utils_prop_notify_on_main (ClapperUtilsPropNotifyData *data)
|
||||
{
|
||||
GST_DEBUG ("Prop notify invoked");
|
||||
g_object_notify_by_pspec (data->object, data->pspec);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void
|
||||
clapper_utils_queue_alter_invoke_on_main_sync_take (ClapperUtilsQueueAlterData *data)
|
||||
{
|
||||
@@ -155,6 +191,27 @@ clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue)
|
||||
clapper_utils_queue_alter_invoke_on_main_sync_take (data);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_utils_prop_notify_on_main_sync (GObject *object, GParamSpec *pspec)
|
||||
{
|
||||
ClapperUtilsPropNotifyData *data;
|
||||
|
||||
if (g_main_context_is_owner (g_main_context_default ())) { // already in main thread
|
||||
g_object_notify_by_pspec (object, pspec);
|
||||
return;
|
||||
}
|
||||
|
||||
data = clapper_utils_prop_notify_data_new (object, pspec);
|
||||
|
||||
GST_DEBUG ("Invoking prop notify on main...");
|
||||
|
||||
clapper_shared_utils_context_invoke_sync_full (g_main_context_default (),
|
||||
(GThreadFunc) clapper_utils_prop_notify_on_main, data,
|
||||
(GDestroyNotify) clapper_utils_prop_notify_data_free);
|
||||
|
||||
GST_DEBUG ("Prop notify invoke finished");
|
||||
}
|
||||
|
||||
gchar *
|
||||
clapper_utils_uri_from_file (GFile *file)
|
||||
{
|
||||
@@ -214,3 +271,59 @@ clapper_utils_title_from_uri (const gchar *uri)
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_utils_set_value_from_variant (GValue *value, GVariant *variant)
|
||||
{
|
||||
const gchar *var_type = g_variant_get_type_string (variant);
|
||||
GType val_type;
|
||||
|
||||
switch (var_type[0]) {
|
||||
case 'b':
|
||||
val_type = G_TYPE_BOOLEAN;
|
||||
break;
|
||||
case 'i':
|
||||
val_type = G_TYPE_INT;
|
||||
break;
|
||||
case 'u':
|
||||
val_type = G_TYPE_UINT;
|
||||
break;
|
||||
case 'd':
|
||||
val_type = G_TYPE_DOUBLE;
|
||||
break;
|
||||
case 's':
|
||||
val_type = G_TYPE_STRING;
|
||||
break;
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
|
||||
g_value_init (value, val_type);
|
||||
|
||||
switch (val_type) {
|
||||
case G_TYPE_BOOLEAN:
|
||||
g_value_set_boolean (value, g_variant_get_boolean (variant));
|
||||
break;
|
||||
case G_TYPE_INT:
|
||||
g_value_set_int (value, g_variant_get_int32 (variant));
|
||||
break;
|
||||
case G_TYPE_UINT:
|
||||
g_value_set_uint (value, g_variant_get_uint32 (variant));
|
||||
break;
|
||||
case G_TYPE_DOUBLE:
|
||||
g_value_set_double (value, g_variant_get_double (variant));
|
||||
break;
|
||||
case G_TYPE_STRING:
|
||||
g_value_set_string (value, g_variant_get_string (variant, NULL));
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
||||
error:
|
||||
GST_ERROR ("Unsupported conversion for variant type: %s", var_type);
|
||||
return FALSE;
|
||||
}
|
||||
|
@@ -19,9 +19,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#define __CLAPPER_INSIDE__
|
||||
|
||||
#include <clapper/clapper-visibility.h>
|
||||
@@ -30,6 +27,9 @@
|
||||
#include <clapper/clapper-version.h>
|
||||
|
||||
#include <clapper/clapper-audio-stream.h>
|
||||
#include <clapper/clapper-basic-functions.h>
|
||||
#include <clapper/clapper-enhancer-proxy.h>
|
||||
#include <clapper/clapper-enhancer-proxy-list.h>
|
||||
#include <clapper/clapper-feature.h>
|
||||
#include <clapper/clapper-harvest.h>
|
||||
#include <clapper/clapper-marker.h>
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <clapper/clapper-video-stream.h>
|
||||
|
||||
#include <clapper/clapper-extractable.h>
|
||||
#include <clapper/clapper-reactable.h>
|
||||
|
||||
#include <clapper/clapper-functionalities-availability.h>
|
||||
#include <clapper/features/clapper-features-availability.h>
|
||||
@@ -59,17 +60,4 @@
|
||||
#include <clapper/features/server/clapper-server.h>
|
||||
#endif
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
CLAPPER_API
|
||||
void clapper_init (int *argc, char **argv[]);
|
||||
|
||||
CLAPPER_API
|
||||
gboolean clapper_init_check (int *argc, char **argv[]);
|
||||
|
||||
CLAPPER_API
|
||||
gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#undef __CLAPPER_INSIDE__
|
||||
|
@@ -38,6 +38,6 @@ G_GNUC_INTERNAL
|
||||
ClapperEnhancerDirector * clapper_enhancer_director_new (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GUri *uri, GCancellable *cancellable, GError **error);
|
||||
ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GList *filtered_proxies, GUri *uri, GCancellable *cancellable, GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -17,14 +17,27 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "clapper-enhancer-director-private.h"
|
||||
#include "../clapper-enhancers-loader-private.h"
|
||||
#include "../clapper-basic-functions.h"
|
||||
#include "../clapper-cache-private.h"
|
||||
#include "../clapper-enhancer-proxy-private.h"
|
||||
#include "../clapper-extractable-private.h"
|
||||
#include "../clapper-harvest-private.h"
|
||||
#include "../clapper-utils.h"
|
||||
#include "../../shared/clapper-shared-utils-private.h"
|
||||
|
||||
#include "../clapper-functionalities-availability.h"
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
#include "../clapper-enhancers-loader-private.h"
|
||||
#endif
|
||||
|
||||
#define CLEANUP_INTERVAL 10800 // once every 3 hours
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_enhancer_director_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
@@ -38,6 +51,8 @@ G_DEFINE_TYPE (ClapperEnhancerDirector, clapper_enhancer_director, CLAPPER_TYPE_
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ClapperEnhancerDirector *director;
|
||||
GList *filtered_proxies;
|
||||
GUri *uri;
|
||||
GCancellable *cancellable;
|
||||
GError **error;
|
||||
@@ -46,42 +61,67 @@ typedef struct
|
||||
static gpointer
|
||||
clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data)
|
||||
{
|
||||
ClapperExtractable *extractable = NULL;
|
||||
ClapperHarvest *harvest = clapper_harvest_new ();
|
||||
gboolean success = FALSE, cached = FALSE;
|
||||
ClapperEnhancerDirector *self = data->director;
|
||||
GList *el;
|
||||
ClapperHarvest *harvest = NULL;
|
||||
gboolean success = FALSE;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Extraction start");
|
||||
|
||||
/* Cancelled during thread switching */
|
||||
if (g_cancellable_is_cancelled (data->cancellable))
|
||||
goto finish;
|
||||
return NULL;
|
||||
|
||||
/* TODO: Cache lookup */
|
||||
if (cached) {
|
||||
// success = fill harvest from cache
|
||||
goto finish;
|
||||
}
|
||||
GST_DEBUG_OBJECT (self, "Enhancer proxies for URI: %u",
|
||||
g_list_length (data->filtered_proxies));
|
||||
|
||||
extractable = CLAPPER_EXTRACTABLE_CAST (clapper_enhancers_loader_create_enhancer_for_uri (
|
||||
CLAPPER_TYPE_EXTRACTABLE, data->uri));
|
||||
for (el = data->filtered_proxies; el; el = g_list_next (el)) {
|
||||
ClapperEnhancerProxy *proxy = CLAPPER_ENHANCER_PROXY_CAST (el->data);
|
||||
ClapperExtractable *extractable = NULL;
|
||||
GstStructure *config;
|
||||
|
||||
/* Check just before extract */
|
||||
if (g_cancellable_is_cancelled (data->cancellable))
|
||||
goto finish;
|
||||
harvest = clapper_harvest_new (); // fresh harvest for each iteration
|
||||
config = clapper_enhancer_proxy_make_current_config (proxy);
|
||||
|
||||
success = clapper_extractable_extract (extractable, data->uri,
|
||||
harvest, data->cancellable, data->error);
|
||||
|
||||
/* Cancelled during extract */
|
||||
if (g_cancellable_is_cancelled (data->cancellable)) {
|
||||
success = FALSE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
finish:
|
||||
if (success) {
|
||||
if (!cached) {
|
||||
/* TODO: Store in cache */
|
||||
if ((success = clapper_harvest_fill_from_cache (harvest, proxy, config, data->uri))
|
||||
|| g_cancellable_is_cancelled (data->cancellable)) { // Check before extract
|
||||
gst_clear_structure (&config);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
extractable = CLAPPER_EXTRACTABLE_CAST (
|
||||
clapper_enhancers_loader_create_enhancer (proxy, CLAPPER_TYPE_EXTRACTABLE));
|
||||
#endif
|
||||
|
||||
if (G_LIKELY (extractable != NULL)) {
|
||||
if (config)
|
||||
clapper_enhancer_proxy_apply_config_to_enhancer (proxy, config, (GObject *) extractable);
|
||||
|
||||
success = clapper_extractable_extract (extractable, data->uri,
|
||||
harvest, data->cancellable, data->error);
|
||||
gst_object_unref (extractable);
|
||||
|
||||
/* We are done with extractable, but keep harvest and try to cache it */
|
||||
if (success) {
|
||||
if (!g_cancellable_is_cancelled (data->cancellable))
|
||||
clapper_harvest_export_to_cache (harvest, proxy, config, data->uri);
|
||||
|
||||
gst_clear_structure (&config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup to try again with next enhancer */
|
||||
g_clear_object (&harvest);
|
||||
gst_clear_structure (&config);
|
||||
}
|
||||
|
||||
/* Cancelled during extraction or exporting to cache */
|
||||
if (g_cancellable_is_cancelled (data->cancellable))
|
||||
success = FALSE;
|
||||
|
||||
if (!success) {
|
||||
gst_clear_object (&harvest);
|
||||
|
||||
/* Ensure we have some error set on failure */
|
||||
@@ -94,11 +134,176 @@ finish:
|
||||
}
|
||||
}
|
||||
|
||||
gst_clear_object (&extractable);
|
||||
GST_DEBUG_OBJECT (self, "Extraction finish");
|
||||
|
||||
return harvest;
|
||||
}
|
||||
|
||||
static inline void
|
||||
_harvest_delete_if_expired (ClapperEnhancerDirector *self,
|
||||
ClapperEnhancerProxy *proxy, GFile *file, const gint64 epoch_now)
|
||||
{
|
||||
GMappedFile *mapped_file;
|
||||
const gchar *data;
|
||||
gchar *filename;
|
||||
GError *error = NULL;
|
||||
gboolean delete = TRUE;
|
||||
|
||||
filename = g_file_get_path (file);
|
||||
|
||||
if ((mapped_file = clapper_cache_open (filename, &data, &error))) {
|
||||
/* Do not delete if versions match and not expired */
|
||||
if (g_strcmp0 (clapper_cache_read_string (&data),
|
||||
clapper_enhancer_proxy_get_version (proxy)) == 0
|
||||
&& clapper_cache_read_int64 (&data) > epoch_now) {
|
||||
delete = FALSE;
|
||||
}
|
||||
g_mapped_file_unref (mapped_file);
|
||||
} else if (error) {
|
||||
if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT)
|
||||
GST_DEBUG_OBJECT (self, "No cached harvest file found");
|
||||
else
|
||||
GST_ERROR_OBJECT (self, "Could not read cached harvest file, reason: %s", error->message);
|
||||
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
if (delete) {
|
||||
if (G_LIKELY (g_file_delete (file, NULL, &error))) {
|
||||
GST_TRACE_OBJECT (self, "Deleted cached harvest: \"%s\"", filename);
|
||||
} else {
|
||||
GST_ERROR_OBJECT (self, "Could not delete harvest: \"%s\", reason: %s",
|
||||
filename, GST_STR_NULL (error->message));
|
||||
g_error_free (error);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (filename);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_cache_proxy_harvests_cleanup (ClapperEnhancerDirector *self,
|
||||
ClapperEnhancerProxy *proxy, const gint64 epoch_now)
|
||||
{
|
||||
GFile *dir;
|
||||
GFileEnumerator *dir_enum;
|
||||
GError *error = NULL;
|
||||
|
||||
dir = g_file_new_build_filename (g_get_user_cache_dir (), CLAPPER_API_NAME,
|
||||
"enhancers", clapper_enhancer_proxy_get_module_name (proxy),
|
||||
"harvests", NULL);
|
||||
|
||||
if ((dir_enum = g_file_enumerate_children (dir,
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error))) {
|
||||
while (TRUE) {
|
||||
GFileInfo *info = NULL;
|
||||
GFile *child = NULL;
|
||||
|
||||
if (!g_file_enumerator_iterate (dir_enum, &info,
|
||||
&child, NULL, &error) || !info)
|
||||
break;
|
||||
|
||||
if (G_LIKELY (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR
|
||||
&& g_str_has_suffix (g_file_info_get_name (info), ".bin")))
|
||||
_harvest_delete_if_expired (self, proxy, child, epoch_now);
|
||||
}
|
||||
|
||||
g_object_unref (dir_enum);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_NOT_FOUND) {
|
||||
gchar *path = g_file_get_path (dir);
|
||||
|
||||
GST_ERROR_OBJECT (self, "Could not cleanup in dir: \"%s\", reason: %s",
|
||||
path, GST_STR_NULL (error->message));
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_object_unref (dir);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_cache_cleanup_func (ClapperEnhancerDirector *self)
|
||||
{
|
||||
GMappedFile *mapped_file;
|
||||
GDateTime *date;
|
||||
GError *error = NULL;
|
||||
gchar *filename;
|
||||
const gchar *data;
|
||||
gint64 since_cleanup, epoch_now, epoch_last = 0;
|
||||
|
||||
date = g_date_time_new_now_utc ();
|
||||
epoch_now = g_date_time_to_unix (date);
|
||||
g_date_time_unref (date);
|
||||
|
||||
filename = g_build_filename (g_get_user_cache_dir (), CLAPPER_API_NAME,
|
||||
"enhancers", "cleanup.bin", NULL);
|
||||
|
||||
if ((mapped_file = clapper_cache_open (filename, &data, &error))) {
|
||||
epoch_last = clapper_cache_read_int64 (&data);
|
||||
g_mapped_file_unref (mapped_file);
|
||||
} else if (error) {
|
||||
if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT)
|
||||
GST_DEBUG_OBJECT (self, "No cache cleanup file found");
|
||||
else
|
||||
GST_ERROR_OBJECT (self, "Could not read cache cleanup file, reason: %s", error->message);
|
||||
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
since_cleanup = epoch_now - epoch_last;
|
||||
|
||||
if (since_cleanup >= CLEANUP_INTERVAL) {
|
||||
ClapperEnhancerProxyList *proxies;
|
||||
guint i, n_proxies;
|
||||
GByteArray *bytes;
|
||||
|
||||
GST_TRACE_OBJECT (self, "Time for cache cleanup, last was %"
|
||||
CLAPPER_TIME_FORMAT " ago", CLAPPER_TIME_ARGS (since_cleanup));
|
||||
|
||||
/* Start with writing to cache cleanup time,
|
||||
* so other directors can find it earlier */
|
||||
if ((bytes = clapper_cache_create ())) {
|
||||
clapper_cache_store_int64 (bytes, epoch_now);
|
||||
|
||||
if (clapper_cache_write (filename, bytes, &error)) {
|
||||
GST_TRACE_OBJECT (self, "Written data to cache cleanup file, cleanup time: %"
|
||||
G_GINT64_FORMAT, epoch_now);
|
||||
} else if (error) {
|
||||
GST_ERROR_OBJECT (self, "Could not write cache cleanup data, reason: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_byte_array_free (bytes, TRUE);
|
||||
}
|
||||
|
||||
/* Now do cleanup */
|
||||
proxies = clapper_get_global_enhancer_proxies ();
|
||||
n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
|
||||
|
||||
for (i = 0; i < n_proxies; ++i) {
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
|
||||
|
||||
if (!clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE))
|
||||
continue;
|
||||
|
||||
_cache_proxy_harvests_cleanup (self, proxy, epoch_now);
|
||||
}
|
||||
} else {
|
||||
GST_TRACE_OBJECT (self, "No cache cleanup yet, last was %"
|
||||
CLAPPER_TIME_FORMAT " ago", CLAPPER_TIME_ARGS (since_cleanup));
|
||||
}
|
||||
|
||||
g_free (filename);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/*
|
||||
* clapper_enhancer_director_new:
|
||||
*
|
||||
@@ -116,19 +321,32 @@ clapper_enhancer_director_new (void)
|
||||
}
|
||||
|
||||
ClapperHarvest *
|
||||
clapper_enhancer_director_extract (ClapperEnhancerDirector *self, GUri *uri,
|
||||
clapper_enhancer_director_extract (ClapperEnhancerDirector *self,
|
||||
GList *filtered_proxies, GUri *uri,
|
||||
GCancellable *cancellable, GError **error)
|
||||
{
|
||||
ClapperEnhancerDirectorData *data = g_new (ClapperEnhancerDirectorData, 1);
|
||||
GMainContext *context;
|
||||
ClapperHarvest *harvest;
|
||||
|
||||
data->director = self;
|
||||
data->filtered_proxies = filtered_proxies;
|
||||
data->uri = uri;
|
||||
data->cancellable = cancellable;
|
||||
data->error = error;
|
||||
|
||||
return CLAPPER_HARVEST_CAST (clapper_shared_utils_context_invoke_sync_full (
|
||||
clapper_threaded_object_get_context (CLAPPER_THREADED_OBJECT_CAST (self)),
|
||||
context = clapper_threaded_object_get_context (CLAPPER_THREADED_OBJECT_CAST (self));
|
||||
|
||||
harvest = CLAPPER_HARVEST_CAST (clapper_shared_utils_context_invoke_sync_full (context,
|
||||
(GThreadFunc) clapper_enhancer_director_extract_in_thread,
|
||||
data, (GDestroyNotify) g_free));
|
||||
|
||||
/* Run cleanup async. Since context belongs to "self", do not ref it.
|
||||
* This ensures clean shutdown with thread stop function called. */
|
||||
if (!g_cancellable_is_cancelled (cancellable) && !clapper_cache_is_disabled ())
|
||||
g_main_context_invoke (context, (GSourceFunc) _cache_cleanup_func, self);
|
||||
|
||||
return harvest;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -1,495 +0,0 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "clapper-enhancer-src-private.h"
|
||||
#include "clapper-enhancer-director-private.h"
|
||||
|
||||
#include "../clapper-extractable-private.h"
|
||||
#include "../clapper-harvest-private.h"
|
||||
#include "../clapper-enhancers-loader-private.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_enhancer_src_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _ClapperEnhancerSrc
|
||||
{
|
||||
GstPushSrc parent;
|
||||
|
||||
GCancellable *cancellable;
|
||||
gsize buf_size;
|
||||
|
||||
ClapperEnhancerDirector *director;
|
||||
|
||||
gchar *uri;
|
||||
GUri *guri;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_URI,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS_ANY);
|
||||
|
||||
static GstURIType
|
||||
clapper_enhancer_src_uri_handler_get_type (GType type)
|
||||
{
|
||||
return GST_URI_SRC;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
_get_schemes_once (gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
return clapper_enhancers_loader_get_schemes (CLAPPER_TYPE_EXTRACTABLE);
|
||||
}
|
||||
|
||||
static const gchar *const *
|
||||
clapper_enhancer_src_uri_handler_get_protocols (GType type)
|
||||
{
|
||||
static GOnce schemes_once = G_ONCE_INIT;
|
||||
|
||||
g_once (&schemes_once, _get_schemes_once, NULL);
|
||||
return (const gchar *const *) schemes_once.retval;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
clapper_enhancer_src_uri_handler_get_uri (GstURIHandler *handler)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (handler);
|
||||
gchar *uri;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
uri = g_strdup (self->uri);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_enhancer_src_uri_handler_set_uri (GstURIHandler *handler,
|
||||
const gchar *uri, GError **error)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (handler);
|
||||
GUri *guri;
|
||||
const gchar *const *protocols;
|
||||
gboolean supported = FALSE;
|
||||
guint i;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Changing URI to: %s", uri);
|
||||
|
||||
if (!uri) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
|
||||
"URI property cannot be NULL");
|
||||
return FALSE;
|
||||
}
|
||||
if (GST_STATE (GST_ELEMENT_CAST (self)) >= GST_STATE_PAUSED) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
|
||||
"Cannot change URI property while element is running");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
protocols = gst_uri_handler_get_protocols (handler);
|
||||
for (i = 0; protocols[i]; ++i) {
|
||||
if ((supported = gst_uri_has_protocol (uri, protocols[i])))
|
||||
break;
|
||||
}
|
||||
if (!supported) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
|
||||
"URI protocol is not supported");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!(guri = g_uri_parse (uri, G_URI_FLAGS_ENCODED, NULL))) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
|
||||
"URI is invalid");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!clapper_enhancers_loader_check (CLAPPER_TYPE_EXTRACTABLE,
|
||||
g_uri_get_scheme (guri), g_uri_get_host (guri), NULL)) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
|
||||
"None of the available enhancers can handle this URI");
|
||||
g_uri_unref (guri);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
g_set_str (&self->uri, uri);
|
||||
g_clear_pointer (&self->guri, g_uri_unref);
|
||||
self->guri = guri;
|
||||
|
||||
GST_INFO_OBJECT (self, "URI changed to: \"%s\"", self->uri);
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_uri_handler_iface_init (GstURIHandlerInterface *iface)
|
||||
{
|
||||
iface->get_type = clapper_enhancer_src_uri_handler_get_type;
|
||||
iface->get_protocols = clapper_enhancer_src_uri_handler_get_protocols;
|
||||
iface->get_uri = clapper_enhancer_src_uri_handler_get_uri;
|
||||
iface->set_uri = clapper_enhancer_src_uri_handler_set_uri;
|
||||
}
|
||||
|
||||
#define parent_class clapper_enhancer_src_parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (ClapperEnhancerSrc, clapper_enhancer_src, GST_TYPE_PUSH_SRC,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, _uri_handler_iface_init));
|
||||
GST_ELEMENT_REGISTER_DEFINE (clapperenhancersrc, "clapperenhancersrc",
|
||||
512, CLAPPER_TYPE_ENHANCER_SRC);
|
||||
|
||||
static gboolean
|
||||
clapper_enhancer_src_start (GstBaseSrc *base_src)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
|
||||
gboolean can_start;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Start");
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
can_start = (self->guri != NULL);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (G_UNLIKELY (!can_start)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
|
||||
("No media URI"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_enhancer_src_stop (GstBaseSrc *base_src)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Stop");
|
||||
|
||||
self->buf_size = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_enhancer_src_get_size (GstBaseSrc *base_src, guint64 *size)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
|
||||
|
||||
if (self->buf_size > 0) {
|
||||
*size = self->buf_size;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_enhancer_src_is_seekable (GstBaseSrc *base_src)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_enhancer_src_unlock (GstBaseSrc *base_src)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
|
||||
|
||||
GST_LOG_OBJECT (self, "Cancel triggered");
|
||||
g_cancellable_cancel (self->cancellable);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_enhancer_src_unlock_stop (GstBaseSrc *base_src)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
|
||||
|
||||
GST_LOG_OBJECT (self, "Resetting cancellable");
|
||||
|
||||
g_object_unref (self->cancellable);
|
||||
self->cancellable = g_cancellable_new ();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Pushes tags, toc and request headers downstream (all transfer full) */
|
||||
static void
|
||||
_push_events (ClapperEnhancerSrc *self, GstTagList *tags, GstToc *toc,
|
||||
GstStructure *headers, gboolean updated)
|
||||
{
|
||||
GstEvent *event;
|
||||
|
||||
if (tags) {
|
||||
if (!gst_tag_list_is_empty (tags)) {
|
||||
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, tags);
|
||||
|
||||
/* XXX: Normally, we should only be posting event to make it reach
|
||||
* the app after stream start, but currently it is lost that way */
|
||||
gst_element_post_message (GST_ELEMENT (self),
|
||||
gst_message_new_tag (GST_OBJECT_CAST (self), tags));
|
||||
} else {
|
||||
gst_tag_list_unref (tags);
|
||||
}
|
||||
}
|
||||
|
||||
if (toc) {
|
||||
if (g_list_length (gst_toc_get_entries (toc)) > 0) {
|
||||
GST_DEBUG_OBJECT (self, "Pushing TOC"); // TOC is not printable
|
||||
|
||||
/* XXX: Normally, we should only be posting event to make it reach
|
||||
* the app after stream start, but currently it is lost that way */
|
||||
gst_element_post_message (GST_ELEMENT (self),
|
||||
gst_message_new_toc (GST_OBJECT_CAST (self), toc, updated));
|
||||
}
|
||||
gst_toc_unref (toc);
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
GstStructure *http_headers;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, headers);
|
||||
|
||||
http_headers = gst_structure_new ("http-headers",
|
||||
"request-headers", GST_TYPE_STRUCTURE, headers,
|
||||
NULL);
|
||||
gst_structure_free (headers);
|
||||
|
||||
event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, http_headers);
|
||||
gst_pad_push_event (GST_BASE_SRC_PAD (self), event);
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Pushed all events");
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
clapper_enhancer_src_create (GstPushSrc *push_src, GstBuffer **outbuf)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (push_src);
|
||||
GUri *guri;
|
||||
GCancellable *cancellable;
|
||||
ClapperHarvest *harvest;
|
||||
GstCaps *caps = NULL;
|
||||
GstTagList *tags = NULL;
|
||||
GstToc *toc = NULL;
|
||||
GstStructure *headers = NULL;
|
||||
GError *error = NULL;
|
||||
gboolean unpacked;
|
||||
|
||||
/* When non-zero, we already returned complete data */
|
||||
if (self->buf_size > 0)
|
||||
return GST_FLOW_EOS;
|
||||
|
||||
/* Ensure director is created. Since it spins up its own
|
||||
* thread, create it here as we know that it will be used. */
|
||||
if (!self->director)
|
||||
self->director = clapper_enhancer_director_new ();
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
guri = g_uri_ref (self->guri);
|
||||
cancellable = g_object_ref (self->cancellable);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
harvest = clapper_enhancer_director_extract (self->director, guri, cancellable, &error);
|
||||
|
||||
g_uri_unref (guri);
|
||||
g_object_unref (cancellable);
|
||||
|
||||
if (!harvest) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
||||
("%s", error->message), (NULL));
|
||||
g_clear_error (&error);
|
||||
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
unpacked = clapper_harvest_unpack (harvest, outbuf, &self->buf_size,
|
||||
&caps, &tags, &toc, &headers);
|
||||
gst_object_unref (harvest);
|
||||
|
||||
if (!unpacked) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
||||
("Extraction harvest is empty"), (NULL));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
if (gst_base_src_set_caps (GST_BASE_SRC (self), caps))
|
||||
GST_INFO_OBJECT (self, "Using caps: %" GST_PTR_FORMAT, caps);
|
||||
else
|
||||
GST_ERROR_OBJECT (self, "Current caps could not be set");
|
||||
|
||||
gst_clear_caps (&caps);
|
||||
|
||||
/* Now push all events before buffer */
|
||||
_push_events (self, tags, toc, headers, FALSE);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
_handle_uri_query (GstQuery *query)
|
||||
{
|
||||
/* Since our URI does not actually lead to manifest data, we answer
|
||||
* with "nodata" equivalent, so upstream will not try to fetch it */
|
||||
gst_query_set_uri (query, "data:,");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_enhancer_src_query (GstBaseSrc *base_src, GstQuery *query)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_URI:
|
||||
ret = _handle_uri_query (query);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
ret = GST_BASE_SRC_CLASS (parent_class)->query (base_src, query);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_src_init (ClapperEnhancerSrc *self)
|
||||
{
|
||||
self->cancellable = g_cancellable_new ();
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_src_dispose (GObject *object)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_clear_object (&self->director);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_src_finalize (GObject *object)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Finalize");
|
||||
|
||||
g_clear_object (&self->cancellable);
|
||||
g_free (self->uri);
|
||||
g_clear_pointer (&self->guri, g_uri_unref);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_src_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_URI:{
|
||||
GError *error = NULL;
|
||||
if (!gst_uri_handler_set_uri (GST_URI_HANDLER (self),
|
||||
g_value_get_string (value), &error)) {
|
||||
GST_ERROR_OBJECT (self, "%s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_src_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_URI:
|
||||
g_value_take_string (value, gst_uri_handler_get_uri (GST_URI_HANDLER (self)));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_src_class_init (ClapperEnhancerSrcClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GstElementClass *gstelement_class = (GstElementClass *) klass;
|
||||
GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
|
||||
GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersrc", 0,
|
||||
"Clapper Enhancer Source");
|
||||
|
||||
gobject_class->set_property = clapper_enhancer_src_set_property;
|
||||
gobject_class->get_property = clapper_enhancer_src_get_property;
|
||||
gobject_class->dispose = clapper_enhancer_src_dispose;
|
||||
gobject_class->finalize = clapper_enhancer_src_finalize;
|
||||
|
||||
gstbasesrc_class->start = clapper_enhancer_src_start;
|
||||
gstbasesrc_class->stop = clapper_enhancer_src_stop;
|
||||
gstbasesrc_class->get_size = clapper_enhancer_src_get_size;
|
||||
gstbasesrc_class->is_seekable = clapper_enhancer_src_is_seekable;
|
||||
gstbasesrc_class->unlock = clapper_enhancer_src_unlock;
|
||||
gstbasesrc_class->unlock_stop = clapper_enhancer_src_unlock_stop;
|
||||
gstbasesrc_class->query = clapper_enhancer_src_query;
|
||||
|
||||
gstpushsrc_class->create = clapper_enhancer_src_create;
|
||||
|
||||
param_specs[PROP_URI] = g_param_spec_string ("uri",
|
||||
"URI", "URI", NULL,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class, "Clapper Enhancer Source",
|
||||
"Source", "A source element that uses Clapper Enhancers to produce data",
|
||||
"Rafał Dzięgiel <rafostar.github@gmail.com>");
|
||||
}
|
@@ -20,17 +20,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/gstpushsrc.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_TYPE_ENHANCER_SRC (clapper_enhancer_src_get_type())
|
||||
#define CLAPPER_ENHANCER_SRC_CAST(obj) ((ClapperEnhancerSrc *)(obj))
|
||||
#define CLAPPER_TYPE_EXTRACTABLE_SRC (clapper_extractable_src_get_type())
|
||||
#define CLAPPER_EXTRACTABLE_SRC_CAST(obj) ((ClapperExtractableSrc *)(obj))
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
G_DECLARE_FINAL_TYPE (ClapperEnhancerSrc, clapper_enhancer_src, CLAPPER, ENHANCER_SRC, GstPushSrc)
|
||||
G_DECLARE_FINAL_TYPE (ClapperExtractableSrc, clapper_extractable_src, CLAPPER, EXTRACTABLE_SRC, GstPushSrc)
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (clapperenhancersrc)
|
||||
GST_ELEMENT_REGISTER_DECLARE (clapperextractablesrc)
|
||||
|
||||
G_END_DECLS
|
700
src/lib/clapper/gst/clapper-extractable-src.c
Normal file
700
src/lib/clapper/gst/clapper-extractable-src.c
Normal file
@@ -0,0 +1,700 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "clapper-extractable-src-private.h"
|
||||
#include "clapper-enhancer-director-private.h"
|
||||
|
||||
#include "../clapper-basic-functions.h"
|
||||
#include "../clapper-enhancer-proxy.h"
|
||||
#include "../clapper-enhancer-proxy-list.h"
|
||||
#include "../clapper-extractable.h"
|
||||
#include "../clapper-harvest-private.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_extractable_src_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
#define CHECK_SCHEME_IS_HTTPS(scheme) (g_str_has_prefix (scheme, "http") \
|
||||
&& (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0')))
|
||||
|
||||
struct _ClapperExtractableSrc
|
||||
{
|
||||
GstPushSrc parent;
|
||||
|
||||
GCancellable *cancellable;
|
||||
gsize buf_size;
|
||||
|
||||
ClapperEnhancerDirector *director;
|
||||
|
||||
gchar *uri;
|
||||
GUri *guri;
|
||||
|
||||
ClapperEnhancerProxyList *enhancer_proxies;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_URI,
|
||||
PROP_ENHANCER_PROXIES,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS_ANY);
|
||||
|
||||
static GstURIType
|
||||
clapper_extractable_src_uri_handler_get_type (GType type)
|
||||
{
|
||||
return GST_URI_SRC;
|
||||
}
|
||||
|
||||
/*
|
||||
* _make_schemes:
|
||||
*
|
||||
* Make supported schemes array for a given interface type.
|
||||
* The returned array consists of unique strings (no duplicates).
|
||||
*
|
||||
* Returns: (transfer full): all supported schemes by enhancers of @iface_type.
|
||||
*/
|
||||
static gchar **
|
||||
_make_schemes (gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies ();
|
||||
GSList *found_schemes = NULL, *fs;
|
||||
gchar **schemes_strv;
|
||||
guint i, n_schemes, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
|
||||
|
||||
GST_DEBUG ("Checking for supported URI schemes");
|
||||
|
||||
for (i = 0; i < n_proxies; ++i) {
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
|
||||
const gchar *schemes;
|
||||
|
||||
if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE)
|
||||
&& (schemes = clapper_enhancer_proxy_get_extra_data (proxy, "X-Schemes"))) {
|
||||
gchar **tmp_strv;
|
||||
gint j;
|
||||
|
||||
tmp_strv = g_strsplit (schemes, ";", 0);
|
||||
|
||||
for (j = 0; tmp_strv[j]; ++j) {
|
||||
const gchar *scheme = tmp_strv[j];
|
||||
|
||||
if (!found_schemes || !g_slist_find_custom (found_schemes,
|
||||
scheme, (GCompareFunc) strcmp)) {
|
||||
found_schemes = g_slist_append (found_schemes, g_strdup (scheme));
|
||||
GST_INFO ("Found supported URI scheme: \"%s\"", scheme);
|
||||
}
|
||||
}
|
||||
|
||||
g_strfreev (tmp_strv);
|
||||
}
|
||||
}
|
||||
|
||||
n_schemes = g_slist_length (found_schemes);
|
||||
schemes_strv = g_new0 (gchar *, n_schemes + 1);
|
||||
|
||||
fs = found_schemes;
|
||||
for (i = 0; i < n_schemes; ++i) {
|
||||
schemes_strv[i] = fs->data;
|
||||
fs = fs->next;
|
||||
}
|
||||
|
||||
GST_DEBUG ("Total found URI schemes: %u", n_schemes);
|
||||
|
||||
/* Since string pointers were taken,
|
||||
* free list without content */
|
||||
g_slist_free (found_schemes);
|
||||
|
||||
return schemes_strv;
|
||||
}
|
||||
|
||||
static inline const gchar *
|
||||
_host_fixup (const gchar *host)
|
||||
{
|
||||
/* Strip common subdomains, so plugins do not
|
||||
* have to list all combinations */
|
||||
if (g_str_has_prefix (host, "www."))
|
||||
host += 4;
|
||||
else if (g_str_has_prefix (host, "m."))
|
||||
host += 2;
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
/*
|
||||
* _extractable_check_for_uri:
|
||||
* @self: a #ClapperExtractableSrc
|
||||
* @uri: a #GUri
|
||||
*
|
||||
* Check whether there is at least one extractable enhancer for @uri in global list.
|
||||
* This is used to reject URI early, thus making playbin choose different
|
||||
* source element. It uses global list, since at this stage element is not
|
||||
* yet placed within pipeline, so it cannot get proxies from player.
|
||||
*
|
||||
* Returns: whether at least one extractable enhancer advertises support for given URI.
|
||||
*/
|
||||
static gboolean
|
||||
_extractable_check_for_uri (ClapperExtractableSrc *self, GUri *uri)
|
||||
{
|
||||
ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies ();
|
||||
gboolean is_https;
|
||||
guint i, n_proxies;
|
||||
const gchar *scheme = g_uri_get_scheme (uri);
|
||||
const gchar *host = g_uri_get_host (uri);
|
||||
|
||||
if (host)
|
||||
host = _host_fixup (host);
|
||||
|
||||
GST_INFO_OBJECT (self, "Extractable check, scheme: \"%s\", host: \"%s\"",
|
||||
scheme, GST_STR_NULL (host));
|
||||
|
||||
/* Whether "http(s)" scheme is used */
|
||||
is_https = CHECK_SCHEME_IS_HTTPS (scheme);
|
||||
|
||||
if (!host && is_https)
|
||||
return FALSE;
|
||||
|
||||
n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
|
||||
for (i = 0; i < n_proxies; ++i) {
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
|
||||
|
||||
if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE)
|
||||
&& clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme)
|
||||
&& (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host)))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* _filter_extractables_for_uri:
|
||||
* @self: a #ClapperExtractableSrc
|
||||
* @proxies: a #ClapperEnhancerProxyList
|
||||
* @uri: a #GUri
|
||||
*
|
||||
* Finds all enhancer proxies of target implementing "Extractable"
|
||||
* interface, which advertise support for given @uri.
|
||||
*
|
||||
* Returns: (transfer full): A sublist in the form of #GList with proxies.
|
||||
*/
|
||||
static GList *
|
||||
_filter_extractables_for_uri (ClapperExtractableSrc *self,
|
||||
ClapperEnhancerProxyList *proxies, GUri *uri)
|
||||
{
|
||||
GList *sublist = NULL;
|
||||
guint i, n_proxies;
|
||||
gboolean is_https;
|
||||
const gchar *scheme = g_uri_get_scheme (uri);
|
||||
const gchar *host = g_uri_get_host (uri);
|
||||
|
||||
if (host)
|
||||
host = _host_fixup (host);
|
||||
|
||||
GST_INFO_OBJECT (self, "Extractable filter, scheme: \"%s\", host: \"%s\"",
|
||||
scheme, GST_STR_NULL (host));
|
||||
|
||||
/* Whether "http(s)" scheme is used */
|
||||
is_https = CHECK_SCHEME_IS_HTTPS (scheme);
|
||||
|
||||
if (!host && is_https)
|
||||
return NULL;
|
||||
|
||||
n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
|
||||
for (i = 0; i < n_proxies; ++i) {
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
|
||||
|
||||
if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE)
|
||||
&& clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme)
|
||||
&& (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) {
|
||||
sublist = g_list_append (sublist, gst_object_ref (proxy));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sublist;
|
||||
}
|
||||
|
||||
static const gchar *const *
|
||||
clapper_extractable_src_uri_handler_get_protocols (GType type)
|
||||
{
|
||||
static GOnce schemes_once = G_ONCE_INIT;
|
||||
|
||||
g_once (&schemes_once, (GThreadFunc) _make_schemes, NULL);
|
||||
return (const gchar *const *) schemes_once.retval;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
clapper_extractable_src_uri_handler_get_uri (GstURIHandler *handler)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (handler);
|
||||
gchar *uri;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
uri = g_strdup (self->uri);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_extractable_src_uri_handler_set_uri (GstURIHandler *handler,
|
||||
const gchar *uri, GError **error)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (handler);
|
||||
GUri *guri;
|
||||
const gchar *const *protocols;
|
||||
gboolean supported = FALSE;
|
||||
guint i;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Changing URI to: %s", uri);
|
||||
|
||||
if (!uri) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
|
||||
"URI property cannot be NULL");
|
||||
return FALSE;
|
||||
}
|
||||
if (GST_STATE (GST_ELEMENT_CAST (self)) >= GST_STATE_PAUSED) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
|
||||
"Cannot change URI property while element is running");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
protocols = gst_uri_handler_get_protocols (handler);
|
||||
for (i = 0; protocols[i]; ++i) {
|
||||
if ((supported = gst_uri_has_protocol (uri, protocols[i])))
|
||||
break;
|
||||
}
|
||||
if (!supported) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
|
||||
"URI protocol is not supported");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!(guri = g_uri_parse (uri, G_URI_FLAGS_ENCODED, NULL))) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
|
||||
"URI is invalid");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!_extractable_check_for_uri (self, guri)) {
|
||||
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
|
||||
"None of the available enhancers can handle this URI");
|
||||
g_uri_unref (guri);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
g_set_str (&self->uri, uri);
|
||||
g_clear_pointer (&self->guri, g_uri_unref);
|
||||
self->guri = guri;
|
||||
|
||||
GST_INFO_OBJECT (self, "URI changed to: \"%s\"", self->uri);
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_uri_handler_iface_init (GstURIHandlerInterface *iface)
|
||||
{
|
||||
iface->get_type = clapper_extractable_src_uri_handler_get_type;
|
||||
iface->get_protocols = clapper_extractable_src_uri_handler_get_protocols;
|
||||
iface->get_uri = clapper_extractable_src_uri_handler_get_uri;
|
||||
iface->set_uri = clapper_extractable_src_uri_handler_set_uri;
|
||||
}
|
||||
|
||||
#define parent_class clapper_extractable_src_parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (ClapperExtractableSrc, clapper_extractable_src, GST_TYPE_PUSH_SRC,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, _uri_handler_iface_init));
|
||||
GST_ELEMENT_REGISTER_DEFINE (clapperextractablesrc, "clapperextractablesrc",
|
||||
512, CLAPPER_TYPE_EXTRACTABLE_SRC);
|
||||
|
||||
static gboolean
|
||||
clapper_extractable_src_start (GstBaseSrc *base_src)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
|
||||
gboolean can_start;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Start");
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
can_start = (self->guri != NULL);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (G_UNLIKELY (!can_start)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
|
||||
("No media URI"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_extractable_src_stop (GstBaseSrc *base_src)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Stop");
|
||||
|
||||
self->buf_size = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_extractable_src_get_size (GstBaseSrc *base_src, guint64 *size)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
|
||||
|
||||
if (self->buf_size > 0) {
|
||||
*size = self->buf_size;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_extractable_src_is_seekable (GstBaseSrc *base_src)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_extractable_src_unlock (GstBaseSrc *base_src)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
|
||||
|
||||
GST_LOG_OBJECT (self, "Cancel triggered");
|
||||
g_cancellable_cancel (self->cancellable);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_extractable_src_unlock_stop (GstBaseSrc *base_src)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
|
||||
|
||||
GST_LOG_OBJECT (self, "Resetting cancellable");
|
||||
|
||||
g_object_unref (self->cancellable);
|
||||
self->cancellable = g_cancellable_new ();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Pushes tags, toc and request headers downstream (all transfer full) */
|
||||
static void
|
||||
_push_events (ClapperExtractableSrc *self, GstTagList *tags, GstToc *toc,
|
||||
GstStructure *headers, gboolean updated)
|
||||
{
|
||||
GstEvent *event;
|
||||
|
||||
if (tags) {
|
||||
if (!gst_tag_list_is_empty (tags)) {
|
||||
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, tags);
|
||||
|
||||
/* XXX: Normally, we should only be posting event to make it reach
|
||||
* the app after stream start, but currently it is lost that way */
|
||||
gst_element_post_message (GST_ELEMENT (self),
|
||||
gst_message_new_tag (GST_OBJECT_CAST (self), tags));
|
||||
} else {
|
||||
gst_tag_list_unref (tags);
|
||||
}
|
||||
}
|
||||
|
||||
if (toc) {
|
||||
if (g_list_length (gst_toc_get_entries (toc)) > 0) {
|
||||
GST_DEBUG_OBJECT (self, "Pushing TOC"); // TOC is not printable
|
||||
|
||||
/* XXX: Normally, we should only be posting event to make it reach
|
||||
* the app after stream start, but currently it is lost that way */
|
||||
gst_element_post_message (GST_ELEMENT (self),
|
||||
gst_message_new_toc (GST_OBJECT_CAST (self), toc, updated));
|
||||
}
|
||||
gst_toc_unref (toc);
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
GstStructure *http_headers;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, headers);
|
||||
|
||||
http_headers = gst_structure_new ("http-headers",
|
||||
"request-headers", GST_TYPE_STRUCTURE, headers,
|
||||
NULL);
|
||||
gst_structure_free (headers);
|
||||
|
||||
event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, http_headers);
|
||||
gst_pad_push_event (GST_BASE_SRC_PAD (self), event);
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Pushed all events");
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
clapper_extractable_src_create (GstPushSrc *push_src, GstBuffer **outbuf)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (push_src);
|
||||
ClapperEnhancerProxyList *proxies;
|
||||
GList *filtered_proxies;
|
||||
GUri *guri;
|
||||
GCancellable *cancellable;
|
||||
ClapperHarvest *harvest;
|
||||
GstCaps *caps = NULL;
|
||||
GstTagList *tags = NULL;
|
||||
GstToc *toc = NULL;
|
||||
GstStructure *headers = NULL;
|
||||
GError *error = NULL;
|
||||
gboolean unpacked;
|
||||
|
||||
/* When non-zero, we already returned complete data */
|
||||
if (self->buf_size > 0)
|
||||
return GST_FLOW_EOS;
|
||||
|
||||
/* Ensure director is created. Since it spins up its own
|
||||
* thread, create it here as we know that it will be used. */
|
||||
if (!self->director)
|
||||
self->director = clapper_enhancer_director_new ();
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
if (G_LIKELY (self->enhancer_proxies != NULL)) {
|
||||
GST_INFO_OBJECT (self, "Using enhancer proxies: %" GST_PTR_FORMAT, self->enhancer_proxies);
|
||||
proxies = gst_object_ref (self->enhancer_proxies);
|
||||
} else {
|
||||
/* Compat for old ClapperDiscoverer feature that does not set this property */
|
||||
GST_WARNING_OBJECT (self, "Falling back to using global enhancer proxy list!");
|
||||
proxies = gst_object_ref (clapper_get_global_enhancer_proxies ());
|
||||
}
|
||||
|
||||
guri = g_uri_ref (self->guri);
|
||||
cancellable = g_object_ref (self->cancellable);
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
filtered_proxies = _filter_extractables_for_uri (self, proxies, guri);
|
||||
gst_object_unref (proxies);
|
||||
|
||||
harvest = clapper_enhancer_director_extract (self->director,
|
||||
filtered_proxies, guri, cancellable, &error);
|
||||
|
||||
g_clear_list (&filtered_proxies, gst_object_unref);
|
||||
g_uri_unref (guri);
|
||||
g_object_unref (cancellable);
|
||||
|
||||
if (!harvest) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
||||
("%s", error->message), (NULL));
|
||||
g_clear_error (&error);
|
||||
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
unpacked = clapper_harvest_unpack (harvest, outbuf, &self->buf_size,
|
||||
&caps, &tags, &toc, &headers);
|
||||
gst_object_unref (harvest);
|
||||
|
||||
if (!unpacked) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
||||
("Extraction harvest is empty"), (NULL));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
if (gst_base_src_set_caps (GST_BASE_SRC (self), caps))
|
||||
GST_INFO_OBJECT (self, "Using caps: %" GST_PTR_FORMAT, caps);
|
||||
else
|
||||
GST_ERROR_OBJECT (self, "Current caps could not be set");
|
||||
|
||||
gst_clear_caps (&caps);
|
||||
|
||||
/* Now push all events before buffer */
|
||||
_push_events (self, tags, toc, headers, FALSE);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
_handle_uri_query (GstQuery *query)
|
||||
{
|
||||
/* Since our URI does not actually lead to manifest data, we answer
|
||||
* with "nodata" equivalent, so upstream will not try to fetch it */
|
||||
gst_query_set_uri (query, "data:,");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_extractable_src_query (GstBaseSrc *base_src, GstQuery *query)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_URI:
|
||||
ret = _handle_uri_query (query);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
ret = GST_BASE_SRC_CLASS (parent_class)->query (base_src, query);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_extractable_src_set_enhancer_proxies (ClapperExtractableSrc *self,
|
||||
ClapperEnhancerProxyList *enhancer_proxies)
|
||||
{
|
||||
GST_OBJECT_LOCK (self);
|
||||
gst_object_replace ((GstObject **) &self->enhancer_proxies,
|
||||
GST_OBJECT_CAST (enhancer_proxies));
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_extractable_src_init (ClapperExtractableSrc *self)
|
||||
{
|
||||
self->cancellable = g_cancellable_new ();
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_extractable_src_dispose (GObject *object)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (object);
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_clear_object (&self->director);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_extractable_src_finalize (GObject *object)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Finalize");
|
||||
|
||||
g_clear_object (&self->cancellable);
|
||||
g_free (self->uri);
|
||||
g_clear_pointer (&self->guri, g_uri_unref);
|
||||
gst_clear_object (&self->enhancer_proxies);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_extractable_src_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_URI:{
|
||||
GError *error = NULL;
|
||||
if (!gst_uri_handler_set_uri (GST_URI_HANDLER (self),
|
||||
g_value_get_string (value), &error)) {
|
||||
GST_ERROR_OBJECT (self, "%s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PROP_ENHANCER_PROXIES:
|
||||
clapper_extractable_src_set_enhancer_proxies (self, g_value_get_object (value));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_extractable_src_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_URI:
|
||||
g_value_take_string (value, gst_uri_handler_get_uri (GST_URI_HANDLER (self)));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_extractable_src_class_init (ClapperExtractableSrcClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GstElementClass *gstelement_class = (GstElementClass *) klass;
|
||||
GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
|
||||
GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperextractablesrc", 0,
|
||||
"Clapper Extractable Source");
|
||||
|
||||
gobject_class->set_property = clapper_extractable_src_set_property;
|
||||
gobject_class->get_property = clapper_extractable_src_get_property;
|
||||
gobject_class->dispose = clapper_extractable_src_dispose;
|
||||
gobject_class->finalize = clapper_extractable_src_finalize;
|
||||
|
||||
gstbasesrc_class->start = clapper_extractable_src_start;
|
||||
gstbasesrc_class->stop = clapper_extractable_src_stop;
|
||||
gstbasesrc_class->get_size = clapper_extractable_src_get_size;
|
||||
gstbasesrc_class->is_seekable = clapper_extractable_src_is_seekable;
|
||||
gstbasesrc_class->unlock = clapper_extractable_src_unlock;
|
||||
gstbasesrc_class->unlock_stop = clapper_extractable_src_unlock_stop;
|
||||
gstbasesrc_class->query = clapper_extractable_src_query;
|
||||
|
||||
gstpushsrc_class->create = clapper_extractable_src_create;
|
||||
|
||||
param_specs[PROP_URI] = g_param_spec_string ("uri",
|
||||
"URI", "URI", NULL,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies",
|
||||
NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST,
|
||||
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class, "Clapper Extractable Source",
|
||||
"Source", "A source element that uses Clapper extractable enhancers to produce data",
|
||||
"Rafał Dzięgiel <rafostar.github@gmail.com>");
|
||||
}
|
@@ -21,31 +21,30 @@
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "../clapper-basic-functions.h"
|
||||
#include "../clapper-enhancer-proxy.h"
|
||||
#include "../clapper-enhancer-proxy-list-private.h"
|
||||
#include "../clapper-extractable.h"
|
||||
|
||||
#include "clapper-plugin-private.h"
|
||||
#include "../clapper-functionalities-availability.h"
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
#include "clapper-enhancer-src-private.h"
|
||||
#include "../clapper-extractable-private.h"
|
||||
#include "../clapper-enhancers-loader-private.h"
|
||||
#endif
|
||||
|
||||
#include "clapper-extractable-src-private.h"
|
||||
#include "clapper-uri-list-demux-private.h"
|
||||
|
||||
gboolean
|
||||
clapper_gst_plugin_init (GstPlugin *plugin)
|
||||
{
|
||||
gboolean res = FALSE;
|
||||
ClapperEnhancerProxyList *global_proxies;
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
gst_plugin_add_dependency_simple (plugin,
|
||||
"CLAPPER_ENHANCERS_PATH", CLAPPER_ENHANCERS_PATH, NULL,
|
||||
GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY);
|
||||
|
||||
global_proxies = clapper_get_global_enhancer_proxies ();
|
||||
|
||||
/* Avoid registering an URI handler without schemes */
|
||||
if (clapper_enhancers_loader_has_enhancers (CLAPPER_TYPE_EXTRACTABLE))
|
||||
res |= GST_ELEMENT_REGISTER (clapperenhancersrc, plugin);
|
||||
#endif
|
||||
if (clapper_enhancer_proxy_list_has_proxy_with_interface (global_proxies, CLAPPER_TYPE_EXTRACTABLE))
|
||||
res |= GST_ELEMENT_REGISTER (clapperextractablesrc, plugin);
|
||||
|
||||
res |= GST_ELEMENT_REGISTER (clapperurilistdemux, plugin);
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gstbin.h>
|
||||
|
||||
|
@@ -193,7 +193,7 @@ _feature_filter (GstPluginFeature *feature, const gchar *search_proto)
|
||||
feature_name = gst_plugin_feature_get_name (feature);
|
||||
|
||||
/* Do not loop endlessly creating our own sources and demuxers */
|
||||
if (!feature_name || strcmp (feature_name, "clapperenhancersrc") == 0)
|
||||
if (!feature_name || strcmp (feature_name, "clapperextractablesrc") == 0)
|
||||
return FALSE;
|
||||
|
||||
protocols = gst_element_factory_get_uri_protocols (factory);
|
||||
|
@@ -54,6 +54,7 @@ config_h.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||
config_h.set_quoted('PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper')
|
||||
config_h.set_quoted('PLUGIN_DESC', 'Clapper elements')
|
||||
config_h.set_quoted('PLUGIN_LICENSE', 'LGPL')
|
||||
config_h.set_quoted('CLAPPER_API_NAME', clapper_api_name)
|
||||
config_h.set_quoted('CLAPPER_ENHANCERS_PATH', clapper_enhancers_dir)
|
||||
|
||||
configure_file(
|
||||
@@ -109,6 +110,9 @@ clapper_headers = [
|
||||
'clapper.h',
|
||||
'clapper-enums.h',
|
||||
'clapper-audio-stream.h',
|
||||
'clapper-basic-functions.h',
|
||||
'clapper-enhancer-proxy.h',
|
||||
'clapper-enhancer-proxy-list.h',
|
||||
'clapper-extractable.h',
|
||||
'clapper-feature.h',
|
||||
'clapper-harvest.h',
|
||||
@@ -116,6 +120,7 @@ clapper_headers = [
|
||||
'clapper-media-item.h',
|
||||
'clapper-player.h',
|
||||
'clapper-queue.h',
|
||||
'clapper-reactable.h',
|
||||
'clapper-stream.h',
|
||||
'clapper-stream-list.h',
|
||||
'clapper-subtitle-stream.h',
|
||||
@@ -127,9 +132,12 @@ clapper_headers = [
|
||||
clapper_visibility_header,
|
||||
]
|
||||
clapper_sources = [
|
||||
'clapper.c',
|
||||
'clapper-app-bus.c',
|
||||
'clapper-audio-stream.c',
|
||||
'clapper-basic-functions.c',
|
||||
'clapper-cache.c',
|
||||
'clapper-enhancer-proxy.c',
|
||||
'clapper-enhancer-proxy-list.c',
|
||||
'clapper-extractable.c',
|
||||
'clapper-feature.c',
|
||||
'clapper-features-bus.c',
|
||||
@@ -140,6 +148,8 @@ clapper_sources = [
|
||||
'clapper-playbin-bus.c',
|
||||
'clapper-player.c',
|
||||
'clapper-queue.c',
|
||||
'clapper-reactable.c',
|
||||
'clapper-reactables-manager.c',
|
||||
'clapper-stream.c',
|
||||
'clapper-stream-list.c',
|
||||
'clapper-subtitle-stream.c',
|
||||
@@ -148,6 +158,8 @@ clapper_sources = [
|
||||
'clapper-utils.c',
|
||||
'clapper-video-stream.c',
|
||||
'gst/clapper-plugin.c',
|
||||
'gst/clapper-extractable-src.c',
|
||||
'gst/clapper-enhancer-director.c',
|
||||
'gst/clapper-uri-list-demux.c',
|
||||
'../shared/clapper-shared-utils.c',
|
||||
]
|
||||
@@ -170,8 +182,6 @@ if clapper_with_enhancers_loader
|
||||
clapper_deps += peas_dep
|
||||
clapper_sources += [
|
||||
'clapper-enhancers-loader.c',
|
||||
'gst/clapper-enhancer-src.c',
|
||||
'gst/clapper-enhancer-director.c',
|
||||
]
|
||||
clapper_available_functionalities += 'enhancers-loader'
|
||||
endif
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Skipped by GI, but Vala can handle it fine
|
||||
//init_get_option_group skip=false
|
||||
*_FORMAT skip=false
|
||||
*.peek_* skip=false
|
||||
|
||||
// Init func compatibility
|
||||
init.argv unowned
|
||||
|
@@ -594,11 +594,14 @@ static gboolean
|
||||
gst_clapper_sink_start (GstBaseSink *bsink)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
|
||||
gboolean with_clapper_gtk;
|
||||
|
||||
GST_INFO_OBJECT (self, "Start");
|
||||
|
||||
if (G_UNLIKELY (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_clapper_sink_start_on_main, self)))) {
|
||||
with_clapper_gtk = g_type_from_name ("ClapperGtkVideo");
|
||||
|
||||
if (G_UNLIKELY (!with_clapper_gtk && !(! !gst_gtk_invoke_on_main (
|
||||
(GThreadFunc) (GCallback) gst_clapper_sink_start_on_main, self)))) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
||||
("GtkWidget could not be created"), (NULL));
|
||||
|
||||
|
@@ -5,7 +5,8 @@ vapigen = find_program('vapigen', required: get_option('vapi'))
|
||||
build_vapi = (vapigen.found() and not get_option('vapi').disabled())
|
||||
|
||||
gir_init_section = '--add-init-section=extern void gst_init(gint*,gchar**);' + \
|
||||
'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \
|
||||
'g_setenv("GST_REGISTRY_DISABLE", "yes", TRUE);' + \
|
||||
'g_setenv("GST_REGISTRY_1_0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \
|
||||
'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \
|
||||
'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \
|
||||
'gst_init(NULL,NULL);'
|
||||
|
@@ -41,5 +41,11 @@
|
||||
#endif
|
||||
|
||||
#define @CLAPPER_API@_API _@CLAPPER_API@_VISIBILITY
|
||||
|
||||
#if !defined(@CLAPPER_API@_COMPILATION)
|
||||
#define @CLAPPER_API@_DEPRECATED G_DEPRECATED _@CLAPPER_API@_VISIBILITY
|
||||
#define @CLAPPER_API@_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _@CLAPPER_API@_VISIBILITY
|
||||
#else
|
||||
#define @CLAPPER_API@_DEPRECATED _@CLAPPER_API@_VISIBILITY
|
||||
#define @CLAPPER_API@_DEPRECATED_FOR(f) _@CLAPPER_API@_VISIBILITY
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user