diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 6caa8cea7..9d85f869e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -28,6 +28,7 @@ jobs: - name: Build run: | meson setup builddir -Dauto_features=enabled \ + -Denhancers-loader=disabled \ -Dclapper-app=disabled -Dvapi=disabled -Ddoc=true \ -Dglimporter=auto -Dgluploader=auto -Drawimporter=auto cd builddir diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index aeba6f7bf..b9a6aae78 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,6 +35,7 @@ jobs: mingw-w64-${{ matrix.arch }}-gst-plugins-bad mingw-w64-${{ matrix.arch }}-gst-plugins-ugly mingw-w64-${{ matrix.arch }}-gst-libav + mingw-w64-${{ matrix.arch }}-libpeas2 mingw-w64-${{ matrix.arch }}-libsoup3 mingw-w64-${{ matrix.arch }}-libmicrodns mingw-w64-${{ matrix.arch }}-gtk4 diff --git a/meson.build b/meson.build index e63689c00..377fe254c 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,11 @@ libadwaita_dep = dependency('libadwaita-1', required: false, ) +# Optional +peas_dep = dependency('libpeas-2', + required: false, +) + cc = meson.get_compiler('c') libm = cc.find_library('m', required: false) @@ -147,6 +152,9 @@ summary('vapi', build_vapi ? 'Yes' : 'No', section: 'Build') summary('doc', build_doc ? 'Yes' : 'No', section: 'Build') if build_clapper + foreach name : clapper_possible_functionalities + summary(name, clapper_available_functionalities.contains(name) ? 'Yes' : 'No', section: 'Functionalities') + endforeach foreach name : clapper_possible_features summary(name, clapper_available_features.contains(name) ? 'Yes' : 'No', section: 'Features') endforeach diff --git a/meson_options.txt b/meson_options.txt index ec9cce168..7adf225df 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -35,6 +35,13 @@ option('doc', description: 'Build documentation' ) +# Functionalities +option('enhancers-loader', + type: 'feature', + value: 'enabled', + description: 'Ability to load libpeas based plugins that enhance capabilities' +) + # Features option('discoverer', type: 'feature', diff --git a/pkgs/flatpak/com.github.rafostar.Clapper-nightly.json b/pkgs/flatpak/com.github.rafostar.Clapper-nightly.json index 426680ef1..2b9090a2e 100644 --- a/pkgs/flatpak/com.github.rafostar.Clapper-nightly.json +++ b/pkgs/flatpak/com.github.rafostar.Clapper-nightly.json @@ -50,7 +50,8 @@ "testing/dav1d.json", "testing/gstreamer.json", "testing/gst-plugins-rs.json", - "testing/gtuber.json", + "testing/yt-dlp.json", + "testing/libpeas.json", { "name": "clapper", "buildsystem": "meson", @@ -60,7 +61,8 @@ "path": "../../." } ] - } + }, + "testing/clapper-enhancers.json" ], "cleanup-commands": [ "mkdir -p /app/lib/ffmpeg", diff --git a/pkgs/flatpak/com.github.rafostar.Clapper.json b/pkgs/flatpak/com.github.rafostar.Clapper.json index cd8aac690..44d7611a0 100644 --- a/pkgs/flatpak/com.github.rafostar.Clapper.json +++ b/pkgs/flatpak/com.github.rafostar.Clapper.json @@ -41,7 +41,8 @@ "flathub/lib/uchardet.json", "flathub/lib/libmicrodns.json", "flathub/gstreamer-1.0/gstreamer.json", - "testing/gtuber.json", + "testing/yt-dlp.json", + "testing/libpeas.json", { "name": "clapper", "buildsystem": "meson", @@ -54,7 +55,8 @@ "path": "../../." } ] - } + }, + "testing/clapper-enhancers.json" ], "cleanup-commands": [ "mkdir -p /app/lib/ffmpeg", diff --git a/pkgs/flatpak/testing/clapper-enhancers.json b/pkgs/flatpak/testing/clapper-enhancers.json new file mode 100644 index 000000000..2b96e4e71 --- /dev/null +++ b/pkgs/flatpak/testing/clapper-enhancers.json @@ -0,0 +1,14 @@ +{ + "name": "clapper-enhancers", + "buildsystem": "meson", + "config-opts": [ + "-Dauto_features=enabled" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/Rafostar/clapper-enhancers.git", + "branch": "main" + } + ] +} diff --git a/pkgs/flatpak/testing/gtuber.json b/pkgs/flatpak/testing/gtuber.json deleted file mode 100644 index 998a4c285..000000000 --- a/pkgs/flatpak/testing/gtuber.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "gtuber", - "buildsystem": "meson", - "config-opts": [ - "-Dintrospection=disabled", - "-Dvapi=disabled", - "-Dgst-gtuber=enabled" - ], - "cleanup": [ - "/include", - "/lib/pkgconfig" - ], - "sources": [ - { - "type": "git", - "url": "https://github.com/Rafostar/gtuber.git", - "branch": "main" - } - ] -} diff --git a/pkgs/flatpak/testing/libpeas.json b/pkgs/flatpak/testing/libpeas.json new file mode 100644 index 000000000..2c8b6c4b7 --- /dev/null +++ b/pkgs/flatpak/testing/libpeas.json @@ -0,0 +1,21 @@ +{ + "name": "libpeas", + "buildsystem": "meson", + "config-opts": [ + "--wrap-mode=nodownload", + "-Dgjs=false", + "-Dlua51=false", + "-Dintrospection=false" + ], + "sources": [ + { + "type": "archive", + "url": "https://download.gnome.org/sources/libpeas/2.0/libpeas-2.0.5.tar.xz", + "sha256": "376f2f73d731b54e13ddbab1d91b6382cf6a980524def44df62add15489de6dd", + "x-checker-data": { + "type": "gnome", + "name": "libpeas" + } + } + ] +} diff --git a/pkgs/flatpak/testing/yt-dlp.json b/pkgs/flatpak/testing/yt-dlp.json new file mode 100644 index 000000000..2b59cb2f7 --- /dev/null +++ b/pkgs/flatpak/testing/yt-dlp.json @@ -0,0 +1,19 @@ +{ + "name": "yt-dlp", + "buildsystem": "simple", + "build-commands": [ + "pip3 install -v --root-user-action=ignore --no-deps --prefix=/app *.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/bb/68/548f9819b41d53561d4f3d39588111cf39993c066b6e5300b4ae118eb2e6/yt_dlp-2024.10.22-py3-none-any.whl", + "sha256": "ba166602ebe22a220e4dc1ead45bf00eb469ed812b22f4fb8bb54734f9b02084", + "x-checker-data": { + "type": "pypi", + "name": "yt-dlp", + "packagetype": "bdist_wheel" + } + } + ] +} diff --git a/src/lib/clapper/clapper-enhancers-loader-private.h b/src/lib/clapper/clapper-enhancers-loader-private.h new file mode 100644 index 000000000..c2a50d9c9 --- /dev/null +++ b/src/lib/clapper/clapper-enhancers-loader-private.h @@ -0,0 +1,42 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +void clapper_enhancers_loader_initialize (void); + +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); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancers-loader.c b/src/lib/clapper/clapper-enhancers-loader.c new file mode 100644 index 000000000..286a77ecc --- /dev/null +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -0,0 +1,388 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 +#include + +#include "clapper-enhancers-loader-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 + +#define GST_CAT_DEFAULT clapper_enhancers_loader_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +static PeasEngine *_engine = NULL; +static GMutex load_lock; + +/* + * clapper_enhancers_loader_initialize: + * + * Initializes #PeasEngine with directories that store enhancers. + */ +void +clapper_enhancers_loader_initialize (void) +{ + const gchar *enhancers_path; + gchar **dir_paths; + guint i; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersloader", 0, + "Clapper Enhancer Loader"); + + enhancers_path = g_getenv ("CLAPPER_ENHANCERS_PATH"); + if (!enhancers_path || *enhancers_path == '\0') + enhancers_path = CLAPPER_ENHANCERS_PATH; + + GST_INFO ("Initializing Clapper enhancers with path: \"%s\"", enhancers_path); + + _engine = peas_engine_new (); + + /* Peas loaders are loaded lazily, so it should be fine + * to just enable them all here (even if not installed) */ + peas_engine_enable_loader (_engine, "python"); + peas_engine_enable_loader (_engine, "gjs"); + + dir_paths = g_strsplit (enhancers_path, G_SEARCHPATH_SEPARATOR_S, 0); + + for (i = 0; dir_paths[i]; ++i) + peas_engine_add_search_path (_engine, dir_paths[i], NULL); + + g_strfreev (dir_paths); + + if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_INFO) { + GListModel *list = (GListModel *) _engine; + guint n_items = g_list_model_get_n_items (list); + + 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); + } + + GST_INFO ("Clapper enhancers initialized, found: %u", n_items); + } +} + +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: + * @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 + * + * 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. + * + * 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) +{ + GObject *enhancer = NULL; + PeasPluginInfo *info; + const gchar *scheme = g_uri_get_scheme (uri); + const gchar *host = g_uri_get_host (uri); + + if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) { + 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); + } + + return enhancer; +} diff --git a/src/lib/clapper/clapper-extractable-private.h b/src/lib/clapper/clapper-extractable-private.h new file mode 100644 index 000000000..85d6a470f --- /dev/null +++ b/src/lib/clapper/clapper-extractable-private.h @@ -0,0 +1,33 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 +#include + +#include "clapper-extractable.h" +#include "clapper-harvest.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +gboolean clapper_extractable_extract (ClapperExtractable *extractable, GUri *uri, ClapperHarvest *harvest, GCancellable *cancellable, GError **error); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-extractable.c b/src/lib/clapper/clapper-extractable.c new file mode 100644 index 000000000..64fe178b1 --- /dev/null +++ b/src/lib/clapper/clapper-extractable.c @@ -0,0 +1,61 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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. + */ + +/** + * ClapperExtractable: + * + * An interface for creating enhancers that resolve given URI into something playable. + * + * Since: 0.8 + */ + +#include + +#include "clapper-extractable-private.h" +#include "clapper-harvest-private.h" + +G_DEFINE_INTERFACE (ClapperExtractable, clapper_extractable, G_TYPE_OBJECT); + +static gboolean +clapper_extractable_default_extract (ClapperExtractable *self, GUri *uri, + ClapperHarvest *harvest, GCancellable *cancellable, GError **error) +{ + if (*error == NULL) { + g_set_error (error, GST_CORE_ERROR, + GST_CORE_ERROR_NOT_IMPLEMENTED, + "Extractable object did not implement extract function"); + } + + return FALSE; +} + +static void +clapper_extractable_default_init (ClapperExtractableInterface *iface) +{ + iface->extract = clapper_extractable_default_extract; +} + +gboolean +clapper_extractable_extract (ClapperExtractable *self, GUri *uri, + ClapperHarvest *harvest, GCancellable *cancellable, GError **error) +{ + ClapperExtractableInterface *iface = CLAPPER_EXTRACTABLE_GET_IFACE (self); + + return iface->extract (self, uri, harvest, cancellable, error); +} diff --git a/src/lib/clapper/clapper-extractable.h b/src/lib/clapper/clapper-extractable.h new file mode 100644 index 000000000..80c3e5975 --- /dev/null +++ b/src/lib/clapper/clapper-extractable.h @@ -0,0 +1,69 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 can be included directly." +#endif + +#include +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_EXTRACTABLE (clapper_extractable_get_type()) +#define CLAPPER_EXTRACTABLE_CAST(obj) ((ClapperExtractable *)(obj)) + +CLAPPER_API +G_DECLARE_INTERFACE (ClapperExtractable, clapper_extractable, CLAPPER, EXTRACTABLE, GObject) + +/** + * ClapperExtractableInterface: + * @parent_iface: The parent interface structure. + * @extract: Extract data and fill harvest. + */ +struct _ClapperExtractableInterface +{ + GTypeInterface parent_iface; + + /** + * ClapperExtractableInterface::extract: + * @extractable: a #ClapperExtractable + * @uri: a #GUri + * @cancellable: a #GCancellable object + * @error: a #GError + * + * Extract data and fill harvest. + * + * Returns: whether extraction was successful. + * + * Since: 0.8 + */ + gboolean (* extract) (ClapperExtractable *extractable, GUri *uri, ClapperHarvest *harvest, GCancellable *cancellable, GError **error); + + /*< private >*/ + gpointer padding[8]; +}; + +G_END_DECLS diff --git a/src/lib/clapper/clapper-functionalities-availability.h.in b/src/lib/clapper/clapper-functionalities-availability.h.in new file mode 100644 index 000000000..bc32d9617 --- /dev/null +++ b/src/lib/clapper/clapper-functionalities-availability.h.in @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 can be included directly." +#endif + +/** + * CLAPPER_WITH_ENHANCERS_LOADER: + * + * Check if Clapper was compiled with Enhancers Loader functionality. + * + * Alternatively, apps before compiling can also check whether `pkgconfig` + * variable named `functionalities` contains `enhancers-loader` string. + * + * Since: 0.8 + */ +#define CLAPPER_WITH_ENHANCERS_LOADER (@CLAPPER_WITH_ENHANCERS_LOADER@) diff --git a/src/lib/clapper/clapper-harvest-private.h b/src/lib/clapper/clapper-harvest-private.h new file mode 100644 index 000000000..e363b69f8 --- /dev/null +++ b/src/lib/clapper/clapper-harvest-private.h @@ -0,0 +1,35 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 +#include + +#include "clapper-harvest.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +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_END_DECLS diff --git a/src/lib/clapper/clapper-harvest.c b/src/lib/clapper/clapper-harvest.c new file mode 100644 index 000000000..4c0d603a9 --- /dev/null +++ b/src/lib/clapper/clapper-harvest.c @@ -0,0 +1,450 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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. + */ + +/** + * ClapperHarvest: + * + * An object storing data from enhancers that implement [iface@Clapper.Extractable] interface. + * + * Since: 0.8 + */ + +/* + * NOTE: We cannot simply expose GstMiniObjects for + * implementations to assemble TagList/Toc themselves, see: + * https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2867 + */ + +#include "clapper-harvest-private.h" + +#define GST_CAT_DEFAULT clapper_harvest_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperHarvest +{ + GstObject parent; + + GstCaps *caps; + GstBuffer *buffer; + gsize buf_size; + + GstTagList *tags; + GstToc *toc; + GstStructure *headers; + + guint16 n_chapters; + guint16 n_tracks; +}; + +#define parent_class clapper_harvest_parent_class +G_DEFINE_TYPE (ClapperHarvest, clapper_harvest, GST_TYPE_OBJECT); + +static inline void +_ensure_tags (ClapperHarvest *self) +{ + if (!self->tags) { + self->tags = gst_tag_list_new_empty (); + gst_tag_list_set_scope (self->tags, GST_TAG_SCOPE_GLOBAL); + } +} + +static inline void +_ensure_toc (ClapperHarvest *self) +{ + if (!self->toc) + self->toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); +} + +static inline void +_ensure_headers (ClapperHarvest *self) +{ + if (!self->headers) + self->headers = gst_structure_new_empty ("request-headers"); +} + +ClapperHarvest * +clapper_harvest_new (void) +{ + ClapperHarvest *harvest; + + harvest = g_object_new (CLAPPER_TYPE_HARVEST, NULL); + gst_object_ref_sink (harvest); + + return harvest; +} + +gboolean +clapper_harvest_unpack (ClapperHarvest *self, + GstBuffer **buffer, gsize *buf_size, GstCaps **caps, + GstTagList **tags, GstToc **toc, GstStructure **headers) +{ + /* Not filled or already unpacked */ + if (!self->buffer) + return FALSE; + + *buffer = self->buffer; + self->buffer = NULL; + + *buf_size = self->buf_size; + self->buf_size = 0; + + *caps = self->caps; + self->caps = NULL; + + *tags = self->tags; + self->tags = NULL; + + *toc = self->toc; + self->toc = NULL; + + *headers = self->headers; + self->headers = NULL; + + return TRUE; +} + +/** + * clapper_harvest_fill: + * @harvest: a #ClapperHarvest + * @media_type: media mime type + * @data: (array length=size) (element-type guint8) (transfer full): data to fill @harvest + * @size: allocated size of @data + * + * Fill harvest with extracted data. It can be anything that GStreamer + * can parse and play such as single URI or a streaming manifest. + * + * Calling again this function will replace previously filled content. + * + * Commonly used media types are: + * + * * `application/dash+xml` + * + * * `application/x-hls` + * + * * `text/uri-list` + * + * Returns: %TRUE when filled successfully, %FALSE if taken data was empty. + * + * Since: 0.8 + */ +gboolean +clapper_harvest_fill (ClapperHarvest *self, const gchar *media_type, gpointer data, gsize size) +{ + g_return_val_if_fail (CLAPPER_IS_HARVEST (self), FALSE); + g_return_val_if_fail (media_type != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + + if (!data || size == 0) { + g_free (data); + return FALSE; + } + + if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG) { + gboolean is_printable = (strcmp (media_type, "application/dash+xml") == 0) + || (strcmp (media_type, "application/x-hls") == 0) + || (strcmp (media_type, "text/uri-list") == 0); + + if (is_printable) { + gchar *data_str; + + data_str = g_new0 (gchar, size + 1); + memcpy (data_str, data, size); + + GST_DEBUG_OBJECT (self, "Filled with data:\n%s", data_str); + + g_free (data_str); + } + } + + gst_clear_buffer (&self->buffer); + gst_clear_caps (&self->caps); + + self->buffer = gst_buffer_new_wrapped (data, size); + self->buf_size = size; + self->caps = gst_caps_new_simple (media_type, + "source", G_TYPE_STRING, "clapper-harvest", NULL); + + return TRUE; +} + +/** + * clapper_harvest_fill_with_text: + * @harvest: a #ClapperHarvest + * @media_type: media mime type + * @text: (transfer full): data to fill @harvest as %NULL terminated string + * + * A convenience method to fill @harvest using a %NULL terminated string. + * + * For more info, see [method@Clapper.Harvest.fill] documentation. + * + * Returns: %TRUE when filled successfully, %FALSE if taken data was empty. + * + * Since: 0.8 + */ +gboolean +clapper_harvest_fill_with_text (ClapperHarvest *self, const gchar *media_type, gchar *text) +{ + g_return_val_if_fail (text != NULL, FALSE); + + return clapper_harvest_fill (self, media_type, text, strlen (text)); +} + +/** + * clapper_harvest_fill_with_bytes: + * @harvest: a #ClapperHarvest + * @media_type: media mime type + * @bytes: (transfer full): a #GBytes to fill @harvest + * + * A convenience method to fill @harvest with data from #GBytes. + * + * For more info, see [method@Clapper.Harvest.fill] documentation. + * + * Returns: %TRUE when filled successfully, %FALSE if taken data was empty. + * + * Since: 0.8 + */ +gboolean +clapper_harvest_fill_with_bytes (ClapperHarvest *self, const gchar *media_type, GBytes *bytes) +{ + gpointer data; + gsize size = 0; + + g_return_val_if_fail (bytes != NULL, FALSE); + + data = g_bytes_unref_to_data (bytes, &size); + + return clapper_harvest_fill (self, media_type, data, size); +} + +/** + * clapper_harvest_tags_add: + * @harvest: a #ClapperHarvest + * @tag: a name of tag to set + * @...: %NULL-terminated list of arguments + * + * Append one or more tags into the tag list. + * + * Variable arguments should be in the form of tag and value pairs. + * + * Since: 0.8 + */ +void +clapper_harvest_tags_add (ClapperHarvest *self, const gchar *tag, ...) +{ + va_list args; + + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (tag != NULL); + + _ensure_tags (self); + + va_start (args, tag); + gst_tag_list_add_valist (self->tags, GST_TAG_MERGE_APPEND, tag, args); + va_end (args); +} + +/** + * clapper_harvest_tags_add_value: (rename-to clapper_harvest_tags_add) + * @harvest: a #ClapperHarvest + * @tag: a name of tag to set + * @value: a #GValue of tag + * + * Append another tag into the tag list using #GValue. + * + * Since: 0.8 + */ +void +clapper_harvest_tags_add_value (ClapperHarvest *self, const gchar *tag, const GValue *value) +{ + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (tag != NULL); + g_return_if_fail (G_IS_VALUE (value)); + + _ensure_tags (self); + gst_tag_list_add_value (self->tags, GST_TAG_MERGE_APPEND, tag, value); +} + +/** + * clapper_harvest_toc_add: + * @harvest: a #ClapperHarvest + * @type: a #GstTocEntryType + * @title: an entry title + * @start: entry start time in seconds + * @end: entry end time in seconds or -1 if none + * + * Append a chapter or track name into table of contents. + * + * Since: 0.8 + */ +void +clapper_harvest_toc_add (ClapperHarvest *self, GstTocEntryType type, + const gchar *title, gdouble start, gdouble end) +{ + GstTocEntry *entry, *subentry; + GstClockTime start_time, end_time; + gchar edition[3]; // 2 + 1 + gchar id[14]; // 7 + 1 + 5 + 1 + const gchar *id_prefix; + guint16 nth_entry; + + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (type == GST_TOC_ENTRY_TYPE_CHAPTER || type == GST_TOC_ENTRY_TYPE_TRACK); + g_return_if_fail (title != NULL); + g_return_if_fail (start >= 0 && end >= start); + + switch (type) { + case GST_TOC_ENTRY_TYPE_CHAPTER: + id_prefix = "chapter"; + nth_entry = ++(self->n_chapters); + break; + case GST_TOC_ENTRY_TYPE_TRACK: + id_prefix = "track"; + nth_entry = ++(self->n_tracks); + break; + default: + g_assert_not_reached (); + return; + } + + start_time = start * GST_SECOND; + end_time = (end >= 0) ? end * GST_SECOND : GST_CLOCK_TIME_NONE; + + 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\"" + " (%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ")", + id, title, start_time, end_time); + + subentry = gst_toc_entry_new (type, id); + gst_toc_entry_set_tags (subentry, gst_tag_list_new (GST_TAG_TITLE, title, NULL)); + gst_toc_entry_set_start_stop_times (subentry, start_time, end_time); + + _ensure_toc (self); + +find_entry: + if (!(entry = gst_toc_find_entry (self->toc, edition))) { + GstTocEntry *toc_entry; + + toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, edition); + gst_toc_entry_set_start_stop_times (toc_entry, 0, GST_CLOCK_TIME_NONE); + gst_toc_append_entry (self->toc, toc_entry); // transfer full and must be writable + + goto find_entry; + } + + gst_toc_entry_append_sub_entry (entry, subentry); +} + +/** + * clapper_harvest_headers_set: + * @harvest: a #ClapperHarvest + * @key: a header name + * @...: %NULL-terminated list of arguments + * + * Set one or more request headers named with @key to specified `value`. + * + * Arguments should be %NULL terminated list of `key+value` string pairs. + * + * Setting again the same key will update its value to the new one. + * + * Since: 0.8 + */ +void +clapper_harvest_headers_set (ClapperHarvest *self, const gchar *key, ...) +{ + va_list args; + + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (key != NULL); + + _ensure_headers (self); + + va_start (args, key); + + while (key != NULL) { + const gchar *val = va_arg (args, const gchar *); + GST_DEBUG_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 *); + } + + va_end (args); +} + +/** + * clapper_harvest_headers_set_value: (rename-to clapper_harvest_headers_set) + * @harvest: a #ClapperHarvest + * @key: a header name + * @value: a string #GValue of header + * + * Set another header in the request headers list using #GValue. + * + * Setting again the same key will update its value to the new one. + * + * Since: 0.8 + */ +void +clapper_harvest_headers_set_value (ClapperHarvest *self, const gchar *key, const GValue *value) +{ + g_return_if_fail (CLAPPER_IS_HARVEST (self)); + g_return_if_fail (key != NULL); + g_return_if_fail (G_IS_VALUE (value) && G_VALUE_HOLDS_STRING (value)); + + _ensure_headers (self); + + GST_DEBUG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, g_value_get_string (value)); + gst_structure_set_value (self->headers, key, value); +} + +static void +clapper_harvest_init (ClapperHarvest *self) +{ +} + +static void +clapper_harvest_finalize (GObject *object) +{ + ClapperHarvest *self = CLAPPER_HARVEST_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + gst_clear_caps (&self->caps); + gst_clear_buffer (&self->buffer); + + if (self->tags) + gst_tag_list_unref (self->tags); + if (self->toc) + gst_toc_unref (self->toc); + if (self->headers) + gst_structure_free (self->headers); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_harvest_class_init (ClapperHarvestClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperharvest", 0, + "Clapper Harvest"); + + gobject_class->finalize = clapper_harvest_finalize; +} diff --git a/src/lib/clapper/clapper-harvest.h b/src/lib/clapper/clapper-harvest.h new file mode 100644 index 000000000..54cd0ad31 --- /dev/null +++ b/src/lib/clapper/clapper-harvest.h @@ -0,0 +1,64 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 can be included directly." +#endif + +#include +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_HARVEST (clapper_harvest_get_type()) +#define CLAPPER_HARVEST_CAST(obj) ((ClapperHarvest *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperHarvest, clapper_harvest, CLAPPER, HARVEST, GstObject) + +CLAPPER_API +gboolean clapper_harvest_fill (ClapperHarvest *harvest, const gchar *media_type, gpointer data, gsize size); + +CLAPPER_API +gboolean clapper_harvest_fill_with_text (ClapperHarvest *harvest, const gchar *media_type, gchar *text); + +CLAPPER_API +gboolean clapper_harvest_fill_with_bytes (ClapperHarvest *harvest, const gchar *media_type, GBytes *bytes); + +CLAPPER_API +void clapper_harvest_tags_add (ClapperHarvest *harvest, const gchar *tag, ...) G_GNUC_NULL_TERMINATED; + +CLAPPER_API +void clapper_harvest_tags_add_value (ClapperHarvest *harvest, const gchar *tag, const GValue *value); + +CLAPPER_API +void clapper_harvest_toc_add (ClapperHarvest *harvest, GstTocEntryType type, const gchar *title, gdouble start, gdouble end); + +CLAPPER_API +void clapper_harvest_headers_set (ClapperHarvest *harvest, const gchar *key, ...) G_GNUC_NULL_TERMINATED; + +CLAPPER_API +void clapper_harvest_headers_set_value (ClapperHarvest *harvest, const gchar *key, const GValue *value); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-playbin-bus.c b/src/lib/clapper/clapper-playbin-bus.c index fd624f565..0113904d0 100644 --- a/src/lib/clapper/clapper-playbin-bus.c +++ b/src/lib/clapper/clapper-playbin-bus.c @@ -30,6 +30,12 @@ #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 + #define GST_CAT_DEFAULT clapper_playbin_bus_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -869,6 +875,7 @@ _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)) @@ -879,8 +886,21 @@ _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 (G_LIKELY (player->played_item != NULL)) +#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) { + if (player->pending_tags) { + gst_tag_list_unref (player->pending_tags); + } + player->pending_tags = gst_tag_list_ref (tags); + } else if (G_LIKELY (player->played_item != NULL)) { clapper_media_item_update_from_tag_list (player->played_item, tags, player); + } gst_tag_list_unref (tags); } @@ -890,11 +910,10 @@ _handle_toc_msg (GstMessage *msg, ClapperPlayer *player) { GstObject *src = GST_MESSAGE_SRC (msg); GstToc *toc = NULL; - ClapperTimeline *timeline; - gboolean updated = FALSE; + gboolean from_enhancer_src, updated = FALSE; - /* TOC messages should only be posted by sink elements after start */ - if (G_UNLIKELY (!src || !player->played_item)) + /* TOC messages should only be posted by sink elements */ + if (G_UNLIKELY (!src)) return; /* Either new TOC was found or previous one was updated */ @@ -904,11 +923,27 @@ _handle_toc_msg (GstMessage *msg, ClapperPlayer *player) " from element: %s, updated: %s", toc, GST_OBJECT_NAME (src), (updated) ? "yes" : "no"); - timeline = clapper_media_item_get_timeline (player->played_item); +#if CLAPPER_WITH_ENHANCERS_LOADER + from_enhancer_src = CLAPPER_IS_ENHANCER_SRC (src); +#else + from_enhancer_src = FALSE; +#endif - if (clapper_timeline_set_toc (timeline, toc, updated)) { - clapper_app_bus_post_refresh_timeline (player->app_bus, - GST_OBJECT_CAST (player->played_item)); + /* ClapperEnhancerSrc determines TOC before stream start */ + if (from_enhancer_src) { + if (player->pending_toc) { + gst_toc_unref (player->pending_toc); + } + player->pending_toc = gst_toc_ref (toc); + } else if (G_LIKELY (player->played_item != NULL)) { + ClapperTimeline *timeline; + + timeline = clapper_media_item_get_timeline (player->played_item); + + if (clapper_timeline_set_toc (timeline, toc, updated)) { + clapper_app_bus_post_refresh_timeline (player->app_bus, + GST_OBJECT_CAST (player->played_item)); + } } gst_toc_unref (toc); @@ -1035,6 +1070,26 @@ _handle_stream_start_msg (GstMessage *msg, ClapperPlayer *player) /* With playbin2 we update all decoders at once after stream start */ if (!player->use_playbin3) clapper_player_playbin_update_current_decoders (player); + + if (player->pending_tags) { + if (G_LIKELY (player->played_item != NULL)) + clapper_media_item_update_from_tag_list (player->played_item, player->pending_tags, player); + + gst_clear_tag_list (&player->pending_tags); + } + if (player->pending_toc) { + if (G_LIKELY (player->played_item != NULL)) { + ClapperTimeline *timeline = clapper_media_item_get_timeline (player->played_item); + + if (clapper_timeline_set_toc (timeline, player->pending_toc, FALSE)) { + clapper_app_bus_post_refresh_timeline (player->app_bus, + GST_OBJECT_CAST (player->played_item)); + } + } + + gst_toc_unref (player->pending_toc); + player->pending_toc = NULL; + } } static inline void diff --git a/src/lib/clapper/clapper-player-private.h b/src/lib/clapper/clapper-player-private.h index 61e4528b5..b29f64dc9 100644 --- a/src/lib/clapper/clapper-player-private.h +++ b/src/lib/clapper/clapper-player-private.h @@ -19,6 +19,8 @@ #pragma once +#include + #include "clapper-player.h" #include "clapper-queue.h" #include "clapper-enums.h" @@ -53,6 +55,11 @@ struct _ClapperPlayer * different thread, thus needs a lock */ ClapperMediaItem *pending_item; + /* Pending tags/toc that arrive before stream start. + * To be applied to "played_item", thus no lock needed. */ + GstTagList *pending_tags; + GstToc *pending_toc; + GstElement *playbin; GstBus *bus; diff --git a/src/lib/clapper/clapper-player.c b/src/lib/clapper/clapper-player.c index ed8e56049..dc15c2587 100644 --- a/src/lib/clapper/clapper-player.c +++ b/src/lib/clapper/clapper-player.c @@ -729,6 +729,13 @@ clapper_player_reset (ClapperPlayer *self, gboolean pending_dispose) GST_OBJECT_UNLOCK (self); + gst_clear_tag_list (&self->pending_tags); + + if (self->pending_toc) { + gst_toc_unref (self->pending_toc); + self->pending_toc = NULL; + } + /* Emit notify when we are not going to be disposed */ if (!pending_dispose) { /* Clear current decoders (next item might not have video/audio track) */ diff --git a/src/lib/clapper/clapper.c b/src/lib/clapper/clapper.c index 9b14474a9..dfc50955e 100644 --- a/src/lib/clapper/clapper.c +++ b/src/lib/clapper/clapper.c @@ -17,6 +17,8 @@ * Boston, MA 02110-1301, USA. */ +#include "config.h" + #include #include @@ -25,6 +27,11 @@ #include "clapper-playbin-bus-private.h" #include "clapper-app-bus-private.h" #include "clapper-features-bus-private.h" +#include "gst/clapper-plugin-private.h" + +#if CLAPPER_WITH_ENHANCERS_LOADER +#include "clapper-enhancers-loader-private.h" +#endif static gboolean is_initialized = FALSE; static GMutex init_lock; @@ -44,6 +51,22 @@ clapper_init_check_internal (int *argc, char **argv[]) clapper_app_bus_initialize (); clapper_features_bus_initialize (); +#if CLAPPER_WITH_ENHANCERS_LOADER + clapper_enhancers_loader_initialize (); +#endif + + gst_plugin_register_static ( + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + PACKAGE "internal", + PLUGIN_DESC, + (GstPluginInitFunc) clapper_gst_plugin_init, + PACKAGE_VERSION, + PLUGIN_LICENSE, + PACKAGE, + PACKAGE, + PACKAGE_ORIGIN); + is_initialized = TRUE; finish: @@ -92,3 +115,52 @@ clapper_init_check (int *argc, char **argv[]) { return clapper_init_check_internal (argc, argv); } + +/** + * clapper_enhancer_check: + * @iface_type: an interface #GType + * @scheme: an URI scheme + * @host: (nullable): an URI host + * @name: (out) (optional) (transfer none): return location for found enhancer name + * + * Check if an enhancer of @type is available for given @scheme and @host. + * + * A check that compares requested capabilites of all available Clapper enhancers, + * thus it is fast but does not guarantee that the found one will succeed. Please note + * that this function will always return %FALSE if Clapper was built without enhancers + * loader functionality. To check that, use [const@Clapper.WITH_ENHANCERS_LOADER]. + * + * This function can be used to quickly determine early if Clapper will at least try to + * handle URI and with one of its enhancers and which one. + * + * Example: + * + * ```c + * gboolean supported = clapper_enhancer_check (CLAPPER_TYPE_EXTRACTABLE, "https", "example.com", NULL); + * ``` + * + * For self hosted services a custom URI @scheme without @host can be used. Enhancers should announce + * support for such schemes by defining them in their plugin info files. + * + * ```c + * gboolean supported = clapper_enhancer_check (CLAPPER_TYPE_EXTRACTABLE, "example", NULL, NULL); + * ``` + * + * Returns: whether a plausible enhancer was found. + * + * Since: 0.8 + */ +gboolean +clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name) +{ + gboolean success = FALSE; + + 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 + + return success; +} diff --git a/src/lib/clapper/clapper.h b/src/lib/clapper/clapper.h index 62c9e5246..dd5491e5d 100644 --- a/src/lib/clapper/clapper.h +++ b/src/lib/clapper/clapper.h @@ -19,6 +19,9 @@ #pragma once +#include +#include + #define __CLAPPER_INSIDE__ #include @@ -28,6 +31,7 @@ #include #include +#include #include #include #include @@ -40,6 +44,9 @@ #include #include +#include + +#include #include #if CLAPPER_HAVE_DISCOVERER @@ -60,6 +67,9 @@ 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__ diff --git a/src/lib/clapper/features/meson.build b/src/lib/clapper/features/meson.build index 72a9348ee..984df51d6 100644 --- a/src/lib/clapper/features/meson.build +++ b/src/lib/clapper/features/meson.build @@ -14,7 +14,7 @@ clapper_possible_features = [ foreach feature_name : clapper_possible_features subdir(feature_name) features_availability_conf.set( - 'CLAPPER_HAVE_@0@'.format(feature_name.to_upper()), + 'CLAPPER_HAVE_@0@'.format(feature_name.replace('-', '_').to_upper()), clapper_available_features.contains(feature_name) ? 'TRUE' : 'FALSE' ) endforeach diff --git a/src/lib/clapper/gst/clapper-enhancer-director-private.h b/src/lib/clapper/gst/clapper-enhancer-director-private.h new file mode 100644 index 000000000..ebb025cf8 --- /dev/null +++ b/src/lib/clapper/gst/clapper-enhancer-director-private.h @@ -0,0 +1,43 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 +#include +#include + +#include "../clapper-threaded-object.h" +#include "../clapper-harvest.h" + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_ENHANCER_DIRECTOR (clapper_enhancer_director_get_type()) +#define CLAPPER_ENHANCER_DIRECTOR_CAST(obj) ((ClapperEnhancerDirector *)(obj)) + +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (ClapperEnhancerDirector, clapper_enhancer_director, CLAPPER, ENHANCER_DIRECTOR, ClapperThreadedObject) + +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); + +G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-enhancer-director.c b/src/lib/clapper/gst/clapper-enhancer-director.c new file mode 100644 index 000000000..cdb1a1629 --- /dev/null +++ b/src/lib/clapper/gst/clapper-enhancer-director.c @@ -0,0 +1,171 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 + +#include "clapper-enhancer-director-private.h" +#include "../clapper-enhancers-loader-private.h" +#include "../clapper-extractable-private.h" +#include "../clapper-harvest-private.h" +#include "../../shared/clapper-shared-utils-private.h" + +#define GST_CAT_DEFAULT clapper_enhancer_director_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperEnhancerDirector +{ + ClapperThreadedObject parent; +}; + +#define parent_class clapper_enhancer_director_parent_class +G_DEFINE_TYPE (ClapperEnhancerDirector, clapper_enhancer_director, CLAPPER_TYPE_THREADED_OBJECT); + +typedef struct +{ + GUri *uri; + GCancellable *cancellable; + GError **error; +} ClapperEnhancerDirectorData; + +static gpointer +clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data) +{ + ClapperExtractable *extractable = NULL; + ClapperHarvest *harvest = clapper_harvest_new (); + gboolean success = FALSE, cached = FALSE; + + /* Cancelled during thread switching */ + if (g_cancellable_is_cancelled (data->cancellable)) + goto finish; + + /* TODO: Cache lookup */ + if (cached) { + // success = fill harvest from cache + goto finish; + } + + extractable = CLAPPER_EXTRACTABLE_CAST (clapper_enhancers_loader_create_enhancer_for_uri ( + CLAPPER_TYPE_EXTRACTABLE, data->uri)); + + /* Check just before extract */ + if (g_cancellable_is_cancelled (data->cancellable)) + goto finish; + + 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 */ + } + } else { + gst_clear_object (&harvest); + + /* Ensure we have some error set on failure */ + if (*data->error == NULL) { + const gchar *err_msg = (g_cancellable_is_cancelled (data->cancellable)) + ? "Extraction was cancelled" + : "Extraction failed"; + g_set_error (data->error, GST_RESOURCE_ERROR, + GST_RESOURCE_ERROR_FAILED, "%s", err_msg); + } + } + + gst_clear_object (&extractable); + + return harvest; +} + +/* + * clapper_enhancer_director_new: + * + * Returns: (transfer full): a new #ClapperEnhancerDirector instance. + */ +ClapperEnhancerDirector * +clapper_enhancer_director_new (void) +{ + ClapperEnhancerDirector *director; + + director = g_object_new (CLAPPER_TYPE_ENHANCER_DIRECTOR, NULL); + gst_object_ref_sink (director); + + return director; +} + +ClapperHarvest * +clapper_enhancer_director_extract (ClapperEnhancerDirector *self, GUri *uri, + GCancellable *cancellable, GError **error) +{ + ClapperEnhancerDirectorData *data = g_new (ClapperEnhancerDirectorData, 1); + + 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)), + (GThreadFunc) clapper_enhancer_director_extract_in_thread, + data, (GDestroyNotify) g_free)); +} + +static void +clapper_enhancer_director_thread_start (ClapperThreadedObject *threaded_object) +{ + GST_TRACE_OBJECT (threaded_object, "Enhancer director thread start"); +} + +static void +clapper_enhancer_director_thread_stop (ClapperThreadedObject *threaded_object) +{ + GST_TRACE_OBJECT (threaded_object, "Enhancer director thread stop"); +} + +static void +clapper_enhancer_director_init (ClapperEnhancerDirector *self) +{ +} + +static void +clapper_enhancer_director_finalize (GObject *object) +{ + GST_TRACE_OBJECT (object, "Finalize"); + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_enhancer_director_class_init (ClapperEnhancerDirectorClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + ClapperThreadedObjectClass *threaded_object = (ClapperThreadedObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancerdirector", 0, + "Clapper Enhancer Director"); + + gobject_class->finalize = clapper_enhancer_director_finalize; + + threaded_object->thread_start = clapper_enhancer_director_thread_start; + threaded_object->thread_stop = clapper_enhancer_director_thread_stop; +} diff --git a/src/lib/clapper/gst/clapper-enhancer-src-private.h b/src/lib/clapper/gst/clapper-enhancer-src-private.h new file mode 100644 index 000000000..6aeca22e6 --- /dev/null +++ b/src/lib/clapper/gst/clapper-enhancer-src-private.h @@ -0,0 +1,36 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_ENHANCER_SRC (clapper_enhancer_src_get_type()) +#define CLAPPER_ENHANCER_SRC_CAST(obj) ((ClapperEnhancerSrc *)(obj)) + +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (ClapperEnhancerSrc, clapper_enhancer_src, CLAPPER, ENHANCER_SRC, GstPushSrc) + +GST_ELEMENT_REGISTER_DECLARE (clapperenhancersrc) + +G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-enhancer-src.c b/src/lib/clapper/gst/clapper-enhancer-src.c new file mode 100644 index 000000000..198c0157f --- /dev/null +++ b/src/lib/clapper/gst/clapper-enhancer-src.c @@ -0,0 +1,495 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 "); +} diff --git a/src/lib/clapper/gst/clapper-plugin-private.h b/src/lib/clapper/gst/clapper-plugin-private.h new file mode 100644 index 000000000..eca578ed7 --- /dev/null +++ b/src/lib/clapper/gst/clapper-plugin-private.h @@ -0,0 +1,30 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +gboolean clapper_gst_plugin_init (GstPlugin *plugin); + +G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-plugin.c b/src/lib/clapper/gst/clapper-plugin.c new file mode 100644 index 000000000..4c26efb22 --- /dev/null +++ b/src/lib/clapper/gst/clapper-plugin.c @@ -0,0 +1,53 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 + +#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-uri-list-demux-private.h" + +gboolean +clapper_gst_plugin_init (GstPlugin *plugin) +{ + gboolean res = FALSE; + +#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); + + /* Avoid registering an URI handler without schemes */ + if (clapper_enhancers_loader_has_enhancers (CLAPPER_TYPE_EXTRACTABLE)) + res |= GST_ELEMENT_REGISTER (clapperenhancersrc, plugin); +#endif + + res |= GST_ELEMENT_REGISTER (clapperurilistdemux, plugin); + + return res; +} diff --git a/src/lib/clapper/gst/clapper-uri-list-demux-private.h b/src/lib/clapper/gst/clapper-uri-list-demux-private.h new file mode 100644 index 000000000..2839673fe --- /dev/null +++ b/src/lib/clapper/gst/clapper-uri-list-demux-private.h @@ -0,0 +1,36 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_URI_LIST_DEMUX (clapper_uri_list_demux_get_type()) +#define CLAPPER_URI_LIST_DEMUX_CAST(obj) ((ClapperUriListDemux *)(obj)) + +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (ClapperUriListDemux, clapper_uri_list_demux, CLAPPER, URI_LIST_DEMUX, GstBin) + +GST_ELEMENT_REGISTER_DECLARE (clapperurilistdemux) + +G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-uri-list-demux.c b/src/lib/clapper/gst/clapper-uri-list-demux.c new file mode 100644 index 000000000..1c7e3ea50 --- /dev/null +++ b/src/lib/clapper/gst/clapper-uri-list-demux.c @@ -0,0 +1,462 @@ +/* Clapper Playback Library + * Copyright (C) 2024 Rafał Dzięgiel + * + * 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 + +#include "clapper-uri-list-demux-private.h" + +#define GST_CAT_DEFAULT clapper_uri_list_demux_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperUriListDemux +{ + GstBin parent; + + GMutex lock; + + GstAdapter *input_adapter; + + GstElement *uri_handler; + GstElement *typefind; + + GstPad *typefind_src; + + GstStructure *http_headers; +}; + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("text/uri-list, source=(string)clapper-harvest")); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +#define parent_class clapper_uri_list_demux_parent_class +G_DEFINE_TYPE (ClapperUriListDemux, clapper_uri_list_demux, GST_TYPE_BIN); +GST_ELEMENT_REGISTER_DEFINE (clapperurilistdemux, "clapperurilistdemux", + 512, CLAPPER_TYPE_URI_LIST_DEMUX); + +static void +_set_property (GstObject *obj, const gchar *prop_name, gpointer value) +{ + g_object_set (G_OBJECT (obj), prop_name, value, NULL); + + if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG) { + gchar *el_name; + + el_name = gst_object_get_name (obj); + GST_DEBUG ("Set %s %s", el_name, prop_name); + + g_free (el_name); + } +} + +static gboolean +configure_deep_element (GQuark field_id, const GValue *value, GstElement *child) +{ + GObjectClass *gobject_class; + const GstStructure *substructure; + + if (!GST_VALUE_HOLDS_STRUCTURE (value)) + return TRUE; + + substructure = gst_value_get_structure (value); + + if (!gst_structure_has_name (substructure, "request-headers")) + return TRUE; + + gobject_class = G_OBJECT_GET_CLASS (child); + + if (g_object_class_find_property (gobject_class, "user-agent")) { + const gchar *ua; + + if ((ua = gst_structure_get_string (substructure, "User-Agent"))) + _set_property (GST_OBJECT_CAST (child), "user-agent", (gchar *) ua); + } + + if (g_object_class_find_property (gobject_class, "extra-headers")) { + GstStructure *extra_headers; + + extra_headers = gst_structure_copy (substructure); + gst_structure_set_name (extra_headers, "extra-headers"); + gst_structure_remove_field (extra_headers, "User-Agent"); + + _set_property (GST_OBJECT_CAST (child), "extra-headers", extra_headers); + + gst_structure_free (extra_headers); + } + + return TRUE; +} + +static void +clapper_uri_list_demux_deep_element_added (GstBin *bin, GstBin *sub_bin, GstElement *child) +{ + if (GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_SOURCE)) { + ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (bin); + + g_mutex_lock (&self->lock); + + if (self->http_headers) { + gst_structure_foreach (self->http_headers, + (GstStructureForeachFunc) configure_deep_element, child); + } + + g_mutex_unlock (&self->lock); + } +} + +static gboolean +remove_sometimes_pad_cb (GstElement *element, GstPad *pad, ClapperUriListDemux *self) +{ + GstPadTemplate *template = gst_pad_get_pad_template (pad); + GstPadPresence presence = GST_PAD_TEMPLATE_PRESENCE (template); + + gst_object_unref (template); + + if (presence == GST_PAD_SOMETIMES) { + GST_DEBUG_OBJECT (self, "Removing src pad"); + + gst_pad_set_active (pad, FALSE); + + if (G_UNLIKELY (!gst_element_remove_pad (element, pad))) + g_critical ("Failed to remove pad from bin"); + } + + return TRUE; +} + +static void +clapper_uri_list_demux_reset (ClapperUriListDemux *self) +{ + GstElement *element = GST_ELEMENT_CAST (self); + + gst_element_foreach_pad (element, (GstElementForeachPadFunc) remove_sometimes_pad_cb, NULL); +} + +static GstStateChangeReturn +clapper_uri_list_demux_change_state (GstElement *element, GstStateChange transition) +{ + ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (element); + GstStateChangeReturn ret; + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + clapper_uri_list_demux_reset (self); + break; + default: + break; + } + + return ret; +} + +static gboolean +_feature_filter (GstPluginFeature *feature, const gchar *search_proto) +{ + GstElementFactory *factory; + const gchar *const *protocols; + const gchar *feature_name; + guint i; + + if (!GST_IS_ELEMENT_FACTORY (feature)) + return FALSE; + + factory = GST_ELEMENT_FACTORY_CAST (feature); + + if (gst_element_factory_get_uri_type (factory) != GST_URI_SRC) + return FALSE; + + 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) + return FALSE; + + protocols = gst_element_factory_get_uri_protocols (factory); + + if (protocols) { + for (i = 0; protocols[i]; ++i) { + if (g_ascii_strcasecmp (protocols[i], search_proto) == 0) + return TRUE; + } + } + + return FALSE; +} + +static GstElement * +_make_handler_for_uri (ClapperUriListDemux *self, const gchar *uri) +{ + GstElement *element = NULL; + GList *factories, *f; + gchar *protocol; + + if (!gst_uri_is_valid (uri)) { + GST_ERROR_OBJECT (self, "Cannot create handler for invalid URI: \"%s\"", uri); + return NULL; + } + + protocol = gst_uri_get_protocol (uri); + factories = gst_registry_feature_filter (gst_registry_get (), + (GstPluginFeatureFilter) _feature_filter, FALSE, protocol); + g_free (protocol); + + factories = g_list_sort (factories, + (GCompareFunc) gst_plugin_feature_rank_compare_func); + + for (f = factories; f; f = g_list_next (f)) { + GstElementFactory *factory = f->data; + + if ((element = gst_element_factory_create (factory, NULL)) + && gst_uri_handler_set_uri (GST_URI_HANDLER (element), uri, NULL)) + break; + + gst_clear_object (&element); + } + + gst_plugin_feature_list_free (factories); + + GST_DEBUG_OBJECT (self, "Created URI handler: %s", + GST_OBJECT_NAME (element)); + + return element; +} + +static gboolean +clapper_uri_list_demux_process_buffer (ClapperUriListDemux *self, GstBuffer *buffer) +{ + GstMemory *mem; + GstMapInfo info; + + mem = gst_buffer_peek_memory (buffer, 0); + + if (mem && gst_memory_map (mem, &info, GST_MAP_READ)) { + GstPad *uri_handler_src, *typefind_sink, *src_ghostpad; + GstPadLinkReturn pad_link_ret; + + GST_DEBUG_OBJECT (self, "Stream URI: %s", (const gchar *) info.data); + + if (self->uri_handler) { + GST_DEBUG_OBJECT (self, "Trying to reuse existing URI handler"); + + if (gst_uri_handler_set_uri (GST_URI_HANDLER (self->uri_handler), + (const gchar *) info.data, NULL)) { + GST_DEBUG_OBJECT (self, "Reused existing URI handler"); + } else { + GST_DEBUG_OBJECT (self, "Could not reuse existing URI handler"); + + if (self->typefind_src) { + gst_element_remove_pad (GST_ELEMENT_CAST (self), self->typefind_src); + gst_clear_object (&self->typefind_src); + } + + gst_bin_remove (GST_BIN_CAST (self), self->uri_handler); + gst_bin_remove (GST_BIN_CAST (self), self->typefind); + + self->uri_handler = NULL; + self->typefind = NULL; + } + } + + if (!self->uri_handler) { + GST_DEBUG_OBJECT (self, "Creating new URI handler element"); + + self->uri_handler = _make_handler_for_uri (self, (const gchar *) info.data); + + if (G_UNLIKELY (!self->uri_handler)) { + GST_ERROR_OBJECT (self, "Could not create URI handler element"); + + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, + ("Missing plugin to handle URI: %s", info.data), (NULL)); + gst_memory_unmap (mem, &info); + + return FALSE; + } + + gst_bin_add (GST_BIN_CAST (self), self->uri_handler); + + self->typefind = gst_element_factory_make ("typefind", NULL); + gst_bin_add (GST_BIN_CAST (self), self->typefind); + + uri_handler_src = gst_element_get_static_pad (self->uri_handler, "src"); + typefind_sink = gst_element_get_static_pad (self->typefind, "sink"); + + pad_link_ret = gst_pad_link_full (uri_handler_src, typefind_sink, + GST_PAD_LINK_CHECK_NOTHING); + + if (pad_link_ret != GST_PAD_LINK_OK) + g_critical ("Failed to link bin elements"); + + g_object_unref (uri_handler_src); + g_object_unref (typefind_sink); + + self->typefind_src = gst_element_get_static_pad (self->typefind, "src"); + + src_ghostpad = gst_ghost_pad_new_from_template ("src", self->typefind_src, + gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "src")); + + gst_pad_set_active (src_ghostpad, TRUE); + + if (!gst_element_add_pad (GST_ELEMENT_CAST (self), src_ghostpad)) { + g_critical ("Failed to add source pad to bin"); + } else { + GST_DEBUG_OBJECT (self, "Added src pad, signalling \"no-more-pads\""); + gst_element_no_more_pads (GST_ELEMENT_CAST (self)); + } + } + + gst_memory_unmap (mem, &info); + + gst_element_sync_state_with_parent (self->typefind); + gst_element_sync_state_with_parent (self->uri_handler); + } + + return TRUE; +} + +static gboolean +clapper_uri_list_demux_sink_event (GstPad *pad, GstObject *parent, GstEvent *event) +{ + ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (parent); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS:{ + GstBuffer *buffer; + gsize size; + gboolean success; + + size = gst_adapter_available (self->input_adapter); + + if (size == 0) { + GST_WARNING_OBJECT (self, "Received EOS without URI data"); + break; + } + + buffer = gst_adapter_take_buffer (self->input_adapter, size); + success = clapper_uri_list_demux_process_buffer (self, buffer); + gst_buffer_unref (buffer); + + if (success) { + gst_event_unref (event); + return TRUE; + } + break; + } + case GST_EVENT_CUSTOM_DOWNSTREAM_STICKY:{ + const GstStructure *structure = gst_event_get_structure (event); + + if (structure && gst_structure_has_name (structure, "http-headers")) { + GST_DEBUG_OBJECT (self, "Received \"http-headers\" custom event"); + g_mutex_lock (&self->lock); + + gst_clear_structure (&self->http_headers); + self->http_headers = gst_structure_copy (structure); + + g_mutex_unlock (&self->lock); + } + break; + } + default: + break; + } + + return gst_pad_event_default (pad, parent, event); +} + +static GstFlowReturn +clapper_uri_list_demux_sink_chain (GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (parent); + + gst_adapter_push (self->input_adapter, buffer); + GST_DEBUG_OBJECT (self, "Received buffer, total collected: %" G_GSIZE_FORMAT " bytes", + gst_adapter_available (self->input_adapter)); + + return GST_FLOW_OK; +} + +static void +clapper_uri_list_demux_init (ClapperUriListDemux *self) +{ + GstPad *sink_pad; + + g_mutex_init (&self->lock); + + self->input_adapter = gst_adapter_new (); + + sink_pad = gst_pad_new_from_template (gst_element_class_get_pad_template ( + GST_ELEMENT_GET_CLASS (self), "sink"), "sink"); + gst_pad_set_event_function (sink_pad, + GST_DEBUG_FUNCPTR (clapper_uri_list_demux_sink_event)); + gst_pad_set_chain_function (sink_pad, + GST_DEBUG_FUNCPTR (clapper_uri_list_demux_sink_chain)); + + gst_pad_set_active (sink_pad, TRUE); + + if (!gst_element_add_pad (GST_ELEMENT_CAST (self), sink_pad)) + g_critical ("Failed to add sink pad to bin"); +} + +static void +clapper_uri_list_demux_finalize (GObject *object) +{ + ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + g_object_unref (self->input_adapter); + gst_clear_object (&self->typefind_src); + gst_clear_structure (&self->http_headers); + + g_mutex_clear (&self->lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_uri_list_demux_class_init (ClapperUriListDemuxClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstBinClass *gstbin_class = (GstBinClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperurilistdemux", 0, + "Clapper URI List Demux"); + + gobject_class->finalize = clapper_uri_list_demux_finalize; + + gstbin_class->deep_element_added = clapper_uri_list_demux_deep_element_added; + + gstelement_class->change_state = clapper_uri_list_demux_change_state; + + gst_element_class_add_static_pad_template (gstelement_class, &sink_template); + gst_element_class_add_static_pad_template (gstelement_class, &src_template); + + gst_element_class_set_static_metadata (gstelement_class, "Clapper URI List Demux", + "Demuxer", "A custom demuxer for URI lists", + "Rafał Dzięgiel "); +} diff --git a/src/lib/clapper/meson.build b/src/lib/clapper/meson.build index 87f053cd1..4ca10ea5b 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -1,6 +1,8 @@ clapper_dep = dependency('', required: false) clapper_option = get_option('clapper') clapper_headers_dir = join_paths(includedir, clapper_api_name, 'clapper') +clapper_enhancers_dir = join_paths(clapper_libdir, 'enhancers') +clapper_with_enhancers_loader = false build_clapper = false clapper_pkg_reqs = [ @@ -38,6 +40,27 @@ foreach dep : clapper_deps endif endforeach +# libpeas is an optional dependency +enhancers_option = get_option('enhancers-loader') +clapper_with_enhancers_loader = (not enhancers_option.disabled() and peas_dep.found()) + +if not clapper_with_enhancers_loader and enhancers_option.enabled() + error('enhancers-loader option was enabled, but required dependencies were not found') +endif + +config_h = configuration_data() +config_h.set_quoted('PACKAGE', meson.project_name()) +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_ENHANCERS_PATH', clapper_enhancers_dir) + +configure_file( + output: 'config.h', + configuration: config_h, +) + visibility_conf = configuration_data() visibility_conf.set( @@ -86,7 +109,9 @@ clapper_headers = [ 'clapper.h', 'clapper-enums.h', 'clapper-audio-stream.h', + 'clapper-extractable.h', 'clapper-feature.h', + 'clapper-harvest.h', 'clapper-marker.h', 'clapper-media-item.h', 'clapper-player.h', @@ -105,9 +130,11 @@ clapper_sources = [ 'clapper.c', 'clapper-app-bus.c', 'clapper-audio-stream.c', + 'clapper-extractable.c', 'clapper-feature.c', 'clapper-features-bus.c', 'clapper-features-manager.c', + 'clapper-harvest.c', 'clapper-marker.c', 'clapper-media-item.c', 'clapper-playbin-bus.c', @@ -120,6 +147,8 @@ clapper_sources = [ 'clapper-timeline.c', 'clapper-utils.c', 'clapper-video-stream.c', + 'gst/clapper-plugin.c', + 'gst/clapper-uri-list-demux.c', '../shared/clapper-shared-utils.c', ] clapper_c_args = [ @@ -132,6 +161,36 @@ if get_option('default_library') == 'static' clapper_c_args += ['-DCLAPPER_STATIC_COMPILATION'] endif +clapper_possible_functionalities = [ + 'enhancers-loader', +] +clapper_available_functionalities = [] + +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 + +functionalities_availability_conf = configuration_data() + +foreach functionality_name : clapper_possible_functionalities + functionalities_availability_conf.set( + 'CLAPPER_WITH_@0@'.format(functionality_name.replace('-', '_').to_upper()), + clapper_available_functionalities.contains(functionality_name) ? 'TRUE' : 'FALSE' + ) +endforeach + +clapper_headers += configure_file( + input: 'clapper-functionalities-availability.h.in', + output: 'clapper-functionalities-availability.h', + configuration: functionalities_availability_conf, +) + subdir('features') clapper_enums = gnome.mkenums_simple( @@ -205,7 +264,7 @@ if build_vapi sources: clapper_gir[0], packages: clapper_pkg_reqs, metadata_dirs: [ - join_paths (meson.current_source_dir(), 'metadata') + join_paths(meson.current_source_dir(), 'metadata') ], install: true, ) @@ -213,7 +272,9 @@ if build_vapi endif clapper_pkgconfig_variables = [ + 'enhancersdir=${libdir}/' + clapper_api_name + '/enhancers', 'features=' + ' '.join(clapper_available_features), + 'functionalities=' + ' '.join(clapper_available_functionalities), ] pkgconfig.generate(clapper_lib,