35 Commits

Author SHA1 Message Date
Rafał Dzięgiel
c354d31436 clapper: reactable: Inform which properties were updated 2025-06-09 20:21:39 +02:00
Rafał Dzięgiel
c0b360dc0f clapper-app: Add support for MPRIS enhancer 2025-06-09 18:37:40 +02:00
Rafał Dzięgiel
a6ca0b726c clapper: Implement reactables manager
An object for managing instances of reactable type of enhancers.

Based on/similar to features manager which along with Clapper
features objects gets deprecated in favour of reactables.
2025-06-09 18:36:06 +02:00
Rafał Dzięgiel
976bcc338f clapper: Add "Reactable" interface
An interface for creating enhancers that react to the
playback and/or events that should influence it.
2025-06-08 17:46:35 +02:00
Rafał Dzięgiel
6273446817 Merge pull request #558 from Rafostar/tagged
clapper: Add taglist to media items
2025-06-08 17:43:12 +02:00
Rafał Dzięgiel
72ab32d4ef shared: Do not print deprecations when compiling own code
We keep and use old functions in code for the compatibility reasons with
older API versions. Do not print warnings about them being deprecated when
compiling Clapper library that has and uses such function internally.
2025-06-08 17:17:23 +02:00
Rafał Dzięgiel
e9d0d8f345 clapper: Add taglist to media items
Allow apps to read and/or populate initial taglist within media item.
Apps might care about other tags that Clapper application does not,
so this single property allows them to read whatever tag they might need.
2025-06-08 17:17:13 +02:00
Rafał Dzięgiel
0b8d359844 meson: Improve GIR init section
Init with disabled registry and remove it only for clapper-gtk where its not needed.
2025-06-08 16:57:04 +02:00
Rafał Dzięgiel
4a93bea203 Revert "meson: Remove GIR init section"
This reverts commit b05f0f2b30.
2025-06-08 16:56:57 +02:00
Rafał Dzięgiel
5e2c1a8e30 flatpak: Sync with Flathub 2025-06-08 16:40:47 +02:00
Rafał Dzięgiel
72c8e4ab84 clapper: Fix missing pspec ref when copying proxy
Newly created enhancer proxies hold param specs with a reference
on each and unref them when finalized. For this reason, copied
proxy objects needs to ref pspecs from source, otherwise it would
do an unref without holding a reference on object during destruction.
2025-06-02 20:10:53 +02:00
Rafał Dzięgiel
db61b9c773 gst-plugin: Avoid main thread invoke when used with "ClapperGtkVideo"
This thread invoke is done mainly to support testing with gst-launch-1.0,
otherwise no need when used with "ClapperGtkVideo". We can avoid doing this,
by checking whether this type was already registered in which case it means
that "ClapperGtkVideo" widget is used within GTK application and registered
before sink starts processing data.

In case of "ClapperGtkVideo" we might run into situation where these two threads
are stuck waiting for each other to be idle. This change works around this issue.

Fixes #555
2025-05-25 15:06:32 +02:00
Rafał Dzięgiel
682ad6c3c8 clapper: Allow peeking in Vala
Vala does better job at handling objects without increased reference than
interpreted languages, so its safe to expose list "peek" functions to it.
2025-05-23 16:15:11 +02:00
Rafał Dzięgiel
749796a12f clapper: doc: Update enhancer proxy docs 2025-05-23 16:11:46 +02:00
Rafał Dzięgiel
c557c11e86 clapper: Check enhancer config existence before applying it
Fixes crash due to trying to apply config for an enhancer while
there are no settings in this enhancer to be applied
2025-05-23 08:12:09 +02:00
Rafał Dzięgiel
a2f67a9bc0 Merge pull request #553 from Rafostar/harvest-caching
clapper: Implement harvest caching
2025-05-21 19:49:26 +02:00
Rafał Dzięgiel
ddc0a4d8f9 clapper: doc: Fix missing transfer annotation 2025-05-21 19:42:27 +02:00
Rafał Dzięgiel
92e3e686db clapper: doc: Fix adaptive-start-bitrate description 2025-05-21 18:36:19 +02:00
Rafał Dzięgiel
9fd87dbbb9 clapper: Rename enhancersrc -> extractablesrc
Since this element only uses enhancers of "extractable" type
2025-05-21 17:42:15 +02:00
Rafał Dzięgiel
ca15f4760a clapper: Cleanup cached harvests periodically 2025-05-21 17:42:08 +02:00
Rafał Dzięgiel
6ddb53252a clapper: Implement harvest caching
Using recently added local cache functionality, store harvests
that have expiration date. With this, next time the same URI is
selected for playback we can read it from cache, skipping loading
of any enhancer plugins and doing network requests.

This also works nicely with Clapper discoverer feature.
Making queued items be fetched and cached ahead of playback.
2025-05-20 18:56:28 +02:00
Rafał Dzięgiel
b30d53d8ce clapper: Add ability to set harvest expiration date 2025-05-20 17:18:20 +02:00
Rafał Dzięgiel
1527873bcc clapper: Fix missing unref of mapped file
In case where enhancer plugin version did not match
an unref was missing of read file.
2025-05-19 20:42:32 +02:00
Rafał Dzięgiel
e23f2acb3e meson: Remove unused "config.h" variable 2025-05-18 13:24:51 +02:00
Rafał Dzięgiel
1dfcb218ac meson: Bump min required libadwaita version
Since URI dialog was ported to use AdwAlertDialog,
a minimal Adw version has to be changed to 1.5.
2025-05-17 18:55:31 +02:00
Rafał Dzięgiel
b05f0f2b30 meson: Remove GIR init section
Clapper GStreamer plugin is not part of GObject Introspection,
so no need to init GStreamer when compiling bindings.
2025-05-17 18:20:33 +02:00
Rafał Dzięgiel
dad0d46196 Merge pull request #546 from Rafostar/configurable-enhancers
Make Clapper Enhancers configurable
2025-05-16 18:30:05 +02:00
Rafał Dzięgiel
e34f729f62 clapper: Move basic functions into separate files
Avoid including whole "clapper.h" internally.
Faster recompilation when changes are done.
2025-05-16 17:02:29 +02:00
Rafał Dzięgiel
a97e7d1a96 clapper: Avoid using hardcoded API name in enhancers loader on win32 2025-05-16 17:02:10 +02:00
Rafał Dzięgiel
c6c4fe309b clapper: Implement data cache
Add data cache functions and use them to store enhancer data into local cache file.
This way we can restore all properties and interfaces used in enhancer without
creating its instance. This avoids loading interpreters like Python at init time
making startup a lot faster.
2025-05-16 17:02:07 +02:00
Rafał Dzięgiel
3ef6e9694a clapper-app: Add enhancers to preferences window
Browse, read info and configure Clapper enhancer plugins from preferences window
2025-05-16 17:02:03 +02:00
Rafał Dzięgiel
d951be7a56 clapper: Remove usage of "X-Interfaces" in enhancer data
At this point its sole remaining place where its used is one debug message.
For this reason remove it. With this, enhancer plugin files no longer need to
have this in them unless they want to keep Clapper 0.8 compat (otherwise unused).
2025-05-16 17:01:59 +02:00
Rafał Dzięgiel
98fdd7c58b clapper: Introduce "ClapperEnhancerProxy" objects
Add support for configuring Clapper Enhancers. In order to do that,
introduce enhancer proxy object that act as intermediary between
player and enhancer plugin.

We cannot give direct access to enhancer instances from code, since
they are managed (created and destroyed) by player as/when needed.
Also due to some interpreted languages not working with multiple
threads. Instead, give proxy objects that will store each enhancer
configuration to be applied when an enhancer instance is created.

With this, implementations also gain ability to browse available
enhancers, see what they support and change their properties.

Enhancers are now also assigned to player, instead of being only global.
This allows to configure them separately on a per player instance basis.

Writing configurable enhancers is super easy too, as all plugin has
to do is install standard GParamSpec properties to its class with a
corresponding gschema file (for global props only) and its done.
2025-05-16 17:01:51 +02:00
Rafał Dzięgiel
147d94088c Merge pull request #548 from ximion/master
Use modern appstream, instead of appstream-util for validation
2025-05-03 15:42:28 +02:00
Matthias Klumpp
c7790d9f7b Use modern appstream, instead of appstream-util for validation 2025-05-03 01:56:53 +02:00
63 changed files with 6383 additions and 1079 deletions

View File

@@ -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('.')

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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'),
]
)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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>

View File

@@ -176,7 +176,6 @@ if build_gir
clappergtk_enums,
],
extra_args: [
gir_init_section,
'--quiet',
'--warn-all',
'-DCLAPPER_GTK_COMPILATION',

View File

@@ -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 ""

View File

@@ -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"

View File

@@ -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;
}

View 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

View 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

View 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);
}

View 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

View 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);
}

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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)
/**

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View 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);
});
}

View 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

View 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

View 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;
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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__

View File

@@ -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

View File

@@ -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

View File

@@ -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>");
}

View File

@@ -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

View 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>");
}

View File

@@ -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);

View File

@@ -20,6 +20,7 @@
#pragma once
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <gst/gstbin.h>

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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));

View File

@@ -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);'

View File

@@ -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