clapper: Support libpeas based plugin system

Allow loading external plugins called "Enhancers" that as the name suggests,
enhance Clapper library capabilities.

Currently implemented is a "ClapperExtractable" interface meant to extract
an actual media that GStreamer can later play from an initial input URI.
Additionally, an internal GStreamer elements that work with it are ported/moved
here from "gtuber" library that this functionality replaces.
This commit is contained in:
Rafał Dzięgiel
2024-09-28 09:07:18 +02:00
parent 220913de14
commit c8ef0f891d
26 changed files with 2742 additions and 11 deletions

View File

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

View File

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

View File

@@ -0,0 +1,42 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <glib-object.h>
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

View File

@@ -0,0 +1,388 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include <gst/gst.h>
#include <libpeas.h>
#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;
}

View File

@@ -0,0 +1,33 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gio/gio.h>
#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

View File

@@ -0,0 +1,61 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperExtractable:
*
* An interface for creating enhancers that resolve given URI into something playable.
*
* Since: 0.8
*/
#include <gst/gst.h>
#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);
}

View File

@@ -0,0 +1,69 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <clapper/clapper-visibility.h>
#include <clapper/clapper-harvest.h>
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

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> 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@)

View File

@@ -0,0 +1,35 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/tag/tag.h>
#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

View File

@@ -0,0 +1,450 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* 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;
}

View File

@@ -0,0 +1,64 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <gst/tag/tag.h>
#include <clapper/clapper-visibility.h>
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

View File

@@ -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,12 +923,28 @@ _handle_toc_msg (GstMessage *msg, ClapperPlayer *player)
" from element: %s, updated: %s",
toc, GST_OBJECT_NAME (src), (updated) ? "yes" : "no");
#if CLAPPER_WITH_ENHANCERS_LOADER
from_enhancer_src = CLAPPER_IS_ENHANCER_SRC (src);
#else
from_enhancer_src = FALSE;
#endif
/* ClapperEnhancerSrc determines TOC before stream start */
if (from_enhancer_src) {
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

View File

@@ -19,6 +19,8 @@
#pragma once
#include <gst/tag/tag.h>
#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;

View File

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

View File

@@ -17,6 +17,8 @@
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
@@ -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;
}

View File

@@ -19,6 +19,9 @@
#pragma once
#include <glib.h>
#include <glib-object.h>
#define __CLAPPER_INSIDE__
#include <clapper/clapper-visibility.h>
@@ -28,6 +31,7 @@
#include <clapper/clapper-audio-stream.h>
#include <clapper/clapper-feature.h>
#include <clapper/clapper-harvest.h>
#include <clapper/clapper-marker.h>
#include <clapper/clapper-media-item.h>
#include <clapper/clapper-player.h>
@@ -40,6 +44,9 @@
#include <clapper/clapper-utils.h>
#include <clapper/clapper-video-stream.h>
#include <clapper/clapper-extractable.h>
#include <clapper/clapper-functionalities-availability.h>
#include <clapper/features/clapper-features-availability.h>
#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__

View File

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

View File

@@ -0,0 +1,43 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#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

View File

@@ -0,0 +1,171 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gst/gst.h>
#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;
}

View File

@@ -0,0 +1,36 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
#include <gst/base/gstpushsrc.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_ENHANCER_SRC (clapper_enhancer_src_get_type())
#define CLAPPER_ENHANCER_SRC_CAST(obj) ((ClapperEnhancerSrc *)(obj))
G_GNUC_INTERNAL
G_DECLARE_FINAL_TYPE (ClapperEnhancerSrc, clapper_enhancer_src, CLAPPER, ENHANCER_SRC, GstPushSrc)
GST_ELEMENT_REGISTER_DECLARE (clapperenhancersrc)
G_END_DECLS

View File

@@ -0,0 +1,495 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "clapper-enhancer-src-private.h"
#include "clapper-enhancer-director-private.h"
#include "../clapper-extractable-private.h"
#include "../clapper-harvest-private.h"
#include "../clapper-enhancers-loader-private.h"
#define GST_CAT_DEFAULT clapper_enhancer_src_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperEnhancerSrc
{
GstPushSrc parent;
GCancellable *cancellable;
gsize buf_size;
ClapperEnhancerDirector *director;
gchar *uri;
GUri *guri;
};
enum
{
PROP_0,
PROP_URI,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstURIType
clapper_enhancer_src_uri_handler_get_type (GType type)
{
return GST_URI_SRC;
}
static gpointer
_get_schemes_once (gpointer user_data G_GNUC_UNUSED)
{
return clapper_enhancers_loader_get_schemes (CLAPPER_TYPE_EXTRACTABLE);
}
static const gchar *const *
clapper_enhancer_src_uri_handler_get_protocols (GType type)
{
static GOnce schemes_once = G_ONCE_INIT;
g_once (&schemes_once, _get_schemes_once, NULL);
return (const gchar *const *) schemes_once.retval;
}
static gchar *
clapper_enhancer_src_uri_handler_get_uri (GstURIHandler *handler)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (handler);
gchar *uri;
GST_OBJECT_LOCK (self);
uri = g_strdup (self->uri);
GST_OBJECT_UNLOCK (self);
return uri;
}
static gboolean
clapper_enhancer_src_uri_handler_set_uri (GstURIHandler *handler,
const gchar *uri, GError **error)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (handler);
GUri *guri;
const gchar *const *protocols;
gboolean supported = FALSE;
guint i;
GST_DEBUG_OBJECT (self, "Changing URI to: %s", uri);
if (!uri) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"URI property cannot be NULL");
return FALSE;
}
if (GST_STATE (GST_ELEMENT_CAST (self)) >= GST_STATE_PAUSED) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
"Cannot change URI property while element is running");
return FALSE;
}
protocols = gst_uri_handler_get_protocols (handler);
for (i = 0; protocols[i]; ++i) {
if ((supported = gst_uri_has_protocol (uri, protocols[i])))
break;
}
if (!supported) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
"URI protocol is not supported");
return FALSE;
}
if (!(guri = g_uri_parse (uri, G_URI_FLAGS_ENCODED, NULL))) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"URI is invalid");
return FALSE;
}
if (!clapper_enhancers_loader_check (CLAPPER_TYPE_EXTRACTABLE,
g_uri_get_scheme (guri), g_uri_get_host (guri), NULL)) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"None of the available enhancers can handle this URI");
g_uri_unref (guri);
return FALSE;
}
GST_OBJECT_LOCK (self);
g_set_str (&self->uri, uri);
g_clear_pointer (&self->guri, g_uri_unref);
self->guri = guri;
GST_INFO_OBJECT (self, "URI changed to: \"%s\"", self->uri);
GST_OBJECT_UNLOCK (self);
return TRUE;
}
static void
_uri_handler_iface_init (GstURIHandlerInterface *iface)
{
iface->get_type = clapper_enhancer_src_uri_handler_get_type;
iface->get_protocols = clapper_enhancer_src_uri_handler_get_protocols;
iface->get_uri = clapper_enhancer_src_uri_handler_get_uri;
iface->set_uri = clapper_enhancer_src_uri_handler_set_uri;
}
#define parent_class clapper_enhancer_src_parent_class
G_DEFINE_TYPE_WITH_CODE (ClapperEnhancerSrc, clapper_enhancer_src, GST_TYPE_PUSH_SRC,
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, _uri_handler_iface_init));
GST_ELEMENT_REGISTER_DEFINE (clapperenhancersrc, "clapperenhancersrc",
512, CLAPPER_TYPE_ENHANCER_SRC);
static gboolean
clapper_enhancer_src_start (GstBaseSrc *base_src)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
gboolean can_start;
GST_DEBUG_OBJECT (self, "Start");
GST_OBJECT_LOCK (self);
can_start = (self->guri != NULL);
GST_OBJECT_UNLOCK (self);
if (G_UNLIKELY (!can_start)) {
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
("No media URI"), (NULL));
return FALSE;
}
return TRUE;
}
static gboolean
clapper_enhancer_src_stop (GstBaseSrc *base_src)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
GST_DEBUG_OBJECT (self, "Stop");
self->buf_size = 0;
return TRUE;
}
static gboolean
clapper_enhancer_src_get_size (GstBaseSrc *base_src, guint64 *size)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
if (self->buf_size > 0) {
*size = self->buf_size;
return TRUE;
}
return FALSE;
}
static gboolean
clapper_enhancer_src_is_seekable (GstBaseSrc *base_src)
{
return FALSE;
}
static gboolean
clapper_enhancer_src_unlock (GstBaseSrc *base_src)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
GST_LOG_OBJECT (self, "Cancel triggered");
g_cancellable_cancel (self->cancellable);
return TRUE;
}
static gboolean
clapper_enhancer_src_unlock_stop (GstBaseSrc *base_src)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
GST_LOG_OBJECT (self, "Resetting cancellable");
g_object_unref (self->cancellable);
self->cancellable = g_cancellable_new ();
return TRUE;
}
/* Pushes tags, toc and request headers downstream (all transfer full) */
static void
_push_events (ClapperEnhancerSrc *self, GstTagList *tags, GstToc *toc,
GstStructure *headers, gboolean updated)
{
GstEvent *event;
if (tags) {
if (!gst_tag_list_is_empty (tags)) {
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, tags);
/* XXX: Normally, we should only be posting event to make it reach
* the app after stream start, but currently it is lost that way */
gst_element_post_message (GST_ELEMENT (self),
gst_message_new_tag (GST_OBJECT_CAST (self), tags));
} else {
gst_tag_list_unref (tags);
}
}
if (toc) {
if (g_list_length (gst_toc_get_entries (toc)) > 0) {
GST_DEBUG_OBJECT (self, "Pushing TOC"); // TOC is not printable
/* XXX: Normally, we should only be posting event to make it reach
* the app after stream start, but currently it is lost that way */
gst_element_post_message (GST_ELEMENT (self),
gst_message_new_toc (GST_OBJECT_CAST (self), toc, updated));
}
gst_toc_unref (toc);
}
if (headers) {
GstStructure *http_headers;
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, headers);
http_headers = gst_structure_new ("http-headers",
"request-headers", GST_TYPE_STRUCTURE, headers,
NULL);
gst_structure_free (headers);
event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, http_headers);
gst_pad_push_event (GST_BASE_SRC_PAD (self), event);
}
GST_DEBUG_OBJECT (self, "Pushed all events");
}
static GstFlowReturn
clapper_enhancer_src_create (GstPushSrc *push_src, GstBuffer **outbuf)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (push_src);
GUri *guri;
GCancellable *cancellable;
ClapperHarvest *harvest;
GstCaps *caps = NULL;
GstTagList *tags = NULL;
GstToc *toc = NULL;
GstStructure *headers = NULL;
GError *error = NULL;
gboolean unpacked;
/* When non-zero, we already returned complete data */
if (self->buf_size > 0)
return GST_FLOW_EOS;
/* Ensure director is created. Since it spins up its own
* thread, create it here as we know that it will be used. */
if (!self->director)
self->director = clapper_enhancer_director_new ();
GST_OBJECT_LOCK (self);
guri = g_uri_ref (self->guri);
cancellable = g_object_ref (self->cancellable);
GST_OBJECT_UNLOCK (self);
harvest = clapper_enhancer_director_extract (self->director, guri, cancellable, &error);
g_uri_unref (guri);
g_object_unref (cancellable);
if (!harvest) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("%s", error->message), (NULL));
g_clear_error (&error);
return GST_FLOW_ERROR;
}
unpacked = clapper_harvest_unpack (harvest, outbuf, &self->buf_size,
&caps, &tags, &toc, &headers);
gst_object_unref (harvest);
if (!unpacked) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Extraction harvest is empty"), (NULL));
return GST_FLOW_ERROR;
}
if (gst_base_src_set_caps (GST_BASE_SRC (self), caps))
GST_INFO_OBJECT (self, "Using caps: %" GST_PTR_FORMAT, caps);
else
GST_ERROR_OBJECT (self, "Current caps could not be set");
gst_clear_caps (&caps);
/* Now push all events before buffer */
_push_events (self, tags, toc, headers, FALSE);
return GST_FLOW_OK;
}
static inline gboolean
_handle_uri_query (GstQuery *query)
{
/* Since our URI does not actually lead to manifest data, we answer
* with "nodata" equivalent, so upstream will not try to fetch it */
gst_query_set_uri (query, "data:,");
return TRUE;
}
static gboolean
clapper_enhancer_src_query (GstBaseSrc *base_src, GstQuery *query)
{
gboolean ret = FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_URI:
ret = _handle_uri_query (query);
break;
default:
break;
}
if (!ret)
ret = GST_BASE_SRC_CLASS (parent_class)->query (base_src, query);
return ret;
}
static void
clapper_enhancer_src_init (ClapperEnhancerSrc *self)
{
self->cancellable = g_cancellable_new ();
}
static void
clapper_enhancer_src_dispose (GObject *object)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
GST_OBJECT_LOCK (self);
g_clear_object (&self->director);
GST_OBJECT_UNLOCK (self);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
clapper_enhancer_src_finalize (GObject *object)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
g_clear_object (&self->cancellable);
g_free (self->uri);
g_clear_pointer (&self->guri, g_uri_unref);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_enhancer_src_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
switch (prop_id) {
case PROP_URI:{
GError *error = NULL;
if (!gst_uri_handler_set_uri (GST_URI_HANDLER (self),
g_value_get_string (value), &error)) {
GST_ERROR_OBJECT (self, "%s", error->message);
g_error_free (error);
}
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_enhancer_src_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
switch (prop_id) {
case PROP_URI:
g_value_take_string (value, gst_uri_handler_get_uri (GST_URI_HANDLER (self)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_enhancer_src_class_init (ClapperEnhancerSrcClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersrc", 0,
"Clapper Enhancer Source");
gobject_class->set_property = clapper_enhancer_src_set_property;
gobject_class->get_property = clapper_enhancer_src_get_property;
gobject_class->dispose = clapper_enhancer_src_dispose;
gobject_class->finalize = clapper_enhancer_src_finalize;
gstbasesrc_class->start = clapper_enhancer_src_start;
gstbasesrc_class->stop = clapper_enhancer_src_stop;
gstbasesrc_class->get_size = clapper_enhancer_src_get_size;
gstbasesrc_class->is_seekable = clapper_enhancer_src_is_seekable;
gstbasesrc_class->unlock = clapper_enhancer_src_unlock;
gstbasesrc_class->unlock_stop = clapper_enhancer_src_unlock_stop;
gstbasesrc_class->query = clapper_enhancer_src_query;
gstpushsrc_class->create = clapper_enhancer_src_create;
param_specs[PROP_URI] = g_param_spec_string ("uri",
"URI", "URI", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
gst_element_class_set_static_metadata (gstelement_class, "Clapper Enhancer Source",
"Source", "A source element that uses Clapper Enhancers to produce data",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
}

View File

@@ -0,0 +1,30 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
G_BEGIN_DECLS
G_GNUC_INTERNAL
gboolean clapper_gst_plugin_init (GstPlugin *plugin);
G_END_DECLS

View File

@@ -0,0 +1,53 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include <gst/gst.h>
#include "clapper-plugin-private.h"
#include "../clapper-functionalities-availability.h"
#if CLAPPER_WITH_ENHANCERS_LOADER
#include "clapper-enhancer-src-private.h"
#include "../clapper-extractable-private.h"
#include "../clapper-enhancers-loader-private.h"
#endif
#include "clapper-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;
}

View File

@@ -0,0 +1,36 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
#include <gst/gstbin.h>
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

View File

@@ -0,0 +1,462 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gst/base/gstadapter.h>
#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 <rafostar.github@gmail.com>");
}

View File

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