mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 15:22:11 +02:00
Compare commits
34 Commits
mpris-comp
...
b9e98e2fdb
Author | SHA1 | Date | |
---|---|---|---|
|
b9e98e2fdb | ||
|
555b93451e | ||
|
cdfac76d66 | ||
|
ed13d53db9 | ||
|
acf2b75f27 | ||
|
d9c8534bb7 | ||
|
292b339e8a | ||
|
ddfedddce5 | ||
|
743097060e | ||
|
b0b15cec5c | ||
|
b9a8b28a1f | ||
|
4c8c76c8f7 | ||
|
a5db6f701d | ||
|
4301a9a9fa | ||
|
c21e1c1c6a | ||
|
92e0e22e8b | ||
|
4d5519b42d | ||
|
4a34fb3484 | ||
|
d8ea220aad | ||
|
dc56cb201a | ||
|
07f944fb31 | ||
|
b2e6533c30 | ||
|
7a56fbfff8 | ||
|
7457ffda13 | ||
|
bee2e08fb1 | ||
|
0c1d291006 | ||
|
4002a63e3a | ||
|
ff054743e6 | ||
|
9432156aec | ||
|
47d3ebe693 | ||
|
5f8270f0e8 | ||
|
54f059aaa3 | ||
|
f63e13ed39 | ||
|
daadabba8d |
49
examples/clapper-gtk/audio/simple/python/example.py
Executable file
49
examples/clapper-gtk/audio/simple/python/example.py
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import gi
|
||||
gi.require_version('Adw', '1')
|
||||
gi.require_version('Clapper', '0.0')
|
||||
gi.require_version('ClapperGtk', '0.0')
|
||||
gi.require_version('Gtk', '4.0')
|
||||
from gi.repository import Adw, Clapper, ClapperGtk, Gtk
|
||||
|
||||
Clapper.init(None)
|
||||
|
||||
def on_activate(app):
|
||||
# Create our widgets.
|
||||
win = Gtk.ApplicationWindow(application=app, title='Clapper Audio', default_width=640, default_height=96)
|
||||
audio = ClapperGtk.Audio()
|
||||
box = Gtk.Box(valign=Gtk.Align.CENTER, margin_start=8, margin_end=8, spacing=4)
|
||||
prev_btn = ClapperGtk.PreviousItemButton()
|
||||
play_btn = ClapperGtk.TogglePlayButton()
|
||||
next_btn = ClapperGtk.NextItemButton()
|
||||
seek_bar = ClapperGtk.SeekBar()
|
||||
|
||||
# Add media for playback. First media item in queue will be automatically selected.
|
||||
item = Clapper.MediaItem(uri='https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3')
|
||||
audio.props.player.props.queue.add_item(item)
|
||||
|
||||
item = Clapper.MediaItem(uri='https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3')
|
||||
audio.props.player.props.queue.add_item(item)
|
||||
|
||||
# Assemble window.
|
||||
box.append(prev_btn)
|
||||
box.append(play_btn)
|
||||
box.append(next_btn)
|
||||
box.append(seek_bar)
|
||||
audio.set_child(box)
|
||||
win.set_child(audio)
|
||||
win.present()
|
||||
|
||||
# Not too loud. Mind the ears.
|
||||
audio.props.player.props.volume = 0.7
|
||||
|
||||
# Start playback.
|
||||
audio.props.player.play()
|
||||
|
||||
# Create a new application.
|
||||
app = Adw.Application(application_id='com.example.ClapperAudio')
|
||||
app.connect('activate', on_activate)
|
||||
|
||||
# Run the application.
|
||||
app.run(None)
|
@@ -1,5 +1,5 @@
|
||||
project('clapper', 'c',
|
||||
version: '0.9.0',
|
||||
version: '0.9.1',
|
||||
meson_version: '>= 0.64.0',
|
||||
license: 'LGPL-2.1-or-later AND GPL-3.0-or-later', # LGPL-2.1+ for libs and gst-plugin, GPL-3.0+ for app
|
||||
default_options: [
|
||||
|
@@ -16,7 +16,7 @@
|
||||
"autodelete": false
|
||||
},
|
||||
"com.github.rafostar.Clapper.Enhancers": {
|
||||
"versions": "master;test;stable",
|
||||
"versions": "stable;test;master",
|
||||
"directory": "extensions/clapper/enhancers",
|
||||
"add-ld-path": "lib",
|
||||
"no-autodownload": false,
|
||||
|
@@ -12,7 +12,7 @@
|
||||
"autodelete": false
|
||||
},
|
||||
"com.github.rafostar.Clapper.Enhancers": {
|
||||
"versions": "master;test;stable",
|
||||
"versions": "stable;test;master",
|
||||
"directory": "extensions/clapper/enhancers",
|
||||
"add-ld-path": "lib",
|
||||
"no-autodownload": false,
|
||||
|
Submodule pkgs/flatpak/flathub updated: b7c25acba8...3a672be190
@@ -642,18 +642,6 @@ clapper_app_application_command_line (GApplication *app, GApplicationCommandLine
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_is_claps_file (GFile *file)
|
||||
{
|
||||
gchar *basename = g_file_get_basename (file);
|
||||
gboolean is_claps;
|
||||
|
||||
is_claps = (basename && g_str_has_suffix (basename, ".claps"));
|
||||
g_free (basename);
|
||||
|
||||
return is_claps;
|
||||
}
|
||||
|
||||
static void
|
||||
add_item_from_file (GFile *file, ClapperQueue *queue)
|
||||
{
|
||||
@@ -666,51 +654,6 @@ add_item_from_file (GFile *file, ClapperQueue *queue)
|
||||
gst_object_unref (item);
|
||||
}
|
||||
|
||||
static void
|
||||
add_items_from_claps_file (GFile *file, ClapperQueue *queue)
|
||||
{
|
||||
GDataInputStream *dstream = NULL;
|
||||
GFileInputStream *stream;
|
||||
GError *error = NULL;
|
||||
gchar *line;
|
||||
|
||||
if (!(stream = g_file_read (file, NULL, &error)))
|
||||
goto finish;
|
||||
|
||||
dstream = g_data_input_stream_new (G_INPUT_STREAM (stream));
|
||||
|
||||
while ((line = g_data_input_stream_read_line (
|
||||
dstream, NULL, NULL, &error))) {
|
||||
g_strstrip (line);
|
||||
|
||||
if (strlen (line) > 0) {
|
||||
GFile *tmp_file = gst_uri_is_valid (line)
|
||||
? g_file_new_for_uri (line)
|
||||
: g_file_new_for_path (line);
|
||||
|
||||
if (_is_claps_file (tmp_file))
|
||||
add_items_from_claps_file (tmp_file, queue);
|
||||
else
|
||||
add_item_from_file (tmp_file, queue);
|
||||
|
||||
g_object_unref (tmp_file);
|
||||
}
|
||||
|
||||
g_free (line);
|
||||
}
|
||||
|
||||
finish:
|
||||
if (error) {
|
||||
GST_ERROR ("Could not read \".claps\" file, reason: %s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
if (stream) {
|
||||
g_input_stream_close (G_INPUT_STREAM (stream), NULL, NULL);
|
||||
g_object_unref (stream);
|
||||
}
|
||||
g_clear_object (&dstream);
|
||||
}
|
||||
|
||||
static void
|
||||
add_item_with_subtitles (GFile *media_file,
|
||||
GFile *subs_file, ClapperQueue *queue)
|
||||
@@ -779,12 +722,8 @@ clapper_app_application_open (GApplication *app,
|
||||
if (!handled) {
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < n_files; ++i) {
|
||||
if (_is_claps_file (files[i]))
|
||||
add_items_from_claps_file (files[i], queue);
|
||||
else
|
||||
add_item_from_file (files[i], queue);
|
||||
}
|
||||
for (i = 0; i < n_files; ++i)
|
||||
add_item_from_file (files[i], queue);
|
||||
}
|
||||
|
||||
add_only = (g_strcmp0 (hint, "add-only") == 0);
|
||||
|
@@ -582,7 +582,7 @@ _create_pipeline_svg_file_in_thread (GTask *task, GObject *source G_GNUC_UNUSED,
|
||||
GVC_t *gvc;
|
||||
gchar *path, *template = NULL, *dot_data = NULL, *img_data = NULL;
|
||||
gint fd;
|
||||
guint size = 0;
|
||||
gsize size = 0;
|
||||
|
||||
if (!(tmp_subdir = _create_tmp_subdir ("pipelines", cancellable, &error)))
|
||||
goto finish;
|
||||
@@ -610,7 +610,16 @@ _create_pipeline_svg_file_in_thread (GTask *task, GObject *source G_GNUC_UNUSED,
|
||||
|
||||
gvc = gvContext ();
|
||||
gvLayout (gvc, graph, "dot");
|
||||
|
||||
#ifdef HAVE_GVC_13
|
||||
gvRenderData (gvc, graph, "svg", &img_data, &size);
|
||||
#else
|
||||
{
|
||||
guint tmp_size = 0; // Temporary uint to satisfy older API
|
||||
gvRenderData (gvc, graph, "svg", &img_data, &tmp_size);
|
||||
size = tmp_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
agclose (graph);
|
||||
gvFreeContext (gvc);
|
||||
|
@@ -231,7 +231,7 @@ video_map_cb (GtkWidget *widget, ClapperAppWindow *self)
|
||||
|
||||
GST_TRACE_OBJECT (self, "Video map");
|
||||
|
||||
player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self->video));
|
||||
|
||||
g_signal_connect (player, "notify::volume",
|
||||
G_CALLBACK (_player_volume_changed_cb), self);
|
||||
@@ -252,7 +252,7 @@ video_unmap_cb (GtkWidget *widget, ClapperAppWindow *self)
|
||||
|
||||
GST_TRACE_OBJECT (self, "Video unmap");
|
||||
|
||||
player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self->video));
|
||||
|
||||
g_signal_handlers_disconnect_by_func (player, _player_volume_changed_cb, self);
|
||||
g_signal_handlers_disconnect_by_func (player, _player_speed_changed_cb, self);
|
||||
@@ -524,7 +524,7 @@ drag_update_cb (GtkGestureDrag *drag,
|
||||
static inline void
|
||||
_alter_volume (ClapperAppWindow *self, gdouble dy)
|
||||
{
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self->video));
|
||||
gdouble volume = clapper_player_get_volume (player);
|
||||
|
||||
/* We do not want for volume to change too suddenly */
|
||||
@@ -547,7 +547,7 @@ _alter_volume (ClapperAppWindow *self, gdouble dy)
|
||||
static inline void
|
||||
_alter_speed (ClapperAppWindow *self, gdouble dx)
|
||||
{
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self->video));
|
||||
gdouble speed = clapper_player_get_speed (player);
|
||||
|
||||
speed -= dx * 0.02;
|
||||
@@ -571,8 +571,7 @@ _begin_seek_operation (ClapperAppWindow *self)
|
||||
if (self->seeking)
|
||||
return FALSE;
|
||||
|
||||
player = clapper_gtk_video_get_player (
|
||||
CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self->video));
|
||||
queue = clapper_player_get_queue (player);
|
||||
current_item = clapper_queue_get_current_item (queue);
|
||||
|
||||
@@ -600,8 +599,8 @@ static void
|
||||
_end_seek_operation (ClapperAppWindow *self)
|
||||
{
|
||||
if (self->seeking && self->current_duration > 0) {
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (
|
||||
CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (
|
||||
CLAPPER_GTK_AV_CAST (self->video));
|
||||
|
||||
clapper_player_seek_custom (player, self->pending_position,
|
||||
g_settings_get_int (self->settings, "seek-method"));
|
||||
@@ -764,8 +763,8 @@ _handle_seek_key_press (ClapperAppWindow *self, gboolean forward)
|
||||
static void
|
||||
_handle_chapter_key_press (ClapperAppWindow *self, gboolean forward)
|
||||
{
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (
|
||||
CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (
|
||||
CLAPPER_GTK_AV_CAST (self->video));
|
||||
ClapperQueue *queue = clapper_player_get_queue (player);
|
||||
ClapperMediaItem *current_item = clapper_queue_get_current_item (queue);
|
||||
ClapperTimeline *timeline;
|
||||
@@ -855,8 +854,8 @@ _handle_chapter_key_press (ClapperAppWindow *self, gboolean forward)
|
||||
static void
|
||||
_handle_item_key_press (ClapperAppWindow *self, gboolean forward)
|
||||
{
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (
|
||||
CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (
|
||||
CLAPPER_GTK_AV_CAST (self->video));
|
||||
ClapperQueue *queue = clapper_player_get_queue (player);
|
||||
guint prev_index, index;
|
||||
|
||||
@@ -864,7 +863,7 @@ _handle_item_key_press (ClapperAppWindow *self, gboolean forward)
|
||||
|
||||
prev_index = clapper_queue_get_current_index (queue);
|
||||
gtk_widget_activate_action (self->video,
|
||||
(forward) ? "video.next-item" : "video.previous-item", NULL);
|
||||
(forward) ? "av.next-item" : "av.previous-item", NULL);
|
||||
index = clapper_queue_get_current_index (queue);
|
||||
|
||||
/* Notify only when changed */
|
||||
@@ -881,14 +880,14 @@ _handle_speed_key_press (ClapperAppWindow *self, gboolean forward)
|
||||
forward ^= (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
|
||||
|
||||
gtk_widget_activate_action (self->video,
|
||||
(forward) ? "video.speed-up" : "video.speed-down", NULL);
|
||||
(forward) ? "av.speed-up" : "av.speed-down", NULL);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_handle_progression_key_press (ClapperAppWindow *self)
|
||||
{
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (
|
||||
CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (
|
||||
CLAPPER_GTK_AV_CAST (self->video));
|
||||
ClapperQueue *queue = clapper_player_get_queue (player);
|
||||
ClapperQueueProgressionMode mode;
|
||||
const gchar *icon = NULL, *label = NULL;
|
||||
@@ -908,11 +907,11 @@ key_pressed_cb (GtkEventControllerKey *controller, guint keyval,
|
||||
switch (keyval) {
|
||||
case GDK_KEY_Up:
|
||||
if ((state & GDK_MODIFIER_MASK) == 0)
|
||||
gtk_widget_activate_action (self->video, "video.volume-up", NULL);
|
||||
gtk_widget_activate_action (self->video, "av.volume-up", NULL);
|
||||
break;
|
||||
case GDK_KEY_Down:
|
||||
if ((state & GDK_MODIFIER_MASK) == 0)
|
||||
gtk_widget_activate_action (self->video, "video.volume-down", NULL);
|
||||
gtk_widget_activate_action (self->video, "av.volume-down", NULL);
|
||||
break;
|
||||
case GDK_KEY_Left:
|
||||
if ((state & GDK_MODIFIER_MASK) == 0) {
|
||||
@@ -943,7 +942,7 @@ key_pressed_cb (GtkEventControllerKey *controller, guint keyval,
|
||||
case GDK_KEY_space:
|
||||
case GDK_KEY_k:
|
||||
if (!self->key_held && (state & GDK_MODIFIER_MASK) == 0)
|
||||
gtk_widget_activate_action (self->video, "video.toggle-play", NULL);
|
||||
gtk_widget_activate_action (self->video, "av.toggle-play", NULL);
|
||||
break;
|
||||
case GDK_KEY_less:
|
||||
if (!self->key_held) // Needs seek (action is slow)
|
||||
@@ -955,7 +954,7 @@ key_pressed_cb (GtkEventControllerKey *controller, guint keyval,
|
||||
break;
|
||||
case GDK_KEY_m:
|
||||
if (!self->key_held && (state & GDK_MODIFIER_MASK) == 0)
|
||||
gtk_widget_activate_action (self->video, "video.toggle-mute", NULL);
|
||||
gtk_widget_activate_action (self->video, "av.toggle-mute", NULL);
|
||||
break;
|
||||
case GDK_KEY_p:
|
||||
if (!self->key_held && (state & GDK_MODIFIER_MASK) == 0)
|
||||
@@ -1123,7 +1122,7 @@ clapper_app_window_get_video (ClapperAppWindow *self)
|
||||
ClapperPlayer *
|
||||
clapper_app_window_get_player (ClapperAppWindow *self)
|
||||
{
|
||||
return clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video));
|
||||
return clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self->video));
|
||||
}
|
||||
|
||||
ClapperAppWindowExtraOptions *
|
||||
@@ -1198,6 +1197,7 @@ clapper_app_window_init (ClapperAppWindow *self)
|
||||
GtkSettings *settings;
|
||||
GtkWidget *dummy_titlebar;
|
||||
gint distance = 0;
|
||||
GtkWindowGroup *group;
|
||||
|
||||
gtk_widget_set_size_request (GTK_WIDGET (self),
|
||||
MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
|
||||
@@ -1229,6 +1229,11 @@ clapper_app_window_init (ClapperAppWindow *self)
|
||||
|
||||
gtk_drop_target_set_gtypes (self->drop_target,
|
||||
(GType[3]) { GDK_TYPE_FILE_LIST, G_TYPE_FILE, G_TYPE_STRING }, 3);
|
||||
|
||||
/* Add to window group */
|
||||
group = gtk_window_group_new ();
|
||||
gtk_window_group_add_window (group, GTK_WINDOW (self));
|
||||
g_object_unref (group);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -105,6 +105,9 @@ if not pp_option.disabled()
|
||||
if cgraph_dep.found() and gvc_dep.found()
|
||||
clapperapp_c_args += ['-DHAVE_GRAPHVIZ']
|
||||
clapperapp_deps += [cgraph_dep, gvc_dep]
|
||||
if gvc_dep.version().version_compare('>= 13.0.0')
|
||||
clapperapp_c_args += ['-DHAVE_GVC_13']
|
||||
endif
|
||||
clapperapp_available_functionalities += 'pipeline-preview'
|
||||
elif pp_option.enabled()
|
||||
error('pipeline-preview option was enabled, but required dependencies were not found')
|
||||
|
268
src/lib/clapper-gtk/clapper-gtk-audio.c
Normal file
268
src/lib/clapper-gtk/clapper-gtk-audio.c
Normal file
@@ -0,0 +1,268 @@
|
||||
/* Clapper GTK Integration Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ClapperGtkAudio:
|
||||
*
|
||||
* A GTK widget for audio playback with Clapper API.
|
||||
*
|
||||
* #ClapperGtkAudio is a widget meant for integrating audio playback
|
||||
* within GTK application. It exposes [class@Clapper.Player] through its
|
||||
* base class [property@ClapperGtk.Av:player] property.
|
||||
*
|
||||
* Other widgets (buttons, seek bar, etc.) provided by `ClapperGtk` library, once placed
|
||||
* anywhere inside audio container (including nesting within another widget like [class@Gtk.Box])
|
||||
* will automatically control #ClapperGtkAudio they are within. This allows to freely create
|
||||
* custom UI best suited for specific application.
|
||||
*
|
||||
* # Basic usage
|
||||
*
|
||||
* A typical use case is to embed audio widget as part of your app where audio playback
|
||||
* is needed (can be even the very first child of the window). Get the [class@Clapper.Player]
|
||||
* belonging to the AV widget and start adding new [class@Clapper.MediaItem] items to the
|
||||
* [class@Clapper.Queue] for playback. For more information please refer to the Clapper
|
||||
* playback library documentation.
|
||||
*
|
||||
* # Actions
|
||||
*
|
||||
* You can use built-in actions of parent [class@ClapperGtk.Av].
|
||||
* See its documentation for the list of available ones.
|
||||
*
|
||||
* # ClapperGtkAudio as GtkBuildable
|
||||
*
|
||||
* #ClapperGtkAudio implementation of the [iface@Gtk.Buildable] interface supports
|
||||
* placing a single widget (which might then hold multiple widgets) as `<child>` element.
|
||||
*
|
||||
* ```xml
|
||||
* <object class="ClapperGtkAudio" id="audio">
|
||||
* <child>
|
||||
* <object class="GtkBox">
|
||||
* <property name="orientation">horizontal</property>
|
||||
* <child>
|
||||
* <object class="ClapperGtkPreviousItemButton">
|
||||
* </child>
|
||||
* <child>
|
||||
* <object class="ClapperGtkTogglePlayButton">
|
||||
* </child>
|
||||
* <child>
|
||||
* <object class="ClapperGtkNextItemButton">
|
||||
* </child>
|
||||
* </object>
|
||||
* </child>
|
||||
* </object>
|
||||
* ```
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "clapper-gtk-audio.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_gtk_audio_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _ClapperGtkAudio
|
||||
{
|
||||
ClapperGtkAv parent;
|
||||
|
||||
GtkWidget *child;
|
||||
};
|
||||
|
||||
static void
|
||||
clapper_gtk_audio_add_child (GtkBuildable *buildable,
|
||||
GtkBuilder *builder, GObject *child, const char *type)
|
||||
{
|
||||
if (GTK_IS_WIDGET (child)) {
|
||||
clapper_gtk_audio_set_child (CLAPPER_GTK_AUDIO (buildable), GTK_WIDGET (child));
|
||||
} else {
|
||||
GtkBuildableIface *parent_iface = g_type_interface_peek_parent (GTK_BUILDABLE_GET_IFACE (buildable));
|
||||
parent_iface->add_child (buildable, builder, child, type);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_buildable_iface_init (GtkBuildableIface *iface)
|
||||
{
|
||||
iface->add_child = clapper_gtk_audio_add_child;
|
||||
}
|
||||
|
||||
#define parent_class clapper_gtk_audio_parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (ClapperGtkAudio, clapper_gtk_audio, CLAPPER_GTK_TYPE_AV,
|
||||
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, _buildable_iface_init))
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_CHILD,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static inline void
|
||||
_unparent_child (ClapperGtkAudio *self)
|
||||
{
|
||||
GtkWidget *child;
|
||||
|
||||
if ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
|
||||
gtk_widget_unparent (child);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_audio_new:
|
||||
*
|
||||
* Creates a new #ClapperGtkAudio instance.
|
||||
*
|
||||
* Newly created audio widget will also have set "scaletempo" GStreamer element
|
||||
* as default audio filter on its [class@Clapper.Player] and disable video and
|
||||
* subtitle streams. This can be changed after construction by setting
|
||||
* corresponding player properties.
|
||||
*
|
||||
* Returns: a new audio #GtkWidget.
|
||||
*/
|
||||
GtkWidget *
|
||||
clapper_gtk_audio_new (void)
|
||||
{
|
||||
return g_object_new (CLAPPER_GTK_TYPE_AUDIO, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_audio_set_child:
|
||||
* @audio: a #ClapperGtkAudio
|
||||
* @child: (nullable): a #GtkWidget
|
||||
*
|
||||
* Set a child #GtkWidget of @audio.
|
||||
*/
|
||||
void
|
||||
clapper_gtk_audio_set_child (ClapperGtkAudio *self, GtkWidget *child)
|
||||
{
|
||||
g_return_if_fail (CLAPPER_GTK_IS_AUDIO (self));
|
||||
g_return_if_fail (GTK_IS_WIDGET (child));
|
||||
|
||||
_unparent_child (self);
|
||||
if (child)
|
||||
gtk_widget_set_parent (child, GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_audio_get_child:
|
||||
* @audio: a #ClapperGtkAudio
|
||||
*
|
||||
* Get a child #GtkWidget of @audio.
|
||||
*
|
||||
* Returns: (transfer none) (nullable): #GtkWidget set as child.
|
||||
*/
|
||||
GtkWidget *
|
||||
clapper_gtk_audio_get_child (ClapperGtkAudio *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_AUDIO (self), NULL);
|
||||
|
||||
return gtk_widget_get_first_child (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_audio_init (ClapperGtkAudio *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_audio_constructed (GObject *object)
|
||||
{
|
||||
ClapperGtkAudio *self = CLAPPER_GTK_AUDIO_CAST (object);
|
||||
ClapperPlayer *player;
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
|
||||
player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self));
|
||||
|
||||
clapper_player_set_video_enabled (player, FALSE);
|
||||
clapper_player_set_subtitles_enabled (player, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_audio_dispose (GObject *object)
|
||||
{
|
||||
ClapperGtkAudio *self = CLAPPER_GTK_AUDIO_CAST (object);
|
||||
|
||||
_unparent_child (self);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_audio_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperGtkAudio *self = CLAPPER_GTK_AUDIO_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_CHILD:
|
||||
g_value_set_object (value, clapper_gtk_audio_get_child (self));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_audio_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperGtkAudio *self = CLAPPER_GTK_AUDIO_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_CHILD:
|
||||
clapper_gtk_audio_set_child (self, g_value_get_object (value));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_audio_class_init (ClapperGtkAudioClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkaudio", GST_DEBUG_FG_MAGENTA,
|
||||
"Clapper GTK Audio");
|
||||
|
||||
gobject_class->constructed = clapper_gtk_audio_constructed;
|
||||
gobject_class->get_property = clapper_gtk_audio_get_property;
|
||||
gobject_class->set_property = clapper_gtk_audio_set_property;
|
||||
gobject_class->dispose = clapper_gtk_audio_dispose;
|
||||
|
||||
/**
|
||||
* ClapperGtkAudio:child:
|
||||
*
|
||||
* The child widget of `ClapperGtkAudio`.
|
||||
*/
|
||||
param_specs[PROP_CHILD] = g_param_spec_object ("child",
|
||||
NULL, NULL, GTK_TYPE_WIDGET,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
||||
|
||||
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
|
||||
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC);
|
||||
gtk_widget_class_set_css_name (widget_class, "clapper-gtk-audio");
|
||||
}
|
49
src/lib/clapper-gtk/clapper-gtk-audio.h
Normal file
49
src/lib/clapper-gtk/clapper-gtk-audio.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* Clapper GTK Integration Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION)
|
||||
#error "Only <clapper-gtk/clapper-gtk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <clapper-gtk/clapper-gtk-av.h>
|
||||
#include <clapper-gtk/clapper-gtk-visibility.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_GTK_TYPE_AUDIO (clapper_gtk_audio_get_type())
|
||||
#define CLAPPER_GTK_AUDIO_CAST(obj) ((ClapperGtkAudio *)(obj))
|
||||
|
||||
CLAPPER_GTK_API
|
||||
G_DECLARE_FINAL_TYPE (ClapperGtkAudio, clapper_gtk_audio, CLAPPER_GTK, AUDIO, ClapperGtkAv)
|
||||
|
||||
CLAPPER_GTK_API
|
||||
GtkWidget * clapper_gtk_audio_new (void);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
void clapper_gtk_audio_set_child (ClapperGtkAudio *audio, GtkWidget *child);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
GtkWidget * clapper_gtk_audio_get_child (ClapperGtkAudio *audio);
|
||||
|
||||
G_END_DECLS
|
645
src/lib/clapper-gtk/clapper-gtk-av.c
Normal file
645
src/lib/clapper-gtk/clapper-gtk-av.c
Normal file
@@ -0,0 +1,645 @@
|
||||
/* Clapper GTK Integration Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ClapperGtkAv:
|
||||
*
|
||||
* A base class for GTK audio and video widgets.
|
||||
*
|
||||
* See its descendants: [class@ClapperGtk.Audio] and [class@ClapperGtk.Video].
|
||||
*
|
||||
* # Actions
|
||||
*
|
||||
* #ClapperGtkAv defines a set of built-in actions:
|
||||
*
|
||||
* ```yaml
|
||||
* - "av.toggle-play": toggle play/pause
|
||||
* - "av.play": start/resume playback
|
||||
* - "av.pause": pause playback
|
||||
* - "av.stop": stop playback
|
||||
* - "av.seek": seek to position (variant "d")
|
||||
* - "av.seek-custom": seek to position using seek method (variant "(di)")
|
||||
* - "av.toggle-mute": toggle mute state
|
||||
* - "av.set-mute": set mute state (variant "b")
|
||||
* - "av.volume-up": increase volume by 2%
|
||||
* - "av.volume-down": decrease volume by 2%
|
||||
* - "av.set-volume": set volume to specified value (variant "d")
|
||||
* - "av.speed-up": increase speed (from 0.05x - 2x range to nearest quarter)
|
||||
* - "av.speed-down": decrease speed (from 0.05x - 2x range to nearest quarter)
|
||||
* - "av.set-speed": set speed to specified value (variant "d")
|
||||
* - "av.previous-item": select previous item in queue
|
||||
* - "av.next-item": select next item in queue
|
||||
* - "av.select-item": select item at specified index in queue (variant "u")
|
||||
* ```
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "clapper-gtk-av.h"
|
||||
|
||||
#define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01)
|
||||
|
||||
#define DEFAULT_AUTO_INHIBIT FALSE
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_gtk_av_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
typedef struct _ClapperGtkAvPrivate ClapperGtkAvPrivate;
|
||||
|
||||
struct _ClapperGtkAvPrivate
|
||||
{
|
||||
ClapperPlayer *player;
|
||||
gboolean auto_inhibit;
|
||||
|
||||
guint inhibit_cookie;
|
||||
};
|
||||
|
||||
#define parent_class clapper_gtk_av_parent_class
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (ClapperGtkAv, clapper_gtk_av, GTK_TYPE_WIDGET)
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_PLAYER,
|
||||
PROP_AUTO_INHIBIT,
|
||||
PROP_INHIBITED,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static gboolean provider_added = FALSE;
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static void
|
||||
toggle_play_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
|
||||
switch (clapper_player_get_state (player)) {
|
||||
case CLAPPER_PLAYER_STATE_PLAYING:
|
||||
clapper_player_pause (player);
|
||||
break;
|
||||
case CLAPPER_PLAYER_STATE_STOPPED:
|
||||
case CLAPPER_PLAYER_STATE_PAUSED:
|
||||
clapper_player_play (player);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
play_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
|
||||
clapper_player_play (player);
|
||||
}
|
||||
|
||||
static void
|
||||
pause_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
|
||||
clapper_player_pause (player);
|
||||
}
|
||||
|
||||
static void
|
||||
stop_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
|
||||
clapper_player_stop (player);
|
||||
}
|
||||
|
||||
static void
|
||||
seek_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
gdouble position = g_variant_get_double (parameter);
|
||||
|
||||
clapper_player_seek (player, position);
|
||||
}
|
||||
|
||||
static void
|
||||
seek_custom_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
ClapperPlayerSeekMethod method = CLAPPER_PLAYER_SEEK_METHOD_NORMAL;
|
||||
gdouble position = 0;
|
||||
|
||||
g_variant_get (parameter, "(di)", &position, &method);
|
||||
clapper_player_seek_custom (player, position, method);
|
||||
}
|
||||
|
||||
static void
|
||||
toggle_mute_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
|
||||
clapper_player_set_mute (player, !clapper_player_get_mute (player));
|
||||
}
|
||||
|
||||
static void
|
||||
set_mute_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
gboolean mute = g_variant_get_boolean (parameter);
|
||||
|
||||
clapper_player_set_mute (player, mute);
|
||||
}
|
||||
|
||||
static void
|
||||
volume_up_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
gdouble volume = (clapper_player_get_volume (player) + 0.02);
|
||||
|
||||
if (volume > 2.0)
|
||||
volume = 2.0;
|
||||
|
||||
clapper_player_set_volume (player, PERCENTAGE_ROUND (volume));
|
||||
}
|
||||
|
||||
static void
|
||||
volume_down_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
gdouble volume = (clapper_player_get_volume (player) - 0.02);
|
||||
|
||||
if (volume < 0)
|
||||
volume = 0;
|
||||
|
||||
clapper_player_set_volume (player, PERCENTAGE_ROUND (volume));
|
||||
}
|
||||
|
||||
static void
|
||||
set_volume_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
gdouble volume = g_variant_get_double (parameter);
|
||||
|
||||
clapper_player_set_volume (player, volume);
|
||||
}
|
||||
|
||||
static void
|
||||
speed_up_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
gdouble dest, speed = clapper_player_get_speed (player);
|
||||
|
||||
if (speed >= 2.0)
|
||||
return;
|
||||
|
||||
dest = 0.25;
|
||||
while (speed >= dest)
|
||||
dest += 0.25;
|
||||
|
||||
if (dest > 2.0)
|
||||
dest = 2.0;
|
||||
|
||||
clapper_player_set_speed (player, dest);
|
||||
}
|
||||
|
||||
static void
|
||||
speed_down_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
gdouble dest, speed = clapper_player_get_speed (player);
|
||||
|
||||
if (speed <= 0.05)
|
||||
return;
|
||||
|
||||
dest = 2.0;
|
||||
while (speed <= dest)
|
||||
dest -= 0.25;
|
||||
|
||||
if (dest < 0.05)
|
||||
dest = 0.05;
|
||||
|
||||
clapper_player_set_speed (player, dest);
|
||||
}
|
||||
|
||||
static void
|
||||
set_speed_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
gdouble speed = g_variant_get_double (parameter);
|
||||
|
||||
clapper_player_set_speed (player, speed);
|
||||
}
|
||||
|
||||
static void
|
||||
previous_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
|
||||
clapper_queue_select_previous_item (clapper_player_get_queue (player));
|
||||
}
|
||||
|
||||
static void
|
||||
next_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
|
||||
clapper_queue_select_next_item (clapper_player_get_queue (player));
|
||||
}
|
||||
|
||||
static void
|
||||
select_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_av_get_player (self);
|
||||
guint index = g_variant_get_uint32 (parameter);
|
||||
|
||||
clapper_queue_select_index (clapper_player_get_queue (player), index);
|
||||
}
|
||||
|
||||
static void
|
||||
_ensure_css_provider (void)
|
||||
{
|
||||
GdkDisplay *display;
|
||||
|
||||
if (provider_added)
|
||||
return;
|
||||
|
||||
display = gdk_display_get_default ();
|
||||
|
||||
if (G_LIKELY (display != NULL)) {
|
||||
GtkCssProvider *provider = gtk_css_provider_new ();
|
||||
gtk_css_provider_load_from_resource (provider,
|
||||
CLAPPER_GTK_RESOURCE_PREFIX "/css/styles.css");
|
||||
|
||||
gtk_style_context_add_provider_for_display (display,
|
||||
(GtkStyleProvider *) provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
|
||||
g_object_unref (provider);
|
||||
|
||||
provider_added = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
_set_inhibit_session (ClapperGtkAv *self, gboolean inhibit)
|
||||
{
|
||||
ClapperGtkAvPrivate *priv = clapper_gtk_av_get_instance_private (self);
|
||||
GtkRoot *root;
|
||||
GApplication *app;
|
||||
gboolean inhibited = (priv->inhibit_cookie != 0);
|
||||
|
||||
if (inhibited == inhibit)
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Trying to %sinhibit session...", (inhibit) ? "" : "un");
|
||||
|
||||
root = gtk_widget_get_root (GTK_WIDGET (self));
|
||||
|
||||
if (!root && !GTK_IS_WINDOW (root)) {
|
||||
GST_WARNING_OBJECT (self, "Cannot %sinhibit session "
|
||||
"without root window", (inhibit) ? "" : "un");
|
||||
return;
|
||||
}
|
||||
|
||||
/* NOTE: Not using application from window prop,
|
||||
* as it goes away early when unrooting */
|
||||
app = g_application_get_default ();
|
||||
|
||||
if (!app && !GTK_IS_APPLICATION (app)) {
|
||||
GST_WARNING_OBJECT (self, "Cannot %sinhibit session "
|
||||
"without window application set", (inhibit) ? "" : "un");
|
||||
return;
|
||||
}
|
||||
|
||||
if (inhibited) {
|
||||
gtk_application_uninhibit (GTK_APPLICATION (app), priv->inhibit_cookie);
|
||||
priv->inhibit_cookie = 0;
|
||||
}
|
||||
if (inhibit) {
|
||||
priv->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (app),
|
||||
GTK_WINDOW (root), GTK_APPLICATION_INHIBIT_IDLE,
|
||||
"Media is playing");
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Session %sinhibited", (inhibit) ? "" : "un");
|
||||
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_INHIBITED]);
|
||||
}
|
||||
|
||||
static void
|
||||
_player_state_changed_cb (ClapperPlayer *player,
|
||||
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkAv *self)
|
||||
{
|
||||
ClapperGtkAvPrivate *priv = clapper_gtk_av_get_instance_private (self);
|
||||
|
||||
if (priv->auto_inhibit) {
|
||||
ClapperPlayerState state = clapper_player_get_state (player);
|
||||
_set_inhibit_session (self, state == CLAPPER_PLAYER_STATE_PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_av_get_player:
|
||||
* @av: a #ClapperGtkAv
|
||||
*
|
||||
* Get #ClapperPlayer used by this #ClapperGtkAv instance.
|
||||
*
|
||||
* Returns: (transfer none): a #ClapperPlayer used by widget.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
ClapperPlayer *
|
||||
clapper_gtk_av_get_player (ClapperGtkAv *self)
|
||||
{
|
||||
ClapperGtkAvPrivate *priv;
|
||||
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_AV (self), NULL);
|
||||
|
||||
priv = clapper_gtk_av_get_instance_private (self);
|
||||
|
||||
return priv->player;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_av_set_auto_inhibit:
|
||||
* @av: a #ClapperGtkAv
|
||||
* @inhibit: whether to enable automatic session inhibit
|
||||
*
|
||||
* Set whether widget should try to automatically inhibit session
|
||||
* from idling (and possibly screen going black) when media is playing.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
void
|
||||
clapper_gtk_av_set_auto_inhibit (ClapperGtkAv *self, gboolean inhibit)
|
||||
{
|
||||
ClapperGtkAvPrivate *priv;
|
||||
|
||||
g_return_if_fail (CLAPPER_GTK_IS_AV (self));
|
||||
|
||||
priv = clapper_gtk_av_get_instance_private (self);
|
||||
|
||||
if (priv->auto_inhibit != inhibit) {
|
||||
priv->auto_inhibit = inhibit;
|
||||
|
||||
/* Uninhibit if we were auto inhibited earlier */
|
||||
if (!priv->auto_inhibit)
|
||||
_set_inhibit_session (self, FALSE);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_AUTO_INHIBIT]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_av_get_auto_inhibit:
|
||||
* @av: a #ClapperGtkAv
|
||||
*
|
||||
* Get whether automatic session inhibit is enabled.
|
||||
*
|
||||
* Returns: %TRUE if enabled, %FALSE otherwise.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
gboolean
|
||||
clapper_gtk_av_get_auto_inhibit (ClapperGtkAv *self)
|
||||
{
|
||||
ClapperGtkAvPrivate *priv;
|
||||
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_AV (self), FALSE);
|
||||
|
||||
priv = clapper_gtk_av_get_instance_private (self);
|
||||
|
||||
return priv->auto_inhibit;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_av_get_inhibited:
|
||||
* @av: a #ClapperGtkAv
|
||||
*
|
||||
* Get whether session is currently inhibited by
|
||||
* [property@ClapperGtk.Av:auto-inhibit].
|
||||
*
|
||||
* Returns: %TRUE if inhibited, %FALSE otherwise.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
gboolean
|
||||
clapper_gtk_av_get_inhibited (ClapperGtkAv *self)
|
||||
{
|
||||
ClapperGtkAvPrivate *priv;
|
||||
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_AV (self), FALSE);
|
||||
|
||||
priv = clapper_gtk_av_get_instance_private (self);
|
||||
|
||||
return (priv->inhibit_cookie != 0);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_av_root (GtkWidget *widget)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
ClapperGtkAvPrivate *priv = clapper_gtk_av_get_instance_private (self);
|
||||
|
||||
_ensure_css_provider ();
|
||||
|
||||
GTK_WIDGET_CLASS (parent_class)->root (widget);
|
||||
|
||||
if (priv->auto_inhibit) {
|
||||
ClapperPlayerState state = clapper_player_get_state (priv->player);
|
||||
_set_inhibit_session (self, state == CLAPPER_PLAYER_STATE_PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_av_unroot (GtkWidget *widget)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (widget);
|
||||
|
||||
_set_inhibit_session (self, FALSE);
|
||||
|
||||
GTK_WIDGET_CLASS (parent_class)->unroot (widget);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_av_init (ClapperGtkAv *self)
|
||||
{
|
||||
ClapperGtkAvPrivate *priv = clapper_gtk_av_get_instance_private (self);
|
||||
|
||||
priv->auto_inhibit = DEFAULT_AUTO_INHIBIT;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_av_constructed (GObject *object)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (object);
|
||||
ClapperGtkAvPrivate *priv = clapper_gtk_av_get_instance_private (self);
|
||||
GstElement *afilter;
|
||||
|
||||
priv->player = clapper_player_new ();
|
||||
|
||||
g_signal_connect (priv->player, "notify::state",
|
||||
G_CALLBACK (_player_state_changed_cb), self);
|
||||
|
||||
afilter = gst_element_factory_make ("scaletempo", NULL);
|
||||
if (G_LIKELY (afilter != NULL))
|
||||
clapper_player_set_audio_filter (priv->player, afilter);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_av_dispose (GObject *object)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (object);
|
||||
ClapperGtkAvPrivate *priv = clapper_gtk_av_get_instance_private (self);
|
||||
|
||||
/* Something else might still be holding a reference on the player,
|
||||
* thus we should disconnect everything before disposing template */
|
||||
if (priv->player) {
|
||||
g_signal_handlers_disconnect_by_func (priv->player,
|
||||
_player_state_changed_cb, self);
|
||||
}
|
||||
|
||||
gst_clear_object (&priv->player);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_av_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_PLAYER:
|
||||
g_value_set_object (value, clapper_gtk_av_get_player (self));
|
||||
break;
|
||||
case PROP_AUTO_INHIBIT:
|
||||
g_value_set_boolean (value, clapper_gtk_av_get_auto_inhibit (self));
|
||||
break;
|
||||
case PROP_INHIBITED:
|
||||
g_value_set_boolean (value, clapper_gtk_av_get_inhibited (self));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_av_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperGtkAv *self = CLAPPER_GTK_AV_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_AUTO_INHIBIT:
|
||||
clapper_gtk_av_set_auto_inhibit (self, g_value_get_boolean (value));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_av_class_init (ClapperGtkAvClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkav", GST_DEBUG_FG_MAGENTA,
|
||||
"Clapper GTK AV");
|
||||
|
||||
widget_class->root = clapper_gtk_av_root;
|
||||
widget_class->unroot = clapper_gtk_av_unroot;
|
||||
|
||||
gobject_class->constructed = clapper_gtk_av_constructed;
|
||||
gobject_class->get_property = clapper_gtk_av_get_property;
|
||||
gobject_class->set_property = clapper_gtk_av_set_property;
|
||||
gobject_class->dispose = clapper_gtk_av_dispose;
|
||||
|
||||
/**
|
||||
* ClapperGtkAv:player:
|
||||
*
|
||||
* A #ClapperPlayer used by widget.
|
||||
*/
|
||||
param_specs[PROP_PLAYER] = g_param_spec_object ("player",
|
||||
NULL, NULL, CLAPPER_TYPE_PLAYER,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperGtkAv:auto-inhibit:
|
||||
*
|
||||
* Try to automatically inhibit session when media is playing.
|
||||
*/
|
||||
param_specs[PROP_AUTO_INHIBIT] = g_param_spec_boolean ("auto-inhibit",
|
||||
NULL, NULL, DEFAULT_AUTO_INHIBIT,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperGtkAv:inhibited:
|
||||
*
|
||||
* Get whether session is currently inhibited by playback.
|
||||
*/
|
||||
param_specs[PROP_INHIBITED] = g_param_spec_boolean ("inhibited",
|
||||
NULL, NULL, FALSE,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
||||
|
||||
gtk_widget_class_install_action (widget_class, "av.toggle-play", NULL, toggle_play_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.play", NULL, play_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.pause", NULL, pause_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.stop", NULL, stop_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.seek", "d", seek_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.seek-custom", "(di)", seek_custom_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.toggle-mute", NULL, toggle_mute_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.set-mute", "b", set_mute_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.volume-up", NULL, volume_up_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.volume-down", NULL, volume_down_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.set-volume", "d", set_volume_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.speed-up", NULL, speed_up_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.speed-down", NULL, speed_down_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.set-speed", "d", set_speed_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.previous-item", NULL, previous_item_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.next-item", NULL, next_item_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "av.select-item", "u", select_item_action_cb);
|
||||
|
||||
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
|
||||
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC);
|
||||
gtk_widget_class_set_css_name (widget_class, "clapper-gtk-av");
|
||||
}
|
60
src/lib/clapper-gtk/clapper-gtk-av.h
Normal file
60
src/lib/clapper-gtk/clapper-gtk-av.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/* Clapper GTK Integration Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION)
|
||||
#error "Only <clapper-gtk/clapper-gtk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <clapper/clapper.h>
|
||||
|
||||
#include <clapper-gtk/clapper-gtk-visibility.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_GTK_TYPE_AV (clapper_gtk_av_get_type())
|
||||
#define CLAPPER_GTK_AV_CAST(obj) ((ClapperGtkAv *)(obj))
|
||||
|
||||
CLAPPER_GTK_API
|
||||
G_DECLARE_DERIVABLE_TYPE (ClapperGtkAv, clapper_gtk_av, CLAPPER_GTK, AV, GtkWidget)
|
||||
|
||||
struct _ClapperGtkAvClass
|
||||
{
|
||||
GtkWidgetClass parent_class;
|
||||
|
||||
/*< private >*/
|
||||
gpointer padding[4];
|
||||
};
|
||||
|
||||
CLAPPER_GTK_API
|
||||
ClapperPlayer * clapper_gtk_av_get_player (ClapperGtkAv *av);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
void clapper_gtk_av_set_auto_inhibit (ClapperGtkAv *av, gboolean inhibit);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
gboolean clapper_gtk_av_get_auto_inhibit (ClapperGtkAv *av);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
gboolean clapper_gtk_av_get_inhibited (ClapperGtkAv *av);
|
||||
|
||||
G_END_DECLS
|
@@ -82,7 +82,7 @@ clapper_gtk_next_item_button_init (ClapperGtkNextItemButton *self)
|
||||
{
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
|
||||
gtk_button_set_icon_name (GTK_BUTTON (self), "media-skip-forward-symbolic");
|
||||
gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.next-item");
|
||||
gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "av.next-item");
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -82,7 +82,7 @@ clapper_gtk_previous_item_button_init (ClapperGtkPreviousItemButton *self)
|
||||
{
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
|
||||
gtk_button_set_icon_name (GTK_BUTTON (self), "media-skip-backward-symbolic");
|
||||
gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.previous-item");
|
||||
gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "av.previous-item");
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -83,7 +83,7 @@ static void
|
||||
clapper_gtk_toggle_play_button_init (ClapperGtkTogglePlayButton *self)
|
||||
{
|
||||
gtk_button_set_icon_name (GTK_BUTTON (self), PLAY_ICON_NAME);
|
||||
gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.toggle-play");
|
||||
gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "av.toggle-play");
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -21,7 +21,7 @@
|
||||
#include <glib/gi18n-lib.h>
|
||||
|
||||
#include "clapper-gtk-utils-private.h"
|
||||
#include "clapper-gtk-video.h"
|
||||
#include "clapper-gtk-av.h"
|
||||
|
||||
static gboolean initialized = FALSE;
|
||||
|
||||
@@ -29,18 +29,18 @@ static gboolean initialized = FALSE;
|
||||
* clapper_gtk_get_player_from_ancestor:
|
||||
* @widget: a #GtkWidget
|
||||
*
|
||||
* Get [class@Clapper.Player] used by [class@ClapperGtk.Video] ancestor of @widget.
|
||||
* Get [class@Clapper.Player] used by [class@ClapperGtk.Av] ancestor of @widget.
|
||||
*
|
||||
* This utility is a convenience wrapper for calling [method@Gtk.Widget.get_ancestor]
|
||||
* of type `CLAPPER_GTK_TYPE_VIDEO` and [method@ClapperGtk.Video.get_player] with
|
||||
* of type `CLAPPER_GTK_TYPE_AV` and [method@ClapperGtk.Av.get_player] with
|
||||
* additional %NULL checking and type casting.
|
||||
*
|
||||
* This is meant to be used mainly for custom widget development as an easy access to the
|
||||
* underlying parent [class@Clapper.Player] object. If you want to get the player from
|
||||
* [class@ClapperGtk.Video] widget itself, use [method@ClapperGtk.Video.get_player] instead.
|
||||
* [class@ClapperGtk.Av] widget itself, use [method@ClapperGtk.Av.get_player] instead.
|
||||
*
|
||||
* Rememeber that this function will return %NULL when widget does not have
|
||||
* a [class@ClapperGtk.Video] ancestor in widget hierarchy (widget is not yet placed).
|
||||
* a [class@ClapperGtk.Av] ancestor in widget hierarchy (widget is not yet placed).
|
||||
*
|
||||
* Returns: (transfer none) (nullable): a #ClapperPlayer from ancestor of a @widget.
|
||||
*/
|
||||
@@ -52,8 +52,8 @@ clapper_gtk_get_player_from_ancestor (GtkWidget *widget)
|
||||
|
||||
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
|
||||
|
||||
if ((parent = gtk_widget_get_ancestor (widget, CLAPPER_GTK_TYPE_VIDEO)))
|
||||
player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (parent));
|
||||
if ((parent = gtk_widget_get_ancestor (widget, CLAPPER_GTK_TYPE_AV)))
|
||||
player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (parent));
|
||||
|
||||
return player;
|
||||
}
|
||||
|
95
src/lib/clapper-gtk/clapper-gtk-version.c
Normal file
95
src/lib/clapper-gtk/clapper-gtk-version.c
Normal file
@@ -0,0 +1,95 @@
|
||||
/* Clapper GTK Integration Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "clapper-gtk-version.h"
|
||||
|
||||
/**
|
||||
* clapper_gtk_get_major_version:
|
||||
*
|
||||
* ClapperGtk runtime major version component
|
||||
*
|
||||
* This returns the ClapperGtk library version your code is
|
||||
* running against unlike [const@ClapperGtk.MAJOR_VERSION]
|
||||
* which represents compile time version.
|
||||
*
|
||||
* Returns: the major version number of the ClapperGtk library
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
guint
|
||||
clapper_gtk_get_major_version (void)
|
||||
{
|
||||
return CLAPPER_GTK_MAJOR_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_get_minor_version:
|
||||
*
|
||||
* ClapperGtk runtime minor version component
|
||||
*
|
||||
* This returns the ClapperGtk library version your code is
|
||||
* running against unlike [const@ClapperGtk.MINOR_VERSION]
|
||||
* which represents compile time version.
|
||||
*
|
||||
* Returns: the minor version number of the ClapperGtk library
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
guint
|
||||
clapper_gtk_get_minor_version (void)
|
||||
{
|
||||
return CLAPPER_GTK_MINOR_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_get_micro_version:
|
||||
*
|
||||
* ClapperGtk runtime micro version component
|
||||
*
|
||||
* This returns the ClapperGtk library version your code is
|
||||
* running against unlike [const@ClapperGtk.MICRO_VERSION]
|
||||
* which represents compile time version.
|
||||
*
|
||||
* Returns: the micro version number of the ClapperGtk library
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
guint
|
||||
clapper_gtk_get_micro_version (void)
|
||||
{
|
||||
return CLAPPER_GTK_MICRO_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_get_version_s:
|
||||
*
|
||||
* ClapperGtk runtime version as string
|
||||
*
|
||||
* This returns the ClapperGtk library version your code is
|
||||
* running against unlike [const@ClapperGtk.VERSION_S]
|
||||
* which represents compile time version.
|
||||
*
|
||||
* Returns: the version of the ClapperGtk library as string
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
const gchar *
|
||||
clapper_gtk_get_version_s (void)
|
||||
{
|
||||
return CLAPPER_GTK_VERSION_S;
|
||||
}
|
@@ -22,6 +22,8 @@
|
||||
#error "Only <clapper-gtk/clapper-gtk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/**
|
||||
* CLAPPER_GTK_MAJOR_VERSION:
|
||||
*
|
||||
@@ -73,3 +75,15 @@
|
||||
(CLAPPER_GTK_MAJOR_VERSION == (major) && CLAPPER_GTK_MINOR_VERSION > (minor)) || \
|
||||
(CLAPPER_GTK_MAJOR_VERSION == (major) && CLAPPER_GTK_MINOR_VERSION == (minor) && \
|
||||
CLAPPER_GTK_MICRO_VERSION >= (micro)))
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
guint clapper_gtk_get_major_version (void);
|
||||
|
||||
guint clapper_gtk_get_minor_version (void);
|
||||
|
||||
guint clapper_gtk_get_micro_version (void);
|
||||
|
||||
const gchar * clapper_gtk_get_version_s (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -22,8 +22,8 @@
|
||||
* A ready to be used GTK video widget implementing Clapper API.
|
||||
*
|
||||
* #ClapperGtkVideo is the main widget exposed by `ClapperGtk` API. It both displays
|
||||
* videos played by [class@Clapper.Player] (exposed as its property) and manages
|
||||
* revealing and fading of any additional widgets overlaid on top of it.
|
||||
* videos played by [class@Clapper.Player] (exposed as [property@ClapperGtk.Av:player] property)
|
||||
* and manages revealing and fading of any additional widgets overlaid on top of it.
|
||||
*
|
||||
* Other widgets provided by `ClapperGtk` library, once placed anywhere on video
|
||||
* (including nesting within another widget like [class@Gtk.Box]) will automatically
|
||||
@@ -34,7 +34,7 @@
|
||||
* # Basic usage
|
||||
*
|
||||
* A typical use case is to embed video widget as part of your app where video playback
|
||||
* is needed. Get the [class@Clapper.Player] belonging to the video widget and start adding
|
||||
* is needed. Get the [class@Clapper.Player] belonging to the AV widget and start adding
|
||||
* new [class@Clapper.MediaItem] items to the [class@Clapper.Queue] for playback.
|
||||
* For more information please refer to the Clapper playback library documentation.
|
||||
*
|
||||
@@ -46,27 +46,8 @@
|
||||
*
|
||||
* # Actions
|
||||
*
|
||||
* #ClapperGtkVideo defines a set of built-in actions:
|
||||
*
|
||||
* ```yaml
|
||||
* - "video.toggle-play": toggle play/pause
|
||||
* - "video.play": start/resume playback
|
||||
* - "video.pause": pause playback
|
||||
* - "video.stop": stop playback
|
||||
* - "video.seek": seek to position (variant "d")
|
||||
* - "video.seek-custom": seek to position using seek method (variant "(di)")
|
||||
* - "video.toggle-mute": toggle mute state
|
||||
* - "video.set-mute": set mute state (variant "b")
|
||||
* - "video.volume-up": increase volume by 2%
|
||||
* - "video.volume-down": decrease volume by 2%
|
||||
* - "video.set-volume": set volume to specified value (variant "d")
|
||||
* - "video.speed-up": increase speed (from 0.05x - 2x range to nearest quarter)
|
||||
* - "video.speed-down": decrease speed (from 0.05x - 2x range to nearest quarter)
|
||||
* - "video.set-speed": set speed to specified value (variant "d")
|
||||
* - "video.previous-item": select previous item in queue
|
||||
* - "video.next-item": select next item in queue
|
||||
* - "video.select-item": select item at specified index in queue (variant "u")
|
||||
* ```
|
||||
* You can use built-in actions of parent [class@ClapperGtk.Av].
|
||||
* See its documentation, for the list of available ones.
|
||||
*
|
||||
* # ClapperGtkVideo as GtkBuildable
|
||||
*
|
||||
@@ -93,8 +74,6 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "clapper-gtk-enums.h"
|
||||
#include "clapper-gtk-video.h"
|
||||
#include "clapper-gtk-lead-container.h"
|
||||
@@ -102,11 +81,8 @@
|
||||
#include "clapper-gtk-buffering-animation-private.h"
|
||||
#include "clapper-gtk-video-placeholder-private.h"
|
||||
|
||||
#define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01)
|
||||
|
||||
#define DEFAULT_FADE_DELAY 3000
|
||||
#define DEFAULT_TOUCH_FADE_DELAY 5000
|
||||
#define DEFAULT_AUTO_INHIBIT FALSE
|
||||
|
||||
#define MIN_MOTION_DELAY 100000
|
||||
|
||||
@@ -115,7 +91,7 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _ClapperGtkVideo
|
||||
{
|
||||
GtkWidget parent;
|
||||
ClapperGtkAv parent;
|
||||
|
||||
GtkWidget *overlay;
|
||||
GtkWidget *status;
|
||||
@@ -125,10 +101,8 @@ struct _ClapperGtkVideo
|
||||
GtkGesture *click_gesture;
|
||||
|
||||
/* Props */
|
||||
ClapperPlayer *player;
|
||||
guint fade_delay;
|
||||
guint touch_fade_delay;
|
||||
gboolean auto_inhibit;
|
||||
|
||||
GPtrArray *overlays;
|
||||
GPtrArray *fading_overlays;
|
||||
@@ -140,8 +114,6 @@ struct _ClapperGtkVideo
|
||||
guint fade_timeout;
|
||||
gboolean reveal, revealed;
|
||||
|
||||
guint inhibit_cookie;
|
||||
|
||||
/* Current pointer coords and type */
|
||||
gdouble x, y;
|
||||
gboolean is_touch;
|
||||
@@ -174,17 +146,14 @@ _buildable_iface_init (GtkBuildableIface *iface)
|
||||
}
|
||||
|
||||
#define parent_class clapper_gtk_video_parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (ClapperGtkVideo, clapper_gtk_video, GTK_TYPE_WIDGET,
|
||||
G_DEFINE_TYPE_WITH_CODE (ClapperGtkVideo, clapper_gtk_video, CLAPPER_GTK_TYPE_AV,
|
||||
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, _buildable_iface_init))
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_PLAYER,
|
||||
PROP_FADE_DELAY,
|
||||
PROP_TOUCH_FADE_DELAY,
|
||||
PROP_AUTO_INHIBIT,
|
||||
PROP_INHIBITED,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
@@ -195,209 +164,111 @@ enum
|
||||
SIGNAL_LAST
|
||||
};
|
||||
|
||||
static gboolean provider_added = FALSE;
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
static guint signals[SIGNAL_LAST] = { 0, };
|
||||
|
||||
/* FIXME: 1.0: Remove these compat actions, since they were moved to base class */
|
||||
|
||||
static void
|
||||
toggle_play_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
|
||||
switch (clapper_player_get_state (player)) {
|
||||
case CLAPPER_PLAYER_STATE_PLAYING:
|
||||
clapper_player_pause (player);
|
||||
break;
|
||||
case CLAPPER_PLAYER_STATE_STOPPED:
|
||||
case CLAPPER_PLAYER_STATE_PAUSED:
|
||||
clapper_player_play (player);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
gtk_widget_activate_action_variant (widget, "av.toggle-play", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
play_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
|
||||
clapper_player_play (player);
|
||||
gtk_widget_activate_action_variant (widget, "av.play", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
pause_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
|
||||
clapper_player_pause (player);
|
||||
gtk_widget_activate_action_variant (widget, "av.pause", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
stop_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
|
||||
clapper_player_stop (player);
|
||||
gtk_widget_activate_action_variant (widget, "av.stop", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
seek_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
gdouble position = g_variant_get_double (parameter);
|
||||
|
||||
clapper_player_seek (player, position);
|
||||
gtk_widget_activate_action_variant (widget, "av.seek", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
seek_custom_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
ClapperPlayerSeekMethod method = CLAPPER_PLAYER_SEEK_METHOD_NORMAL;
|
||||
gdouble position = 0;
|
||||
|
||||
g_variant_get (parameter, "(di)", &position, &method);
|
||||
clapper_player_seek_custom (player, position, method);
|
||||
gtk_widget_activate_action_variant (widget, "av.seek-custom", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
toggle_mute_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
|
||||
clapper_player_set_mute (player, !clapper_player_get_mute (player));
|
||||
gtk_widget_activate_action_variant (widget, "av.toggle-mute", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
set_mute_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
gboolean mute = g_variant_get_boolean (parameter);
|
||||
|
||||
clapper_player_set_mute (player, mute);
|
||||
gtk_widget_activate_action_variant (widget, "av.set-mute", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
volume_up_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
gdouble volume = (clapper_player_get_volume (player) + 0.02);
|
||||
|
||||
if (volume > 2.0)
|
||||
volume = 2.0;
|
||||
|
||||
clapper_player_set_volume (player, PERCENTAGE_ROUND (volume));
|
||||
gtk_widget_activate_action_variant (widget, "av.volume-up", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
volume_down_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
gdouble volume = (clapper_player_get_volume (player) - 0.02);
|
||||
|
||||
if (volume < 0)
|
||||
volume = 0;
|
||||
|
||||
clapper_player_set_volume (player, PERCENTAGE_ROUND (volume));
|
||||
gtk_widget_activate_action_variant (widget, "av.volume-down", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
set_volume_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
gdouble volume = g_variant_get_double (parameter);
|
||||
|
||||
clapper_player_set_volume (player, volume);
|
||||
gtk_widget_activate_action_variant (widget, "av.set-volume", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
speed_up_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
gdouble dest, speed = clapper_player_get_speed (player);
|
||||
|
||||
if (speed >= 2.0)
|
||||
return;
|
||||
|
||||
dest = 0.25;
|
||||
while (speed >= dest)
|
||||
dest += 0.25;
|
||||
|
||||
if (dest > 2.0)
|
||||
dest = 2.0;
|
||||
|
||||
clapper_player_set_speed (player, dest);
|
||||
gtk_widget_activate_action_variant (widget, "av.speed-up", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
speed_down_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
gdouble dest, speed = clapper_player_get_speed (player);
|
||||
|
||||
if (speed <= 0.05)
|
||||
return;
|
||||
|
||||
dest = 2.0;
|
||||
while (speed <= dest)
|
||||
dest -= 0.25;
|
||||
|
||||
if (dest < 0.05)
|
||||
dest = 0.05;
|
||||
|
||||
clapper_player_set_speed (player, dest);
|
||||
gtk_widget_activate_action_variant (widget, "av.speed-down", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
set_speed_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
gdouble speed = g_variant_get_double (parameter);
|
||||
|
||||
clapper_player_set_speed (player, speed);
|
||||
gtk_widget_activate_action_variant (widget, "av.set-speed", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
previous_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
|
||||
clapper_queue_select_previous_item (clapper_player_get_queue (player));
|
||||
gtk_widget_activate_action_variant (widget, "av.previous-item", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
next_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
|
||||
clapper_queue_select_next_item (clapper_player_get_queue (player));
|
||||
gtk_widget_activate_action_variant (widget, "av.next-item", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
select_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
ClapperPlayer *player = clapper_gtk_video_get_player (self);
|
||||
guint index = g_variant_get_uint32 (parameter);
|
||||
|
||||
clapper_queue_select_index (clapper_player_get_queue (player), index);
|
||||
gtk_widget_activate_action_variant (widget, "av.select-item", parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -871,73 +742,6 @@ touch_released_cb (GtkGestureClick *click, gint n_press,
|
||||
_reset_fade_timeout (self);
|
||||
}
|
||||
|
||||
static void
|
||||
_ensure_css_provider (void)
|
||||
{
|
||||
GdkDisplay *display;
|
||||
|
||||
if (provider_added)
|
||||
return;
|
||||
|
||||
display = gdk_display_get_default ();
|
||||
|
||||
if (G_LIKELY (display != NULL)) {
|
||||
GtkCssProvider *provider = gtk_css_provider_new ();
|
||||
gtk_css_provider_load_from_resource (provider,
|
||||
CLAPPER_GTK_RESOURCE_PREFIX "/css/styles.css");
|
||||
|
||||
gtk_style_context_add_provider_for_display (display,
|
||||
(GtkStyleProvider *) provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
|
||||
g_object_unref (provider);
|
||||
|
||||
provider_added = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
_set_inhibit_session (ClapperGtkVideo *self, gboolean inhibit)
|
||||
{
|
||||
GtkRoot *root;
|
||||
GApplication *app;
|
||||
gboolean inhibited = (self->inhibit_cookie != 0);
|
||||
|
||||
if (inhibited == inhibit)
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Trying to %sinhibit session...", (inhibit) ? "" : "un");
|
||||
|
||||
root = gtk_widget_get_root (GTK_WIDGET (self));
|
||||
|
||||
if (!root && !GTK_IS_WINDOW (root)) {
|
||||
GST_WARNING_OBJECT (self, "Cannot %sinhibit session "
|
||||
"without root window", (inhibit) ? "" : "un");
|
||||
return;
|
||||
}
|
||||
|
||||
/* NOTE: Not using application from window prop,
|
||||
* as it goes away early when unrooting */
|
||||
app = g_application_get_default ();
|
||||
|
||||
if (!app && !GTK_IS_APPLICATION (app)) {
|
||||
GST_WARNING_OBJECT (self, "Cannot %sinhibit session "
|
||||
"without window application set", (inhibit) ? "" : "un");
|
||||
return;
|
||||
}
|
||||
|
||||
if (inhibited) {
|
||||
gtk_application_uninhibit (GTK_APPLICATION (app), self->inhibit_cookie);
|
||||
self->inhibit_cookie = 0;
|
||||
}
|
||||
if (inhibit) {
|
||||
self->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (app),
|
||||
GTK_WINDOW (root), GTK_APPLICATION_INHIBIT_IDLE,
|
||||
"Video is playing");
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Session %sinhibited", (inhibit) ? "" : "un");
|
||||
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_INHIBITED]);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_set_buffering_animation_enabled (ClapperGtkVideo *self, gboolean enabled)
|
||||
{
|
||||
@@ -963,9 +767,6 @@ _player_state_changed_cb (ClapperPlayer *player,
|
||||
{
|
||||
ClapperPlayerState state = clapper_player_get_state (player);
|
||||
|
||||
if (self->auto_inhibit)
|
||||
_set_inhibit_session (self, state == CLAPPER_PLAYER_STATE_PLAYING);
|
||||
|
||||
_set_buffering_animation_enabled (self, state == CLAPPER_PLAYER_STATE_BUFFERING);
|
||||
}
|
||||
|
||||
@@ -1106,7 +907,7 @@ _fading_overlay_revealed_cb (GtkRevealer *revealer,
|
||||
*
|
||||
* Creates a new #ClapperGtkVideo instance.
|
||||
*
|
||||
* Newly created video widget will also set some default GStreamer elements
|
||||
* Newly created video widget will also have set some default GStreamer elements
|
||||
* on its [class@Clapper.Player]. This includes Clapper own video sink and
|
||||
* a "scaletempo" element as audio filter. Both can still be changed after
|
||||
* construction by setting corresponding player properties.
|
||||
@@ -1187,19 +988,21 @@ clapper_gtk_video_add_fading_overlay (ClapperGtkVideo *self, GtkWidget *widget)
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_video_get_player:
|
||||
* clapper_gtk_video_get_player: (skip)
|
||||
* @video: a #ClapperGtkVideo
|
||||
*
|
||||
* Get #ClapperPlayer used by this #ClapperGtkVideo instance.
|
||||
*
|
||||
* Returns: (transfer none): a #ClapperPlayer used by video.
|
||||
*
|
||||
* Deprecated: 0.10: Use [method@ClapperGtk.Av.get_player] instead.
|
||||
*/
|
||||
ClapperPlayer *
|
||||
clapper_gtk_video_get_player (ClapperGtkVideo *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), NULL);
|
||||
|
||||
return self->player;
|
||||
return clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1275,60 +1078,58 @@ clapper_gtk_video_get_touch_fade_delay (ClapperGtkVideo *self)
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_video_set_auto_inhibit:
|
||||
* clapper_gtk_video_set_auto_inhibit: (skip)
|
||||
* @video: a #ClapperGtkVideo
|
||||
* @inhibit: whether to enable automatic session inhibit
|
||||
*
|
||||
* Set whether video should try to automatically inhibit session
|
||||
* from idling (and possibly screen going black) when video is playing.
|
||||
*
|
||||
* Deprecated: 0.10: Use [method@ClapperGtk.Av.set_auto_inhibit] instead.
|
||||
*/
|
||||
void
|
||||
clapper_gtk_video_set_auto_inhibit (ClapperGtkVideo *self, gboolean inhibit)
|
||||
{
|
||||
g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self));
|
||||
|
||||
if (self->auto_inhibit != inhibit) {
|
||||
self->auto_inhibit = inhibit;
|
||||
|
||||
/* Uninhibit if we were auto inhibited earlier */
|
||||
if (!self->auto_inhibit)
|
||||
_set_inhibit_session (self, FALSE);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_AUTO_INHIBIT]);
|
||||
}
|
||||
clapper_gtk_av_set_auto_inhibit (CLAPPER_GTK_AV_CAST (self), inhibit);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_video_get_auto_inhibit:
|
||||
* clapper_gtk_video_get_auto_inhibit: (skip)
|
||||
* @video: a #ClapperGtkVideo
|
||||
*
|
||||
* Get whether automatic session inhibit is enabled.
|
||||
*
|
||||
* Returns: %TRUE if enabled, %FALSE otherwise.
|
||||
*
|
||||
* Deprecated: 0.10: Use [method@ClapperGtk.Av.get_auto_inhibit] instead.
|
||||
*/
|
||||
gboolean
|
||||
clapper_gtk_video_get_auto_inhibit (ClapperGtkVideo *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), FALSE);
|
||||
|
||||
return self->auto_inhibit;
|
||||
return clapper_gtk_av_get_auto_inhibit (CLAPPER_GTK_AV_CAST (self));
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_video_get_inhibited:
|
||||
* clapper_gtk_video_get_inhibited: (skip)
|
||||
* @video: a #ClapperGtkVideo
|
||||
*
|
||||
* Get whether session is currently inhibited by
|
||||
* [property@ClapperGtk.Video:auto-inhibit].
|
||||
* [property@ClapperGtk.Av:auto-inhibit].
|
||||
*
|
||||
* Returns: %TRUE if inhibited, %FALSE otherwise.
|
||||
*
|
||||
* Deprecated: 0.10: Use [method@ClapperGtk.Av.get_inhibited] instead.
|
||||
*/
|
||||
gboolean
|
||||
clapper_gtk_video_get_inhibited (ClapperGtkVideo *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), FALSE);
|
||||
|
||||
return (self->inhibit_cookie != 0);
|
||||
return clapper_gtk_av_get_inhibited (CLAPPER_GTK_AV_CAST (self));
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1337,8 +1138,6 @@ clapper_gtk_video_root (GtkWidget *widget)
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget);
|
||||
GtkRoot *root;
|
||||
|
||||
_ensure_css_provider ();
|
||||
|
||||
GTK_WIDGET_CLASS (parent_class)->root (widget);
|
||||
|
||||
root = gtk_widget_get_root (widget);
|
||||
@@ -1350,11 +1149,6 @@ clapper_gtk_video_root (GtkWidget *widget)
|
||||
G_CALLBACK (_window_is_active_cb), self);
|
||||
_window_is_active_cb (window, NULL, self);
|
||||
}
|
||||
|
||||
if (self->auto_inhibit) {
|
||||
ClapperPlayerState state = clapper_player_get_state (self->player);
|
||||
_set_inhibit_session (self, state == CLAPPER_PLAYER_STATE_PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1368,8 +1162,6 @@ clapper_gtk_video_unroot (GtkWidget *widget)
|
||||
_window_is_active_cb, self);
|
||||
}
|
||||
|
||||
_set_inhibit_session (self, FALSE);
|
||||
|
||||
GTK_WIDGET_CLASS (parent_class)->unroot (widget);
|
||||
}
|
||||
|
||||
@@ -1385,7 +1177,6 @@ clapper_gtk_video_init (ClapperGtkVideo *self)
|
||||
|
||||
self->fade_delay = DEFAULT_FADE_DELAY;
|
||||
self->touch_fade_delay = DEFAULT_TOUCH_FADE_DELAY;
|
||||
self->auto_inhibit = DEFAULT_AUTO_INHIBIT;
|
||||
|
||||
/* Ensure private types */
|
||||
g_type_ensure (CLAPPER_GTK_TYPE_STATUS);
|
||||
@@ -1400,15 +1191,18 @@ static void
|
||||
clapper_gtk_video_constructed (GObject *object)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object);
|
||||
GstElement *afilter, *vsink;
|
||||
GstElement *vsink;
|
||||
ClapperPlayer *player;
|
||||
ClapperQueue *queue;
|
||||
|
||||
self->player = clapper_player_new ();
|
||||
queue = clapper_player_get_queue (self->player);
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
|
||||
g_signal_connect (self->player, "notify::state",
|
||||
player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self));
|
||||
queue = clapper_player_get_queue (player);
|
||||
|
||||
g_signal_connect (player, "notify::state",
|
||||
G_CALLBACK (_player_state_changed_cb), self);
|
||||
g_signal_connect (self->player, "notify::video-sink",
|
||||
g_signal_connect (player, "notify::video-sink",
|
||||
G_CALLBACK (_video_sink_changed_cb), self);
|
||||
|
||||
vsink = gst_element_factory_make ("clappersink", NULL);
|
||||
@@ -1428,28 +1222,23 @@ clapper_gtk_video_constructed (GObject *object)
|
||||
}
|
||||
}
|
||||
|
||||
clapper_player_set_video_sink (self->player, vsink);
|
||||
clapper_player_set_video_sink (player, vsink);
|
||||
}
|
||||
|
||||
afilter = gst_element_factory_make ("scaletempo", NULL);
|
||||
if (G_LIKELY (afilter != NULL))
|
||||
clapper_player_set_audio_filter (self->player, afilter);
|
||||
|
||||
g_signal_connect (self->player, "error",
|
||||
g_signal_connect (player, "error",
|
||||
G_CALLBACK (_player_error_cb), self);
|
||||
g_signal_connect (self->player, "missing-plugin",
|
||||
g_signal_connect (player, "missing-plugin",
|
||||
G_CALLBACK (_player_missing_plugin_cb), self);
|
||||
|
||||
g_signal_connect (queue, "notify::current-item",
|
||||
G_CALLBACK (_queue_current_item_changed_cb), self);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_video_dispose (GObject *object)
|
||||
{
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object);
|
||||
ClapperPlayer *player;
|
||||
|
||||
if (self->notify_revealed_id != 0) {
|
||||
GtkRevealer *revealer = GTK_REVEALER (g_ptr_array_index (self->fading_overlays, 0));
|
||||
@@ -1460,18 +1249,20 @@ clapper_gtk_video_dispose (GObject *object)
|
||||
|
||||
g_clear_handle_id (&self->fade_timeout, g_source_remove);
|
||||
|
||||
player = clapper_gtk_av_get_player (CLAPPER_GTK_AV_CAST (self));
|
||||
|
||||
/* Something else might still be holding a reference on the player,
|
||||
* thus we should disconnect everything before disposing template */
|
||||
if (self->player) {
|
||||
ClapperQueue *queue = clapper_player_get_queue (self->player);
|
||||
if (player) { // NULL if dispose run multiple times
|
||||
ClapperQueue *queue = clapper_player_get_queue (player);
|
||||
|
||||
g_signal_handlers_disconnect_by_func (self->player,
|
||||
g_signal_handlers_disconnect_by_func (player,
|
||||
_player_state_changed_cb, self);
|
||||
g_signal_handlers_disconnect_by_func (self->player,
|
||||
g_signal_handlers_disconnect_by_func (player,
|
||||
_video_sink_changed_cb, self);
|
||||
g_signal_handlers_disconnect_by_func (self->player,
|
||||
g_signal_handlers_disconnect_by_func (player,
|
||||
_player_error_cb, self);
|
||||
g_signal_handlers_disconnect_by_func (self->player,
|
||||
g_signal_handlers_disconnect_by_func (player,
|
||||
_player_missing_plugin_cb, self);
|
||||
|
||||
g_signal_handlers_disconnect_by_func (queue,
|
||||
@@ -1481,7 +1272,6 @@ clapper_gtk_video_dispose (GObject *object)
|
||||
gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_VIDEO);
|
||||
|
||||
g_clear_pointer (&self->overlay, gtk_widget_unparent);
|
||||
gst_clear_object (&self->player);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
@@ -1504,21 +1294,12 @@ clapper_gtk_video_get_property (GObject *object, guint prop_id,
|
||||
ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_PLAYER:
|
||||
g_value_set_object (value, clapper_gtk_video_get_player (self));
|
||||
break;
|
||||
case PROP_FADE_DELAY:
|
||||
g_value_set_uint (value, clapper_gtk_video_get_fade_delay (self));
|
||||
break;
|
||||
case PROP_TOUCH_FADE_DELAY:
|
||||
g_value_set_uint (value, clapper_gtk_video_get_touch_fade_delay (self));
|
||||
break;
|
||||
case PROP_AUTO_INHIBIT:
|
||||
g_value_set_boolean (value, clapper_gtk_video_get_auto_inhibit (self));
|
||||
break;
|
||||
case PROP_INHIBITED:
|
||||
g_value_set_boolean (value, clapper_gtk_video_get_inhibited (self));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@@ -1538,9 +1319,6 @@ clapper_gtk_video_set_property (GObject *object, guint prop_id,
|
||||
case PROP_TOUCH_FADE_DELAY:
|
||||
clapper_gtk_video_set_touch_fade_delay (self, g_value_get_uint (value));
|
||||
break;
|
||||
case PROP_AUTO_INHIBIT:
|
||||
clapper_gtk_video_set_auto_inhibit (self, g_value_get_boolean (value));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@@ -1565,15 +1343,6 @@ clapper_gtk_video_class_init (ClapperGtkVideoClass *klass)
|
||||
gobject_class->dispose = clapper_gtk_video_dispose;
|
||||
gobject_class->finalize = clapper_gtk_video_finalize;
|
||||
|
||||
/**
|
||||
* ClapperGtkVideo:player:
|
||||
*
|
||||
* A #ClapperPlayer used by video.
|
||||
*/
|
||||
param_specs[PROP_PLAYER] = g_param_spec_object ("player",
|
||||
NULL, NULL, CLAPPER_TYPE_PLAYER,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperGtkVideo:fade-delay:
|
||||
*
|
||||
@@ -1593,24 +1362,6 @@ clapper_gtk_video_class_init (ClapperGtkVideoClass *klass)
|
||||
NULL, NULL, 1, G_MAXUINT, DEFAULT_TOUCH_FADE_DELAY,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperGtkVideo:auto-inhibit:
|
||||
*
|
||||
* Try to automatically inhibit session when video is playing.
|
||||
*/
|
||||
param_specs[PROP_AUTO_INHIBIT] = g_param_spec_boolean ("auto-inhibit",
|
||||
NULL, NULL, DEFAULT_AUTO_INHIBIT,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperGtkVideo:inhibited:
|
||||
*
|
||||
* Get whether session is currently inhibited by the video.
|
||||
*/
|
||||
param_specs[PROP_INHIBITED] = g_param_spec_boolean ("inhibited",
|
||||
NULL, NULL, FALSE,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperGtkVideo::toggle-fullscreen:
|
||||
* @video: a #ClapperGtkVideo
|
||||
@@ -1642,6 +1393,8 @@ clapper_gtk_video_class_init (ClapperGtkVideoClass *klass)
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
||||
|
||||
/* FIXME: 1.0: Remove these actions, since they were moved to
|
||||
* base class AV widget, but are here for compat reasons. */
|
||||
gtk_widget_class_install_action (widget_class, "video.toggle-play", NULL, toggle_play_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "video.play", NULL, play_action_cb);
|
||||
gtk_widget_class_install_action (widget_class, "video.pause", NULL, pause_action_cb);
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include <clapper/clapper.h>
|
||||
|
||||
#include <clapper-gtk/clapper-gtk-av.h>
|
||||
#include <clapper-gtk/clapper-gtk-visibility.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
@@ -35,7 +36,7 @@ G_BEGIN_DECLS
|
||||
#define CLAPPER_GTK_VIDEO_CAST(obj) ((ClapperGtkVideo *)(obj))
|
||||
|
||||
CLAPPER_GTK_API
|
||||
G_DECLARE_FINAL_TYPE (ClapperGtkVideo, clapper_gtk_video, CLAPPER_GTK, VIDEO, GtkWidget)
|
||||
G_DECLARE_FINAL_TYPE (ClapperGtkVideo, clapper_gtk_video, CLAPPER_GTK, VIDEO, ClapperGtkAv)
|
||||
|
||||
CLAPPER_GTK_API
|
||||
GtkWidget * clapper_gtk_video_new (void);
|
||||
@@ -46,7 +47,7 @@ void clapper_gtk_video_add_overlay (ClapperGtkVideo *video, GtkWidget *widget);
|
||||
CLAPPER_GTK_API
|
||||
void clapper_gtk_video_add_fading_overlay (ClapperGtkVideo *video, GtkWidget *widget);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
CLAPPER_GTK_DEPRECATED_FOR(clapper_gtk_av_get_player)
|
||||
ClapperPlayer * clapper_gtk_video_get_player (ClapperGtkVideo *video);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
@@ -61,13 +62,13 @@ void clapper_gtk_video_set_touch_fade_delay (ClapperGtkVideo *video, guint delay
|
||||
CLAPPER_GTK_API
|
||||
guint clapper_gtk_video_get_touch_fade_delay (ClapperGtkVideo *video);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
CLAPPER_GTK_DEPRECATED_FOR(clapper_gtk_av_set_auto_inhibit)
|
||||
void clapper_gtk_video_set_auto_inhibit (ClapperGtkVideo *video, gboolean inhibit);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
CLAPPER_GTK_DEPRECATED_FOR(clapper_gtk_av_get_auto_inhibit)
|
||||
gboolean clapper_gtk_video_get_auto_inhibit (ClapperGtkVideo *video);
|
||||
|
||||
CLAPPER_GTK_API
|
||||
CLAPPER_GTK_DEPRECATED_FOR(clapper_gtk_av_get_inhibited)
|
||||
gboolean clapper_gtk_video_get_inhibited (ClapperGtkVideo *video);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -23,6 +23,8 @@
|
||||
#include <clapper-gtk/clapper-gtk-enums.h>
|
||||
#include <clapper-gtk/clapper-gtk-version.h>
|
||||
|
||||
#include <clapper-gtk/clapper-gtk-audio.h>
|
||||
#include <clapper-gtk/clapper-gtk-av.h>
|
||||
#include <clapper-gtk/clapper-gtk-billboard.h>
|
||||
#include <clapper-gtk/clapper-gtk-container.h>
|
||||
#include <clapper-gtk/clapper-gtk-extra-menu-button.h>
|
||||
|
@@ -90,6 +90,8 @@ clappergtk_conf_inc = [
|
||||
|
||||
clappergtk_headers = [
|
||||
'clapper-gtk.h',
|
||||
'clapper-gtk-audio.h',
|
||||
'clapper-gtk-av.h',
|
||||
'clapper-gtk-enums.h',
|
||||
'clapper-gtk-billboard.h',
|
||||
'clapper-gtk-container.h',
|
||||
@@ -109,6 +111,8 @@ clappergtk_headers = [
|
||||
clappergtk_visibility_header,
|
||||
]
|
||||
clappergtk_sources = [
|
||||
'clapper-gtk-audio.c',
|
||||
'clapper-gtk-av.c',
|
||||
'clapper-gtk-billboard.c',
|
||||
'clapper-gtk-buffering-animation.c',
|
||||
'clapper-gtk-buffering-paintable.c',
|
||||
@@ -127,6 +131,7 @@ clappergtk_sources = [
|
||||
'clapper-gtk-toggle-fullscreen-button.c',
|
||||
'clapper-gtk-toggle-play-button.c',
|
||||
'clapper-gtk-utils.c',
|
||||
'clapper-gtk-version.c',
|
||||
'clapper-gtk-video.c',
|
||||
'clapper-gtk-video-placeholder.c',
|
||||
clappergtk_resources,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface domain="clapper-gtk">
|
||||
<template class="ClapperGtkVideo" parent="GtkWidget">
|
||||
<template class="ClapperGtkVideo" parent="ClapperGtkAv">
|
||||
<child type="overlay">
|
||||
<object class="ClapperGtkStatus" id="status">
|
||||
<property name="halign">center</property>
|
||||
|
@@ -44,12 +44,16 @@ void clapper_app_bus_post_refresh_streams (ClapperAppBus *app_bus, GstObject *sr
|
||||
|
||||
void clapper_app_bus_post_refresh_timeline (ClapperAppBus *app_bus, GstObject *src);
|
||||
|
||||
void clapper_app_bus_post_insert_playlist (ClapperAppBus *app_bus, GstObject *src, GstObject *playlist_item, GObject *playlist);
|
||||
|
||||
void clapper_app_bus_post_simple_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id);
|
||||
|
||||
void clapper_app_bus_post_object_desc_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GstObject *object, const gchar *desc);
|
||||
|
||||
void clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, const gchar *desc, const gchar *details);
|
||||
|
||||
void clapper_app_bus_post_message_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GstMessage *msg);
|
||||
|
||||
void clapper_app_bus_post_error_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GError *error, const gchar *debug_info);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include "clapper-bus-private.h"
|
||||
#include "clapper-app-bus-private.h"
|
||||
#include "clapper-player-private.h"
|
||||
#include "clapper-queue-private.h"
|
||||
#include "clapper-media-item-private.h"
|
||||
#include "clapper-timeline-private.h"
|
||||
|
||||
@@ -41,9 +42,11 @@ enum
|
||||
CLAPPER_APP_BUS_STRUCTURE_PROP_NOTIFY,
|
||||
CLAPPER_APP_BUS_STRUCTURE_REFRESH_STREAMS,
|
||||
CLAPPER_APP_BUS_STRUCTURE_REFRESH_TIMELINE,
|
||||
CLAPPER_APP_BUS_STRUCTURE_INSERT_PLAYLIST,
|
||||
CLAPPER_APP_BUS_STRUCTURE_SIMPLE_SIGNAL,
|
||||
CLAPPER_APP_BUS_STRUCTURE_OBJECT_DESC_SIGNAL,
|
||||
CLAPPER_APP_BUS_STRUCTURE_DESC_WITH_DETAILS_SIGNAL,
|
||||
CLAPPER_APP_BUS_STRUCTURE_MESSAGE_SIGNAL,
|
||||
CLAPPER_APP_BUS_STRUCTURE_ERROR_SIGNAL
|
||||
};
|
||||
|
||||
@@ -52,9 +55,11 @@ static ClapperBusQuark _structure_quarks[] = {
|
||||
{"prop-notify", 0},
|
||||
{"refresh-streams", 0},
|
||||
{"refresh-timeline", 0},
|
||||
{"insert-playlist", 0},
|
||||
{"simple-signal", 0},
|
||||
{"object-desc-signal", 0},
|
||||
{"desc-with-details-signal", 0},
|
||||
{"message", 0},
|
||||
{"error-signal", 0},
|
||||
{NULL, 0}
|
||||
};
|
||||
@@ -65,6 +70,7 @@ enum
|
||||
CLAPPER_APP_BUS_FIELD_PSPEC,
|
||||
CLAPPER_APP_BUS_FIELD_SIGNAL_ID,
|
||||
CLAPPER_APP_BUS_FIELD_OBJECT,
|
||||
CLAPPER_APP_BUS_FIELD_OTHER_OBJECT,
|
||||
CLAPPER_APP_BUS_FIELD_DESC,
|
||||
CLAPPER_APP_BUS_FIELD_DETAILS,
|
||||
CLAPPER_APP_BUS_FIELD_ERROR,
|
||||
@@ -76,6 +82,7 @@ static ClapperBusQuark _field_quarks[] = {
|
||||
{"pspec", 0},
|
||||
{"signal-id", 0},
|
||||
{"object", 0},
|
||||
{"other-object", 0},
|
||||
{"desc", 0},
|
||||
{"details", 0},
|
||||
{"error", 0},
|
||||
@@ -160,6 +167,36 @@ _handle_refresh_timeline_msg (GstMessage *msg, const GstStructure *structure)
|
||||
clapper_timeline_refresh (timeline);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_app_bus_post_insert_playlist (ClapperAppBus *self, GstObject *src,
|
||||
GstObject *playlist_item, GObject *playlist)
|
||||
{
|
||||
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (INSERT_PLAYLIST),
|
||||
_FIELD_QUARK (OBJECT), GST_TYPE_OBJECT, playlist_item,
|
||||
_FIELD_QUARK (OTHER_OBJECT), G_TYPE_OBJECT, playlist,
|
||||
NULL);
|
||||
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure));
|
||||
}
|
||||
|
||||
static inline void
|
||||
_handle_insert_playlist_msg (GstMessage *msg, const GstStructure *structure)
|
||||
{
|
||||
ClapperPlayer *player = CLAPPER_PLAYER_CAST (GST_MESSAGE_SRC (msg));
|
||||
ClapperQueue *queue = clapper_player_get_queue (player);
|
||||
GstObject *playlist_item;
|
||||
GObject *playlist;
|
||||
|
||||
gst_structure_id_get (structure,
|
||||
_FIELD_QUARK (OBJECT), GST_TYPE_OBJECT, &playlist_item,
|
||||
_FIELD_QUARK (OTHER_OBJECT), G_TYPE_OBJECT, &playlist,
|
||||
NULL);
|
||||
clapper_queue_handle_playlist (queue,
|
||||
CLAPPER_MEDIA_ITEM (playlist_item), G_LIST_STORE (playlist));
|
||||
|
||||
gst_object_unref (playlist_item);
|
||||
g_object_unref (playlist);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_app_bus_post_simple_signal (ClapperAppBus *self, GstObject *src, guint signal_id)
|
||||
{
|
||||
@@ -241,6 +278,53 @@ _handle_desc_with_details_signal_msg (GstMessage *msg, const GstStructure *struc
|
||||
g_free (details);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_app_bus_post_message_signal (ClapperAppBus *self,
|
||||
GstObject *src, guint signal_id, GstMessage *msg)
|
||||
{
|
||||
/* Check for any "message" signal connection */
|
||||
if (g_signal_handler_find (src, G_SIGNAL_MATCH_ID,
|
||||
signal_id, 0, NULL, NULL, NULL) != 0) {
|
||||
const GstStructure *structure = gst_message_get_structure (msg);
|
||||
GQuark detail;
|
||||
|
||||
if (G_UNLIKELY (structure == NULL))
|
||||
return;
|
||||
|
||||
detail = g_quark_from_string (gst_structure_get_name (structure));
|
||||
|
||||
/* If specific detail is connected or ALL "message" handler */
|
||||
if (g_signal_handler_find (src, G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_DETAIL,
|
||||
signal_id, detail, NULL, NULL, NULL) != 0
|
||||
|| g_signal_handler_find (src, G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_DETAIL,
|
||||
signal_id, 0, NULL, NULL, NULL) != 0) {
|
||||
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (MESSAGE_SIGNAL),
|
||||
_FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id,
|
||||
_FIELD_QUARK (DETAILS), G_TYPE_UINT, detail,
|
||||
_FIELD_QUARK (OBJECT), GST_TYPE_MESSAGE, msg,
|
||||
NULL);
|
||||
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
_handle_message_signal_msg (GstMessage *msg, const GstStructure *structure)
|
||||
{
|
||||
guint signal_id = 0;
|
||||
GQuark detail = 0;
|
||||
GstMessage *fwd_message = NULL;
|
||||
|
||||
gst_structure_id_get (structure,
|
||||
_FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id,
|
||||
_FIELD_QUARK (DETAILS), G_TYPE_UINT, &detail,
|
||||
_FIELD_QUARK (OBJECT), GST_TYPE_MESSAGE, &fwd_message,
|
||||
NULL);
|
||||
g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, detail, fwd_message);
|
||||
|
||||
gst_message_unref (fwd_message);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_app_bus_post_error_signal (ClapperAppBus *self,
|
||||
GstObject *src, guint signal_id,
|
||||
@@ -285,10 +369,14 @@ clapper_app_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G
|
||||
_handle_refresh_streams_msg (msg, structure);
|
||||
else if (quark == _STRUCTURE_QUARK (REFRESH_TIMELINE))
|
||||
_handle_refresh_timeline_msg (msg, structure);
|
||||
else if (quark == _STRUCTURE_QUARK (INSERT_PLAYLIST))
|
||||
_handle_insert_playlist_msg (msg, structure);
|
||||
else if (quark == _STRUCTURE_QUARK (SIMPLE_SIGNAL))
|
||||
_handle_simple_signal_msg (msg, structure);
|
||||
else if (quark == _STRUCTURE_QUARK (OBJECT_DESC_SIGNAL))
|
||||
_handle_object_desc_signal_msg (msg, structure);
|
||||
else if (quark == _STRUCTURE_QUARK (MESSAGE_SIGNAL))
|
||||
_handle_message_signal_msg (msg, structure);
|
||||
else if (quark == _STRUCTURE_QUARK (ERROR_SIGNAL))
|
||||
_handle_error_signal_msg (msg, structure);
|
||||
else if (quark == _STRUCTURE_QUARK (DESC_WITH_DETAILS_SIGNAL))
|
||||
|
@@ -18,7 +18,9 @@
|
||||
|
||||
#include "clapper-cache-private.h"
|
||||
#include "clapper-version.h"
|
||||
|
||||
#include "clapper-extractable.h"
|
||||
#include "clapper-playlistable.h"
|
||||
#include "clapper-reactable.h"
|
||||
|
||||
#define CLAPPER_CACHE_HEADER "CLAPPER"
|
||||
@@ -26,6 +28,7 @@
|
||||
typedef enum
|
||||
{
|
||||
CLAPPER_CACHE_IFACE_EXTRACTABLE = 1,
|
||||
CLAPPER_CACHE_IFACE_PLAYLISTABLE,
|
||||
CLAPPER_CACHE_IFACE_REACTABLE,
|
||||
} ClapperCacheIfaces;
|
||||
|
||||
@@ -244,6 +247,8 @@ clapper_cache_read_iface (const gchar **data)
|
||||
switch (iface_id) {
|
||||
case CLAPPER_CACHE_IFACE_EXTRACTABLE:
|
||||
return CLAPPER_TYPE_EXTRACTABLE;
|
||||
case CLAPPER_CACHE_IFACE_PLAYLISTABLE:
|
||||
return CLAPPER_TYPE_PLAYLISTABLE;
|
||||
case CLAPPER_CACHE_IFACE_REACTABLE:
|
||||
return CLAPPER_TYPE_REACTABLE;
|
||||
default:
|
||||
@@ -436,6 +441,8 @@ clapper_cache_store_iface (GByteArray *bytes, GType iface)
|
||||
|
||||
if (iface == CLAPPER_TYPE_EXTRACTABLE)
|
||||
iface_id = CLAPPER_CACHE_IFACE_EXTRACTABLE;
|
||||
else if (iface == CLAPPER_TYPE_PLAYLISTABLE)
|
||||
iface_id = CLAPPER_CACHE_IFACE_PLAYLISTABLE;
|
||||
else if (iface == CLAPPER_TYPE_REACTABLE)
|
||||
iface_id = CLAPPER_CACHE_IFACE_REACTABLE;
|
||||
else
|
||||
|
@@ -46,12 +46,14 @@
|
||||
#include "clapper-enhancer-proxy-list.h"
|
||||
#include "clapper-basic-functions.h"
|
||||
#include "clapper-cache-private.h"
|
||||
#include "clapper-extractable.h"
|
||||
#include "clapper-reactable.h"
|
||||
#include "clapper-player-private.h"
|
||||
#include "clapper-utils-private.h"
|
||||
#include "clapper-enums.h"
|
||||
|
||||
#include "clapper-extractable.h"
|
||||
#include "clapper-playlistable.h"
|
||||
#include "clapper-reactable.h"
|
||||
|
||||
#include "clapper-functionalities-availability.h"
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
@@ -464,7 +466,7 @@ gboolean
|
||||
clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *self, GObject *enhancer)
|
||||
{
|
||||
/* NOTE: REACTABLE must be last for "allowed" to work as expected */
|
||||
const GType enhancer_types[] = { CLAPPER_TYPE_EXTRACTABLE, CLAPPER_TYPE_REACTABLE };
|
||||
const GType enhancer_types[] = { CLAPPER_TYPE_EXTRACTABLE, CLAPPER_TYPE_PLAYLISTABLE, CLAPPER_TYPE_REACTABLE };
|
||||
GType *ifaces;
|
||||
GParamSpec **pspecs;
|
||||
GParamFlags enhancer_flags;
|
||||
@@ -1299,7 +1301,7 @@ clapper_enhancer_proxy_class_init (ClapperEnhancerProxyClass *klass)
|
||||
*
|
||||
* This effectively means whether the given enhancer can be used.
|
||||
*
|
||||
* By default all enhancers that work on-demand such as [iface@Clapper.Extractable]
|
||||
* By default all enhancers that work on-demand ([iface@Clapper.Extractable], [iface@Clapper.Playlistable])
|
||||
* are allowed while enhancers implementing [iface@Clapper.Reactable] are not.
|
||||
*
|
||||
* Value of this property from a `GLOBAL` [class@Clapper.EnhancerProxyList] will carry
|
||||
|
@@ -32,6 +32,7 @@ static HMODULE _enhancers_dll_handle = NULL;
|
||||
|
||||
// Supported interfaces
|
||||
#include "clapper-extractable.h"
|
||||
#include "clapper-playlistable.h"
|
||||
#include "clapper-reactable.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_enhancers_loader_debug
|
||||
@@ -140,7 +141,7 @@ clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies)
|
||||
* Otherwise make an instance and fill missing data from it (slow). */
|
||||
if (!(filled = clapper_enhancer_proxy_fill_from_cache (proxy))) {
|
||||
GObject *enhancer;
|
||||
const GType main_types[] = { CLAPPER_TYPE_EXTRACTABLE, CLAPPER_TYPE_REACTABLE };
|
||||
const GType main_types[] = { CLAPPER_TYPE_EXTRACTABLE, CLAPPER_TYPE_PLAYLISTABLE, CLAPPER_TYPE_REACTABLE };
|
||||
guint j;
|
||||
|
||||
/* We cannot ask libpeas for "any" of our main interfaces, so try each one until found */
|
||||
|
@@ -29,6 +29,9 @@ G_BEGIN_DECLS
|
||||
G_GNUC_INTERNAL
|
||||
ClapperHarvest * clapper_harvest_new (void);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_harvest_set_enhancer_in_caps (ClapperHarvest *harvest, ClapperEnhancerProxy *proxy);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_harvest_unpack (ClapperHarvest *harvest, GstBuffer **buffer, gsize *buf_size, GstCaps **caps, GstTagList **tags, GstToc **toc, GstStructure **headers);
|
||||
|
||||
|
@@ -94,6 +94,13 @@ clapper_harvest_new (void)
|
||||
return harvest;
|
||||
}
|
||||
|
||||
void
|
||||
clapper_harvest_set_enhancer_in_caps (ClapperHarvest *self, ClapperEnhancerProxy *proxy)
|
||||
{
|
||||
gst_caps_set_simple (self->caps, "enhancer", G_TYPE_STRING,
|
||||
clapper_enhancer_proxy_get_module_name (proxy), NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_harvest_unpack (ClapperHarvest *self,
|
||||
GstBuffer **buffer, gsize *buf_size, GstCaps **caps,
|
||||
@@ -221,7 +228,6 @@ clapper_harvest_fill_from_cache (ClapperHarvest *self, ClapperEnhancerProxy *pro
|
||||
gchar *filename;
|
||||
const gchar *data, *read_str;
|
||||
const guint8 *buf_data;
|
||||
guint8 *buf_copy;
|
||||
gsize buf_size;
|
||||
gint64 epoch_cached, epoch_now = 0;
|
||||
gdouble exp_seconds;
|
||||
@@ -281,10 +287,10 @@ clapper_harvest_fill_from_cache (ClapperHarvest *self, ClapperEnhancerProxy *pro
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Read media type */
|
||||
/* Read caps */
|
||||
read_str = clapper_cache_read_string (&data);
|
||||
if (G_UNLIKELY (read_str == NULL)) {
|
||||
GST_ERROR_OBJECT (self, "Could not read media type from cache file");
|
||||
GST_ERROR_OBJECT (self, "Could not read caps from cache file");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
@@ -296,9 +302,12 @@ clapper_harvest_fill_from_cache (ClapperHarvest *self, ClapperEnhancerProxy *pro
|
||||
}
|
||||
|
||||
/* Fill harvest */
|
||||
buf_copy = g_memdup2 (buf_data, buf_size);
|
||||
if (!clapper_harvest_fill (self, read_str, buf_copy, buf_size))
|
||||
if (!(self->caps = gst_caps_from_string (read_str))) {
|
||||
GST_ERROR_OBJECT (self, "Could not construct caps from cache");
|
||||
goto finish;
|
||||
}
|
||||
self->buffer = gst_buffer_new_memdup (buf_data, buf_size);
|
||||
self->buf_size = buf_size;
|
||||
|
||||
/* Read tags */
|
||||
read_str = clapper_cache_read_string (&data);
|
||||
@@ -332,7 +341,6 @@ clapper_harvest_export_to_cache (ClapperHarvest *self, ClapperEnhancerProxy *pro
|
||||
const GstStructure *config, GUri *uri)
|
||||
{
|
||||
GByteArray *bytes;
|
||||
const GstStructure *caps_structure;
|
||||
gchar *filename, *temp_str = NULL;
|
||||
gboolean data_ok = TRUE;
|
||||
|
||||
@@ -366,12 +374,13 @@ clapper_harvest_export_to_cache (ClapperHarvest *self, ClapperEnhancerProxy *pro
|
||||
clapper_cache_store_string (bytes, temp_str); // NULL when no config
|
||||
g_clear_pointer (&temp_str, g_free);
|
||||
|
||||
/* Store media type */
|
||||
caps_structure = gst_caps_get_structure (self->caps, 0);
|
||||
if (G_LIKELY (caps_structure != NULL)) {
|
||||
clapper_cache_store_string (bytes, gst_structure_get_name (caps_structure));
|
||||
/* Store caps */
|
||||
temp_str = gst_caps_to_string (self->caps);
|
||||
if (G_LIKELY (temp_str != NULL)) {
|
||||
clapper_cache_store_string (bytes, temp_str);
|
||||
g_clear_pointer (&temp_str, g_free);
|
||||
} else {
|
||||
GST_ERROR_OBJECT (self, "Cannot cache empty caps");
|
||||
GST_ERROR_OBJECT (self, "Cannot cache caps");
|
||||
data_ok = FALSE;
|
||||
}
|
||||
|
||||
@@ -434,11 +443,15 @@ clapper_harvest_export_to_cache (ClapperHarvest *self, ClapperEnhancerProxy *pro
|
||||
*
|
||||
* Commonly used media types are:
|
||||
*
|
||||
* * `application/dash+xml`
|
||||
* * `application/dash+xml` - DASH manifest
|
||||
*
|
||||
* * `application/x-hls`
|
||||
* * `application/x-hls` - HLS manifest
|
||||
*
|
||||
* * `text/uri-list`
|
||||
* * `text/x-uri` - direct media URI
|
||||
*
|
||||
* * `text/uri-list` - playlist of URIs
|
||||
*
|
||||
* * `application/clapper-playlist` - custom playlist format
|
||||
*
|
||||
* Returns: %TRUE when filled successfully, %FALSE if taken data was empty.
|
||||
*
|
||||
@@ -459,6 +472,7 @@ clapper_harvest_fill (ClapperHarvest *self, const gchar *media_type, gpointer da
|
||||
if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_LOG) {
|
||||
gboolean is_printable = (strcmp (media_type, "application/dash+xml") == 0)
|
||||
|| (strcmp (media_type, "application/x-hls") == 0)
|
||||
|| (strcmp (media_type, "text/x-uri") == 0)
|
||||
|| (strcmp (media_type, "text/uri-list") == 0);
|
||||
|
||||
if (is_printable) {
|
||||
|
@@ -33,6 +33,9 @@ void clapper_media_item_update_from_tag_list (ClapperMediaItem *item, const GstT
|
||||
G_GNUC_INTERNAL
|
||||
void clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDiscovererInfo *info);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_media_item_update_from_item (ClapperMediaItem *item, ClapperMediaItem *other_item, ClapperPlayer *player);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_media_item_set_duration (ClapperMediaItem *item, gdouble duration, ClapperAppBus *app_bus);
|
||||
|
||||
|
@@ -51,6 +51,10 @@ struct _ClapperMediaItem
|
||||
gchar *container_format;
|
||||
gdouble duration;
|
||||
|
||||
/* Whether using title from URI */
|
||||
gboolean title_is_parsed;
|
||||
|
||||
GSList *redirects;
|
||||
gchar *cache_uri;
|
||||
|
||||
/* For shuffle */
|
||||
@@ -198,6 +202,12 @@ clapper_media_item_get_id (ClapperMediaItem *self)
|
||||
return self->id;
|
||||
}
|
||||
|
||||
/* FIXME: 1.0:
|
||||
* Consider change to be transfer-full and just return latest data from redirects
|
||||
* list (alternatively expose redirect URI). This should make it possible to work
|
||||
* with enhancers that would benefit from knowledge about URI changes
|
||||
* (e.g "Recall" could read actual media instead of playlist file).
|
||||
*/
|
||||
/**
|
||||
* clapper_media_item_get_uri:
|
||||
* @item: a #ClapperMediaItem
|
||||
@@ -295,11 +305,11 @@ clapper_media_item_get_title (ClapperMediaItem *self)
|
||||
|
||||
static inline gboolean
|
||||
_refresh_tag_prop_unlocked (ClapperMediaItem *self, const gchar *tag,
|
||||
gboolean from_user, gchar **tag_ptr)
|
||||
gboolean allow_overwrite, gchar **tag_ptr)
|
||||
{
|
||||
const gchar *string;
|
||||
|
||||
if ((*tag_ptr && from_user) // if already set, user cannot modify it
|
||||
if ((*tag_ptr && !allow_overwrite)
|
||||
|| !gst_tag_list_peek_string_index (self->tags, tag, 0, &string) // guarantees non-empty string
|
||||
|| (g_strcmp0 (*tag_ptr, string) == 0))
|
||||
return FALSE;
|
||||
@@ -488,11 +498,12 @@ clapper_media_item_insert_tags_internal (ClapperMediaItem *self, const GstTagLis
|
||||
*flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TAGS;
|
||||
|
||||
if ((title_changed = _refresh_tag_prop_unlocked (self, GST_TAG_TITLE,
|
||||
from_user, &self->title))) {
|
||||
(!from_user || self->title_is_parsed), &self->title))) {
|
||||
self->title_is_parsed = FALSE;
|
||||
*flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TITLE;
|
||||
}
|
||||
cont_changed = _refresh_tag_prop_unlocked (self, GST_TAG_CONTAINER_FORMAT,
|
||||
from_user, &self->container_format);
|
||||
!from_user, &self->container_format);
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
@@ -668,6 +679,61 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco
|
||||
gst_object_unref (player);
|
||||
}
|
||||
|
||||
/* XXX: Must be set from player thread */
|
||||
static inline gboolean
|
||||
clapper_media_item_set_redirect_uri (ClapperMediaItem *self, const gchar *redirect_uri)
|
||||
{
|
||||
/* Check if we did not already redirect into that URI (prevent endless loop) */
|
||||
if (!redirect_uri || g_slist_find_custom (self->redirects, redirect_uri, (GCompareFunc) strcmp))
|
||||
return FALSE;
|
||||
|
||||
self->redirects = g_slist_prepend (self->redirects, g_strdup (redirect_uri));
|
||||
GST_DEBUG_OBJECT (self, "Set redirect URI: \"%s\"", (gchar *) self->redirects->data);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_media_item_update_from_item (ClapperMediaItem *self, ClapperMediaItem *other_item,
|
||||
ClapperPlayer *player)
|
||||
{
|
||||
gboolean title_changed = FALSE;
|
||||
|
||||
if (!clapper_media_item_set_redirect_uri (self, clapper_media_item_get_uri (other_item)))
|
||||
return FALSE;
|
||||
|
||||
GST_OBJECT_LOCK (other_item);
|
||||
|
||||
if (other_item->tags)
|
||||
clapper_media_item_update_from_tag_list (self, other_item->tags, player);
|
||||
|
||||
/* Since its redirect now, we have to update title to describe new file instead of
|
||||
* being a playlist title. If other item had parsed title, it also means that tags
|
||||
* did not contain it, thus we have to manually update it and notify. */
|
||||
if (other_item->title_is_parsed) {
|
||||
GST_OBJECT_LOCK (self);
|
||||
title_changed = g_set_str (&self->title, other_item->title);
|
||||
self->title_is_parsed = TRUE;
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (other_item);
|
||||
|
||||
if (title_changed) {
|
||||
ClapperReactableItemUpdatedFlags flags = CLAPPER_REACTABLE_ITEM_UPDATED_TITLE;
|
||||
ClapperFeaturesManager *features_manager;
|
||||
|
||||
clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_TITLE]);
|
||||
|
||||
if (player->reactables_manager)
|
||||
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags);
|
||||
if ((features_manager = clapper_player_get_features_manager (player)))
|
||||
clapper_features_manager_trigger_item_updated (features_manager, self);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* XXX: Must be set from player thread or upon construction */
|
||||
void
|
||||
clapper_media_item_set_cache_location (ClapperMediaItem *self, const gchar *location)
|
||||
@@ -682,7 +748,7 @@ clapper_media_item_set_cache_location (ClapperMediaItem *self, const gchar *loca
|
||||
}
|
||||
|
||||
/* XXX: Can only be read from player thread.
|
||||
* Returns cache URI if available, item URI otherwise. */
|
||||
* Returns cache URI if available, otherwise redirect or item URI. */
|
||||
inline const gchar *
|
||||
clapper_media_item_get_playback_uri (ClapperMediaItem *self)
|
||||
{
|
||||
@@ -702,6 +768,9 @@ clapper_media_item_get_playback_uri (ClapperMediaItem *self)
|
||||
clapper_media_item_set_cache_location (self, NULL);
|
||||
}
|
||||
|
||||
if (self->redirects)
|
||||
return self->redirects->data;
|
||||
|
||||
return self->uri;
|
||||
}
|
||||
|
||||
@@ -745,6 +814,7 @@ clapper_media_item_constructed (GObject *object)
|
||||
self->uri = g_strdup ("file://");
|
||||
|
||||
self->title = clapper_utils_title_from_uri (self->uri);
|
||||
self->title_is_parsed = (self->title != NULL);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
}
|
||||
@@ -765,6 +835,7 @@ clapper_media_item_finalize (GObject *object)
|
||||
gst_object_unparent (GST_OBJECT_CAST (self->timeline));
|
||||
gst_object_unref (self->timeline);
|
||||
|
||||
g_slist_free_full (self->redirects, g_free);
|
||||
g_free (self->cache_uri);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
@@ -892,7 +963,8 @@ clapper_media_item_class_init (ClapperMediaItemClass *klass)
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/* FIXME: 1.0: Consider rename to e.g. "(menu/display)-title"
|
||||
* and also make it non-nullable (return URI as final fallback) */
|
||||
* and also make it non-nullable (return URI as final fallback).
|
||||
* NOTE: It would probably need to work with redirect URI */
|
||||
/**
|
||||
* ClapperMediaItem:title:
|
||||
*
|
||||
|
@@ -831,6 +831,71 @@ _handle_element_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
|
||||
g_free (name);
|
||||
g_free (details);
|
||||
} else if (gst_message_has_name (msg, "ClapperPlaylistParsed")) {
|
||||
ClapperMediaItem *playlist_item = NULL;
|
||||
GListStore *playlist = NULL;
|
||||
const GstStructure *structure = gst_message_get_structure (msg);
|
||||
guint n_items;
|
||||
|
||||
/* If message contains item, use that.
|
||||
* Otherwise assume pending item was parsed. */
|
||||
if (gst_structure_has_field (structure, "item")) {
|
||||
gst_structure_get (structure,
|
||||
"item", CLAPPER_TYPE_MEDIA_ITEM, &playlist_item, NULL);
|
||||
} else {
|
||||
GST_OBJECT_LOCK (player);
|
||||
|
||||
/* Playlist is always parsed before playback starts */
|
||||
if (player->pending_item)
|
||||
playlist_item = gst_object_ref (player->pending_item);
|
||||
|
||||
GST_OBJECT_UNLOCK (player);
|
||||
}
|
||||
|
||||
if (G_UNLIKELY (playlist_item == NULL)) {
|
||||
GST_WARNING_OBJECT (player, "Playlist parsed without media item set");
|
||||
return;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT (player, "Received parsed playlist of %" GST_PTR_FORMAT
|
||||
"(%s)", playlist_item, clapper_media_item_get_uri (playlist_item));
|
||||
|
||||
gst_structure_get (structure,
|
||||
"playlist", G_TYPE_LIST_STORE, &playlist, NULL);
|
||||
|
||||
n_items = g_list_model_get_n_items (G_LIST_MODEL (playlist));
|
||||
|
||||
if (G_LIKELY (n_items > 0)) {
|
||||
ClapperMediaItem *active_item = g_list_model_get_item (G_LIST_MODEL (playlist), 0);
|
||||
gboolean updated;
|
||||
|
||||
/* Update redirect URI (must be done from player thread) */
|
||||
updated = clapper_media_item_update_from_item (playlist_item, active_item, player);
|
||||
gst_object_unref (active_item);
|
||||
|
||||
if (!updated) {
|
||||
GstMessage *msg;
|
||||
GError *error;
|
||||
|
||||
error = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
|
||||
"Detected infinite redirection in playlist");
|
||||
msg = gst_message_new_error (GST_OBJECT (player), error, NULL);
|
||||
|
||||
_handle_error_msg (msg, player);
|
||||
|
||||
g_error_free (error);
|
||||
gst_message_unref (msg);
|
||||
} else if (n_items > 1) {
|
||||
/* Forward to append remaining items (must be done from main thread) */
|
||||
clapper_app_bus_post_insert_playlist (player->app_bus,
|
||||
GST_OBJECT_CAST (player),
|
||||
GST_OBJECT_CAST (playlist_item),
|
||||
G_OBJECT (playlist));
|
||||
}
|
||||
}
|
||||
|
||||
gst_object_unref (playlist_item);
|
||||
g_object_unref (playlist);
|
||||
} else if (gst_message_has_name (msg, "GstCacheDownloadComplete")) {
|
||||
ClapperMediaItem *downloaded_item = NULL;
|
||||
const GstStructure *structure;
|
||||
@@ -865,6 +930,11 @@ _handle_element_msg (GstMessage *msg, ClapperPlayer *player)
|
||||
GST_OBJECT_CAST (downloaded_item), location);
|
||||
|
||||
gst_object_unref (downloaded_item);
|
||||
} else {
|
||||
guint signal_id = g_signal_lookup ("message", CLAPPER_TYPE_PLAYER);
|
||||
|
||||
clapper_app_bus_post_message_signal (player->app_bus,
|
||||
GST_OBJECT_CAST (player), signal_id, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -111,6 +111,7 @@ enum
|
||||
SIGNAL_SEEK_DONE,
|
||||
SIGNAL_DOWNLOAD_COMPLETE,
|
||||
SIGNAL_MISSING_PLUGIN,
|
||||
SIGNAL_MESSAGE,
|
||||
SIGNAL_WARNING,
|
||||
SIGNAL_ERROR,
|
||||
SIGNAL_LAST
|
||||
@@ -826,7 +827,8 @@ _element_setup_cb (GstElement *playbin, GstElement *element, ClapperPlayer *self
|
||||
factory_name = g_intern_static_string (GST_OBJECT_NAME (factory));
|
||||
GST_INFO_OBJECT (self, "Element setup: %s", factory_name);
|
||||
|
||||
if (factory_name == g_intern_static_string ("clapperextractablesrc")) {
|
||||
if (factory_name == g_intern_static_string ("clapperextractablesrc")
|
||||
|| factory_name == g_intern_static_string ("clapperplaylistdemux")) {
|
||||
g_object_set (element,
|
||||
"enhancer-proxies", self->enhancer_proxies,
|
||||
NULL);
|
||||
@@ -2984,6 +2986,28 @@ clapper_player_class_init (ClapperPlayerClass *klass)
|
||||
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
|
||||
|
||||
/**
|
||||
* ClapperPlayer::message:
|
||||
* @player: a #ClapperPlayer
|
||||
* @msg: a #GstMessage
|
||||
*
|
||||
* Allows for applications to receive element messages posted
|
||||
* on the underlaying pipeline bus.
|
||||
*
|
||||
* This is a detailed signal. Connect to it via `message::name`
|
||||
* to only receive messages with a certain `name`.
|
||||
*
|
||||
* Player will only forward messages to the main app thread (from which
|
||||
* this signal is emitted) that have a matching signal handler, thus
|
||||
* it is more efficient to listen only for specific messages instead
|
||||
* of connecting to simply `message` with no details (without message name).
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
signals[SIGNAL_MESSAGE] = g_signal_new ("message", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS | G_SIGNAL_DETAILED,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_MESSAGE);
|
||||
|
||||
/**
|
||||
* ClapperPlayer::warning:
|
||||
* @player: a #ClapperPlayer
|
||||
|
31
src/lib/clapper/clapper-playlistable-private.h
Normal file
31
src/lib/clapper/clapper-playlistable-private.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "clapper-playlistable.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
gboolean clapper_playlistable_parse (ClapperPlaylistable *playlistable, GUri *uri, GBytes *bytes, GListStore *playlist, GCancellable *cancellable, GError **error);
|
||||
|
||||
G_END_DECLS
|
59
src/lib/clapper/clapper-playlistable.c
Normal file
59
src/lib/clapper/clapper-playlistable.c
Normal file
@@ -0,0 +1,59 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ClapperPlaylistable:
|
||||
*
|
||||
* An interface for creating enhancers that parse data into individual media items.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "clapper-playlistable-private.h"
|
||||
|
||||
G_DEFINE_INTERFACE (ClapperPlaylistable, clapper_playlistable, G_TYPE_OBJECT);
|
||||
|
||||
static gboolean
|
||||
clapper_playlistable_default_parse (ClapperPlaylistable *self, GUri *uri, GBytes *bytes,
|
||||
GListStore *playlist, GCancellable *cancellable, GError **error)
|
||||
{
|
||||
if (*error == NULL) {
|
||||
g_set_error (error, GST_CORE_ERROR,
|
||||
GST_CORE_ERROR_NOT_IMPLEMENTED,
|
||||
"Playlistable object did not implement parse function");
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_playlistable_default_init (ClapperPlaylistableInterface *iface)
|
||||
{
|
||||
iface->parse = clapper_playlistable_default_parse;
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_playlistable_parse (ClapperPlaylistable *self, GUri *uri, GBytes *bytes,
|
||||
GListStore *playlist, GCancellable *cancellable, GError **error)
|
||||
{
|
||||
ClapperPlaylistableInterface *iface = CLAPPER_PLAYLISTABLE_GET_IFACE (self);
|
||||
|
||||
return iface->parse (self, uri, bytes, playlist, cancellable, error);
|
||||
}
|
71
src/lib/clapper/clapper-playlistable.h
Normal file
71
src/lib/clapper/clapper-playlistable.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_TYPE_PLAYLISTABLE (clapper_playlistable_get_type())
|
||||
#define CLAPPER_PLAYLISTABLE_CAST(obj) ((ClapperPlaylistable *)(obj))
|
||||
|
||||
CLAPPER_API
|
||||
G_DECLARE_INTERFACE (ClapperPlaylistable, clapper_playlistable, CLAPPER, PLAYLISTABLE, GObject)
|
||||
|
||||
/**
|
||||
* ClapperPlaylistableInterface:
|
||||
* @parent_iface: The parent interface structure.
|
||||
* @parse: Parse bytes and fill playlist.
|
||||
*/
|
||||
struct _ClapperPlaylistableInterface
|
||||
{
|
||||
GTypeInterface parent_iface;
|
||||
|
||||
/**
|
||||
* ClapperPlaylistableInterface::parse:
|
||||
* @playlistable: a #ClapperPlaylistable
|
||||
* @uri: a source #GUri
|
||||
* @bytes: a #GBytes
|
||||
* @playlist: a #GListStore for media items
|
||||
* @cancellable: a #GCancellable object
|
||||
* @error: a #GError
|
||||
*
|
||||
* Parse @bytes and fill @playlist with [class@Clapper.MediaItem] objects.
|
||||
*
|
||||
* If implementation returns %FALSE, whole @playlist content will be discarded.
|
||||
*
|
||||
* Returns: whether parsing was successful.
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
gboolean (* parse) (ClapperPlaylistable *playlistable, GUri *uri, GBytes *bytes, GListStore *playlist, GCancellable *cancellable, GError **error);
|
||||
|
||||
/*< private >*/
|
||||
gpointer padding[8];
|
||||
};
|
||||
|
||||
G_END_DECLS
|
@@ -19,6 +19,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "clapper-queue.h"
|
||||
#include "clapper-media-item.h"
|
||||
@@ -31,6 +32,8 @@ ClapperQueue * clapper_queue_new (void);
|
||||
|
||||
void clapper_queue_handle_played_item_changed (ClapperQueue *queue, ClapperMediaItem *played_item, ClapperAppBus *app_bus);
|
||||
|
||||
void clapper_queue_handle_playlist (ClapperQueue *queue, ClapperMediaItem *playlist_item, GListStore *playlist);
|
||||
|
||||
void clapper_queue_handle_about_to_finish (ClapperQueue *queue, ClapperPlayer *player);
|
||||
|
||||
gboolean clapper_queue_handle_eos (ClapperQueue *queue, ClapperPlayer *player);
|
||||
|
@@ -337,6 +337,42 @@ _get_next_item_unlocked (ClapperQueue *self, ClapperQueueProgressionMode mode)
|
||||
return next_item;
|
||||
}
|
||||
|
||||
static void
|
||||
_take_item_unlocked (ClapperQueue *self, ClapperMediaItem *item, gint index)
|
||||
{
|
||||
guint prev_length = self->items->len;
|
||||
|
||||
g_ptr_array_insert (self->items, index, item);
|
||||
gst_object_set_parent (GST_OBJECT_CAST (item), GST_OBJECT_CAST (self));
|
||||
|
||||
/* In append we inserted at array length */
|
||||
if (index < 0)
|
||||
index = prev_length;
|
||||
|
||||
_announce_model_update (self, index, 0, 1, item);
|
||||
|
||||
/* If has selection and inserting before it */
|
||||
if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION
|
||||
&& (guint) index <= self->current_index) {
|
||||
self->current_index++;
|
||||
_announce_current_index_change (self);
|
||||
} else if (prev_length == 0 && _replace_current_item_unlocked (self, item, 0)) {
|
||||
/* If queue was empty, auto select first item and announce it */
|
||||
_announce_current_item_and_index_change (self);
|
||||
} else if (self->current_index == prev_length - 1
|
||||
&& clapper_queue_get_progression_mode (self) == CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE) {
|
||||
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
||||
gboolean after_eos = (gboolean) g_atomic_int_get (&player->eos);
|
||||
|
||||
/* In consecutive progression automatically select next item
|
||||
* if we were after EOS of last queue item */
|
||||
if (after_eos && _replace_current_item_unlocked (self, item, index))
|
||||
_announce_current_item_and_index_change (self);
|
||||
|
||||
gst_object_unref (player);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For gapless we need to manually replace current item in queue when it starts
|
||||
* playing and emit notify about change, this function will do that if necessary
|
||||
@@ -366,6 +402,31 @@ clapper_queue_handle_played_item_changed (ClapperQueue *self, ClapperMediaItem *
|
||||
}
|
||||
}
|
||||
|
||||
/* Must be called from main thread */
|
||||
void
|
||||
clapper_queue_handle_playlist (ClapperQueue *self, ClapperMediaItem *playlist_item,
|
||||
GListStore *playlist)
|
||||
{
|
||||
GListModel *playlist_model = G_LIST_MODEL (playlist);
|
||||
guint i, index, n_items = g_list_model_get_n_items (playlist_model);
|
||||
|
||||
CLAPPER_QUEUE_REC_LOCK (self);
|
||||
|
||||
/* If playlist item is still in the queue, insert
|
||||
* remaining items after it, otherwise append */
|
||||
if (G_LIKELY (g_ptr_array_find (self->items, playlist_item, &index)))
|
||||
index++;
|
||||
else
|
||||
index = self->items->len;
|
||||
|
||||
for (i = 1; i < n_items; ++i) {
|
||||
ClapperMediaItem *item = g_list_model_get_item (playlist_model, i);
|
||||
_take_item_unlocked (self, item, index++);
|
||||
}
|
||||
|
||||
CLAPPER_QUEUE_REC_UNLOCK (self);
|
||||
}
|
||||
|
||||
void
|
||||
clapper_queue_handle_about_to_finish (ClapperQueue *self, ClapperPlayer *player)
|
||||
{
|
||||
@@ -481,39 +542,8 @@ clapper_queue_insert_item (ClapperQueue *self, ClapperMediaItem *item, gint inde
|
||||
|
||||
CLAPPER_QUEUE_REC_LOCK (self);
|
||||
|
||||
if (!g_ptr_array_find (self->items, item, NULL)) {
|
||||
guint prev_length = self->items->len;
|
||||
|
||||
g_ptr_array_insert (self->items, index, gst_object_ref (item));
|
||||
gst_object_set_parent (GST_OBJECT_CAST (item), GST_OBJECT_CAST (self));
|
||||
|
||||
/* In append we inserted at array length */
|
||||
if (index < 0)
|
||||
index = prev_length;
|
||||
|
||||
_announce_model_update (self, index, 0, 1, item);
|
||||
|
||||
/* If has selection and inserting before it */
|
||||
if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION
|
||||
&& (guint) index <= self->current_index) {
|
||||
self->current_index++;
|
||||
_announce_current_index_change (self);
|
||||
} else if (prev_length == 0 && _replace_current_item_unlocked (self, item, 0)) {
|
||||
/* If queue was empty, auto select first item and announce it */
|
||||
_announce_current_item_and_index_change (self);
|
||||
} else if (self->current_index == prev_length - 1
|
||||
&& clapper_queue_get_progression_mode (self) == CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE) {
|
||||
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
|
||||
gboolean after_eos = (gboolean) g_atomic_int_get (&player->eos);
|
||||
|
||||
/* In consecutive progression automatically select next item
|
||||
* if we were after EOS of last queue item */
|
||||
if (after_eos && _replace_current_item_unlocked (self, item, index))
|
||||
_announce_current_item_and_index_change (self);
|
||||
|
||||
gst_object_unref (player);
|
||||
}
|
||||
}
|
||||
if (!g_ptr_array_find (self->items, item, NULL))
|
||||
_take_item_unlocked (self, gst_object_ref (item), index);
|
||||
|
||||
CLAPPER_QUEUE_REC_UNLOCK (self);
|
||||
}
|
||||
|
95
src/lib/clapper/clapper-version.c
Normal file
95
src/lib/clapper/clapper-version.c
Normal file
@@ -0,0 +1,95 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "clapper-version.h"
|
||||
|
||||
/**
|
||||
* clapper_get_major_version:
|
||||
*
|
||||
* Clapper runtime major version component
|
||||
*
|
||||
* This returns the Clapper library version your code is
|
||||
* running against unlike [const@Clapper.MAJOR_VERSION]
|
||||
* which represents compile time version.
|
||||
*
|
||||
* Returns: the major version number of the Clapper library
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
guint
|
||||
clapper_get_major_version (void)
|
||||
{
|
||||
return CLAPPER_MAJOR_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_get_minor_version:
|
||||
*
|
||||
* Clapper runtime minor version component
|
||||
*
|
||||
* This returns the Clapper library version your code is
|
||||
* running against unlike [const@Clapper.MINOR_VERSION]
|
||||
* which represents compile time version.
|
||||
*
|
||||
* Returns: the minor version number of the Clapper library
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
guint
|
||||
clapper_get_minor_version (void)
|
||||
{
|
||||
return CLAPPER_MINOR_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_get_micro_version:
|
||||
*
|
||||
* Clapper runtime micro version component
|
||||
*
|
||||
* This returns the Clapper library version your code is
|
||||
* running against unlike [const@Clapper.MICRO_VERSION]
|
||||
* which represents compile time version.
|
||||
*
|
||||
* Returns: the micro version number of the Clapper library
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
guint
|
||||
clapper_get_micro_version (void)
|
||||
{
|
||||
return CLAPPER_MICRO_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_get_version_s:
|
||||
*
|
||||
* Clapper runtime version as string
|
||||
*
|
||||
* This returns the Clapper library version your code is
|
||||
* running against unlike [const@Clapper.VERSION_S]
|
||||
* which represents compile time version.
|
||||
*
|
||||
* Returns: the version of the Clapper library as string
|
||||
*
|
||||
* Since: 0.10
|
||||
*/
|
||||
const gchar *
|
||||
clapper_get_version_s (void)
|
||||
{
|
||||
return CLAPPER_VERSION_S;
|
||||
}
|
@@ -22,6 +22,8 @@
|
||||
#error "Only <clapper/clapper.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/**
|
||||
* CLAPPER_MAJOR_VERSION:
|
||||
*
|
||||
@@ -73,3 +75,15 @@
|
||||
(CLAPPER_MAJOR_VERSION == (major) && CLAPPER_MINOR_VERSION > (minor)) || \
|
||||
(CLAPPER_MAJOR_VERSION == (major) && CLAPPER_MINOR_VERSION == (minor) && \
|
||||
CLAPPER_MICRO_VERSION >= (micro)))
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
guint clapper_get_major_version (void);
|
||||
|
||||
guint clapper_get_minor_version (void);
|
||||
|
||||
guint clapper_get_micro_version (void);
|
||||
|
||||
const gchar * clapper_get_version_s (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -44,6 +44,7 @@
|
||||
#include <clapper/clapper-video-stream.h>
|
||||
|
||||
#include <clapper/clapper-extractable.h>
|
||||
#include <clapper/clapper-playlistable.h>
|
||||
#include <clapper/clapper-reactable.h>
|
||||
|
||||
#include <clapper/clapper-functionalities-availability.h>
|
||||
|
@@ -39,6 +39,7 @@
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/tag/tag.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include "clapper-discoverer.h"
|
||||
@@ -128,8 +129,9 @@ _run_discovery (ClapperDiscoverer *self)
|
||||
ClapperMediaItem *item;
|
||||
ClapperQueue *queue;
|
||||
ClapperDiscovererDiscoveryMode discovery_mode;
|
||||
GstTagList *tags;
|
||||
const gchar *uri;
|
||||
gboolean success = FALSE;
|
||||
gboolean empty_tags, success = FALSE;
|
||||
|
||||
if (self->pending_items->len == 0) {
|
||||
GST_DEBUG_OBJECT (self, "No more pending items");
|
||||
@@ -157,6 +159,16 @@ _run_discovery (ClapperDiscoverer *self)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
tags = clapper_media_item_get_tags (item);
|
||||
empty_tags = gst_tag_list_is_empty (tags);
|
||||
gst_tag_list_unref (tags);
|
||||
|
||||
if (!empty_tags) {
|
||||
GST_DEBUG_OBJECT (self, "Queued %" GST_PTR_FORMAT
|
||||
" already has tags, ignoring discovery", item);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
uri = clapper_media_item_get_uri (item);
|
||||
GST_DEBUG_OBJECT (self, "Starting discovery of %"
|
||||
GST_PTR_FORMAT "(%s)", item, uri);
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gio/gio.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "../clapper-threaded-object.h"
|
||||
#include "../clapper-harvest.h"
|
||||
@@ -39,4 +40,7 @@ ClapperEnhancerDirector * clapper_enhancer_director_new (void);
|
||||
G_GNUC_INTERNAL
|
||||
ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GList *filtered_proxies, GUri *uri, GCancellable *cancellable, GError **error);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
GListStore * clapper_enhancer_director_parse (ClapperEnhancerDirector *director, GList *filtered_proxies, GUri *uri, GstBuffer *buffer, GCancellable *cancellable, GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -25,7 +25,9 @@
|
||||
#include "../clapper-cache-private.h"
|
||||
#include "../clapper-enhancer-proxy-private.h"
|
||||
#include "../clapper-extractable-private.h"
|
||||
#include "../clapper-playlistable-private.h"
|
||||
#include "../clapper-harvest-private.h"
|
||||
#include "../clapper-media-item.h"
|
||||
#include "../clapper-utils.h"
|
||||
#include "../../shared/clapper-shared-utils-private.h"
|
||||
|
||||
@@ -53,6 +55,7 @@ typedef struct
|
||||
ClapperEnhancerDirector *director;
|
||||
GList *filtered_proxies;
|
||||
GUri *uri;
|
||||
GstBuffer *buffer;
|
||||
GCancellable *cancellable;
|
||||
GError **error;
|
||||
} ClapperEnhancerDirectorData;
|
||||
@@ -99,13 +102,14 @@ clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data)
|
||||
|
||||
success = clapper_extractable_extract (extractable, data->uri,
|
||||
harvest, data->cancellable, data->error);
|
||||
gst_object_unref (extractable);
|
||||
g_object_unref (extractable);
|
||||
|
||||
/* We are done with extractable, but keep harvest and try to cache it */
|
||||
if (success) {
|
||||
if (!g_cancellable_is_cancelled (data->cancellable))
|
||||
if (!g_cancellable_is_cancelled (data->cancellable)) {
|
||||
clapper_harvest_set_enhancer_in_caps (harvest, proxy);
|
||||
clapper_harvest_export_to_cache (harvest, proxy, config, data->uri);
|
||||
|
||||
}
|
||||
gst_clear_structure (&config);
|
||||
break;
|
||||
}
|
||||
@@ -138,6 +142,101 @@ clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data)
|
||||
return harvest;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
clapper_enhancer_director_parse_in_thread (ClapperEnhancerDirectorData *data)
|
||||
{
|
||||
ClapperEnhancerDirector *self = data->director;
|
||||
GstMemory *mem;
|
||||
GstMapInfo info;
|
||||
GBytes *bytes;
|
||||
GList *el;
|
||||
GListStore *playlist = NULL;
|
||||
gboolean success = FALSE;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Parse start");
|
||||
|
||||
/* Cancelled during thread switching */
|
||||
if (g_cancellable_is_cancelled (data->cancellable))
|
||||
return NULL;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Enhancer proxies for buffer: %u",
|
||||
g_list_length (data->filtered_proxies));
|
||||
|
||||
mem = gst_buffer_peek_memory (data->buffer, 0);
|
||||
if (!mem || !gst_memory_map (mem, &info, GST_MAP_READ)) {
|
||||
g_set_error (data->error, GST_RESOURCE_ERROR,
|
||||
GST_RESOURCE_ERROR_FAILED, "Could not read playlist buffer data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bytes = g_bytes_new_static (info.data, info.size);
|
||||
|
||||
for (el = data->filtered_proxies; el; el = g_list_next (el)) {
|
||||
ClapperEnhancerProxy *proxy = CLAPPER_ENHANCER_PROXY_CAST (el->data);
|
||||
ClapperPlaylistable *playlistable = NULL;
|
||||
|
||||
if (g_cancellable_is_cancelled (data->cancellable)) // Check before loading enhancer
|
||||
break;
|
||||
|
||||
#if CLAPPER_WITH_ENHANCERS_LOADER
|
||||
playlistable = CLAPPER_PLAYLISTABLE_CAST (
|
||||
clapper_enhancers_loader_create_enhancer (proxy, CLAPPER_TYPE_PLAYLISTABLE));
|
||||
#endif
|
||||
|
||||
if (playlistable) {
|
||||
GstStructure *config;
|
||||
|
||||
if ((config = clapper_enhancer_proxy_make_current_config (proxy))) {
|
||||
clapper_enhancer_proxy_apply_config_to_enhancer (proxy, config, (GObject *) playlistable);
|
||||
gst_structure_free (config);
|
||||
}
|
||||
|
||||
if (g_cancellable_is_cancelled (data->cancellable)) { // Check before parse
|
||||
g_object_unref (playlistable);
|
||||
break;
|
||||
}
|
||||
|
||||
playlist = g_list_store_new (CLAPPER_TYPE_MEDIA_ITEM); // fresh list store for each iteration
|
||||
|
||||
success = clapper_playlistable_parse (playlistable, data->uri, bytes,
|
||||
playlist, data->cancellable, data->error);
|
||||
g_object_unref (playlistable);
|
||||
|
||||
/* We are done with playlistable, but keep playlist */
|
||||
if (success)
|
||||
break;
|
||||
|
||||
/* Cleanup to try again with next enhancer */
|
||||
g_clear_object (&playlist);
|
||||
}
|
||||
}
|
||||
|
||||
/* Unref bytes, then unmap their data */
|
||||
g_bytes_unref (bytes);
|
||||
gst_memory_unmap (mem, &info);
|
||||
|
||||
/* Cancelled during parsing */
|
||||
if (g_cancellable_is_cancelled (data->cancellable))
|
||||
success = FALSE;
|
||||
|
||||
if (!success) {
|
||||
g_clear_object (&playlist);
|
||||
|
||||
/* Ensure we have some error set on failure */
|
||||
if (*data->error == NULL) {
|
||||
const gchar *err_msg = (g_cancellable_is_cancelled (data->cancellable))
|
||||
? "Playlist parsing was cancelled"
|
||||
: "Could not parse playlist";
|
||||
g_set_error (data->error, GST_RESOURCE_ERROR,
|
||||
GST_RESOURCE_ERROR_FAILED, "%s", err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Parse finish");
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
static inline void
|
||||
_harvest_delete_if_expired (ClapperEnhancerDirector *self,
|
||||
ClapperEnhancerProxy *proxy, GFile *file, const gint64 epoch_now)
|
||||
@@ -331,6 +430,7 @@ clapper_enhancer_director_extract (ClapperEnhancerDirector *self,
|
||||
data->director = self;
|
||||
data->filtered_proxies = filtered_proxies;
|
||||
data->uri = uri;
|
||||
data->buffer = NULL;
|
||||
data->cancellable = cancellable;
|
||||
data->error = error;
|
||||
|
||||
@@ -348,6 +448,31 @@ clapper_enhancer_director_extract (ClapperEnhancerDirector *self,
|
||||
return harvest;
|
||||
}
|
||||
|
||||
GListStore *
|
||||
clapper_enhancer_director_parse (ClapperEnhancerDirector *self,
|
||||
GList *filtered_proxies, GUri *uri, GstBuffer *buffer,
|
||||
GCancellable *cancellable, GError **error)
|
||||
{
|
||||
ClapperEnhancerDirectorData *data = g_new (ClapperEnhancerDirectorData, 1);
|
||||
GMainContext *context;
|
||||
GListStore *playlist;
|
||||
|
||||
data->director = self;
|
||||
data->filtered_proxies = filtered_proxies;
|
||||
data->uri = uri;
|
||||
data->buffer = buffer;
|
||||
data->cancellable = cancellable;
|
||||
data->error = error;
|
||||
|
||||
context = clapper_threaded_object_get_context (CLAPPER_THREADED_OBJECT_CAST (self));
|
||||
|
||||
playlist = (GListStore *) clapper_shared_utils_context_invoke_sync_full (context,
|
||||
(GThreadFunc) clapper_enhancer_director_parse_in_thread,
|
||||
data, (GDestroyNotify) g_free);
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_enhancer_director_thread_start (ClapperThreadedObject *threaded_object)
|
||||
{
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@@ -21,16 +21,17 @@
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gstbin.h>
|
||||
|
||||
#include "clapper-uri-base-demux-private.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))
|
||||
#define CLAPPER_TYPE_HARVEST_URI_DEMUX (clapper_harvest_uri_demux_get_type())
|
||||
#define CLAPPER_HARVEST_URI_DEMUX_CAST(obj) ((ClapperHarvestUriDemux *)(obj))
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
G_DECLARE_FINAL_TYPE (ClapperUriListDemux, clapper_uri_list_demux, CLAPPER, URI_LIST_DEMUX, GstBin)
|
||||
G_DECLARE_FINAL_TYPE (ClapperHarvestUriDemux, clapper_harvest_uri_demux, CLAPPER, HARVEST_URI_DEMUX, ClapperUriBaseDemux)
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (clapperurilistdemux)
|
||||
GST_ELEMENT_REGISTER_DECLARE (clapperharvesturidemux)
|
||||
|
||||
G_END_DECLS
|
190
src/lib/clapper/gst/clapper-harvest-uri-demux.c
Normal file
190
src/lib/clapper/gst/clapper-harvest-uri-demux.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "clapper-harvest-uri-demux-private.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_harvest_uri_demux_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _ClapperHarvestUriDemux
|
||||
{
|
||||
ClapperUriBaseDemux parent;
|
||||
|
||||
GMutex lock;
|
||||
GstStructure *http_headers;
|
||||
};
|
||||
|
||||
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("text/x-uri, source=(string)clapper-harvest"));
|
||||
|
||||
#define parent_class clapper_harvest_uri_demux_parent_class
|
||||
G_DEFINE_TYPE (ClapperHarvestUriDemux, clapper_harvest_uri_demux, CLAPPER_TYPE_URI_BASE_DEMUX);
|
||||
GST_ELEMENT_REGISTER_DEFINE (clapperharvesturidemux, "clapperharvesturidemux",
|
||||
512, CLAPPER_TYPE_HARVEST_URI_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_harvest_uri_demux_deep_element_added (GstBin *bin, GstBin *sub_bin, GstElement *child)
|
||||
{
|
||||
if (GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_SOURCE)) {
|
||||
ClapperHarvestUriDemux *self = CLAPPER_HARVEST_URI_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
|
||||
clapper_harvest_uri_demux_process_buffer (ClapperUriBaseDemux *uri_bd,
|
||||
GstBuffer *buffer, GCancellable *cancellable)
|
||||
{
|
||||
GstMemory *mem = gst_buffer_peek_memory (buffer, 0);
|
||||
GstMapInfo info;
|
||||
gboolean success = FALSE;
|
||||
|
||||
if (mem && gst_memory_map (mem, &info, GST_MAP_READ)) {
|
||||
success = clapper_uri_base_demux_set_uri (uri_bd,
|
||||
(const gchar *) info.data, "clapperextractablesrc");
|
||||
gst_memory_unmap (mem, &info);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_harvest_uri_demux_handle_custom_event (ClapperUriBaseDemux *uri_bd, GstEvent *event)
|
||||
{
|
||||
const GstStructure *structure = gst_event_get_structure (event);
|
||||
|
||||
if (structure && gst_structure_has_name (structure, "http-headers")) {
|
||||
ClapperHarvestUriDemux *self = CLAPPER_HARVEST_URI_DEMUX_CAST (uri_bd);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_harvest_uri_demux_init (ClapperHarvestUriDemux *self)
|
||||
{
|
||||
g_mutex_init (&self->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_harvest_uri_demux_finalize (GObject *object)
|
||||
{
|
||||
ClapperHarvestUriDemux *self = CLAPPER_HARVEST_URI_DEMUX_CAST (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Finalize");
|
||||
|
||||
gst_clear_structure (&self->http_headers);
|
||||
g_mutex_clear (&self->lock);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_harvest_uri_demux_class_init (ClapperHarvestUriDemuxClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GstElementClass *gstelement_class = (GstElementClass *) klass;
|
||||
GstBinClass *gstbin_class = (GstBinClass *) klass;
|
||||
ClapperUriBaseDemuxClass *clapperuribd_class = (ClapperUriBaseDemuxClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperharvesturidemux", 0,
|
||||
"Clapper Harvest URI Demux");
|
||||
|
||||
gobject_class->finalize = clapper_harvest_uri_demux_finalize;
|
||||
|
||||
gstbin_class->deep_element_added = clapper_harvest_uri_demux_deep_element_added;
|
||||
|
||||
clapperuribd_class->process_buffer = clapper_harvest_uri_demux_process_buffer;
|
||||
clapperuribd_class->handle_custom_event = clapper_harvest_uri_demux_handle_custom_event;
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class, "Clapper Harvest URI Demux",
|
||||
"Demuxer", "A custom demuxer for harvested URI",
|
||||
"Rafał Dzięgiel <rafostar.github@gmail.com>");
|
||||
}
|
38
src/lib/clapper/gst/clapper-playlist-demux-private.h
Normal file
38
src/lib/clapper/gst/clapper-playlist-demux-private.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "clapper-uri-base-demux-private.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_TYPE_PLAYLIST_DEMUX (clapper_playlist_demux_get_type())
|
||||
#define CLAPPER_PLAYLIST_DEMUX_CAST(obj) ((ClapperPlaylistDemux *)(obj))
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
G_DECLARE_FINAL_TYPE (ClapperPlaylistDemux, clapper_playlist_demux, CLAPPER, PLAYLIST_DEMUX, ClapperUriBaseDemux)
|
||||
|
||||
GST_TYPE_FIND_REGISTER_DECLARE (clapperplaylistdemux)
|
||||
GST_ELEMENT_REGISTER_DECLARE (clapperplaylistdemux)
|
||||
|
||||
G_END_DECLS
|
514
src/lib/clapper/gst/clapper-playlist-demux.c
Normal file
514
src/lib/clapper/gst/clapper-playlist-demux.c
Normal file
@@ -0,0 +1,514 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "clapper-playlist-demux-private.h"
|
||||
#include "clapper-enhancer-director-private.h"
|
||||
|
||||
#include "../clapper-basic-functions.h"
|
||||
#include "../clapper-enhancer-proxy.h"
|
||||
#include "../clapper-enhancer-proxy-list.h"
|
||||
#include "../clapper-media-item.h"
|
||||
#include "../clapper-playlistable.h"
|
||||
|
||||
#define CLAPPER_PLAYLIST_MEDIA_TYPE "application/clapper-playlist"
|
||||
#define CLAPPER_CLAPS_MEDIA_TYPE "text/clapper-claps"
|
||||
#define URI_LIST_MEDIA_TYPE "text/uri-list"
|
||||
#define DATA_CHUNK_SIZE 4096
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_playlist_demux_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _ClapperPlaylistDemux
|
||||
{
|
||||
ClapperUriBaseDemux parent;
|
||||
|
||||
GstCaps *caps;
|
||||
|
||||
ClapperEnhancerDirector *director;
|
||||
ClapperEnhancerProxyList *enhancer_proxies;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_ENHANCER_PROXIES,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (CLAPPER_PLAYLIST_MEDIA_TYPE ";" CLAPPER_CLAPS_MEDIA_TYPE ";" URI_LIST_MEDIA_TYPE));
|
||||
|
||||
static GstStaticCaps clapper_playlist_caps = GST_STATIC_CAPS (CLAPPER_PLAYLIST_MEDIA_TYPE);
|
||||
static GstStaticCaps clapper_claps_caps = GST_STATIC_CAPS (CLAPPER_CLAPS_MEDIA_TYPE);
|
||||
|
||||
static void
|
||||
clapper_playlist_type_find (GstTypeFind *tf, ClapperEnhancerProxy *proxy)
|
||||
{
|
||||
const gchar *prefix, *contains, *regex, *module_name;
|
||||
|
||||
if (!clapper_enhancer_proxy_get_target_creation_allowed (proxy))
|
||||
return;
|
||||
|
||||
if ((prefix = clapper_enhancer_proxy_get_extra_data (proxy, "X-Data-Prefix"))) {
|
||||
size_t len = strlen (prefix);
|
||||
const gchar *data = (const gchar *) gst_type_find_peek (tf, 0, (guint) len);
|
||||
|
||||
if (!data || memcmp (data, prefix, len) != 0)
|
||||
return;
|
||||
}
|
||||
|
||||
contains = clapper_enhancer_proxy_get_extra_data (proxy, "X-Data-Contains");
|
||||
regex = clapper_enhancer_proxy_get_extra_data (proxy, "X-Data-Regex");
|
||||
|
||||
if (contains || regex) {
|
||||
const gchar *data;
|
||||
guint data_size = DATA_CHUNK_SIZE;
|
||||
|
||||
if (!(data = (const gchar *) gst_type_find_peek (tf, 0, data_size))) {
|
||||
guint64 data_len = gst_type_find_get_length (tf);
|
||||
|
||||
if (G_LIKELY (data_len < DATA_CHUNK_SIZE)) { // likely, since whole chunk read failed
|
||||
data_size = (guint) data_len;
|
||||
data = (const gchar *) gst_type_find_peek (tf, 0, data_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (G_UNLIKELY (data == NULL)) {
|
||||
GST_ERROR ("Could not read data!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (contains && !g_strstr_len (data, data_size, contains))
|
||||
return;
|
||||
|
||||
if (regex) {
|
||||
GRegex *reg;
|
||||
GError *error = NULL;
|
||||
gboolean matched;
|
||||
|
||||
if (!(reg = g_regex_new (regex, 0, 0, &error))) {
|
||||
GST_ERROR ("Could not compile regex, reason: %s", error->message);
|
||||
g_error_free (error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
matched = g_regex_match_full (reg, data, (gssize) data_size, 0, 0, NULL, NULL);
|
||||
g_regex_unref (reg);
|
||||
|
||||
if (!matched)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
module_name = clapper_enhancer_proxy_get_module_name (proxy);
|
||||
GST_INFO ("Suggesting likely type: " CLAPPER_PLAYLIST_MEDIA_TYPE
|
||||
", enhancer: %s", module_name);
|
||||
|
||||
gst_type_find_suggest_simple (tf, GST_TYPE_FIND_LIKELY,
|
||||
CLAPPER_PLAYLIST_MEDIA_TYPE, "enhancer", G_TYPE_STRING, module_name, NULL);
|
||||
}
|
||||
|
||||
/* Finds text file of full file paths. Claps file might also use URIs,
|
||||
* but in that case lets GStreamer built-in type finders find that as
|
||||
* "text/uri-list" and we will handle it with this element too. */
|
||||
static void
|
||||
clapper_claps_type_find (GstTypeFind *tf, gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
const guint8 *data;
|
||||
|
||||
if ((data = gst_type_find_peek (tf, 0, 3))) {
|
||||
gboolean possible;
|
||||
|
||||
/* Linux file path */
|
||||
possible = (data[0] == '/' && g_ascii_isalnum (data[1]));
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
/* Windows file path ("C:\..." or "D:/...") */
|
||||
if (!possible)
|
||||
possible = (g_ascii_isalpha (data[0]) && data[1] == ':' && (data[2] == '\\' || data[2] == '/'));
|
||||
|
||||
/* Windows UNC Path */
|
||||
if (!possible)
|
||||
possible = (data[0] == '\\' && data[1] == '\\' && g_ascii_isalnum (data[2]));
|
||||
#endif
|
||||
|
||||
if (possible) {
|
||||
GST_INFO ("Suggesting possible type: " CLAPPER_CLAPS_MEDIA_TYPE);
|
||||
gst_type_find_suggest_empty_simple (tf, GST_TYPE_FIND_POSSIBLE, CLAPPER_CLAPS_MEDIA_TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
type_find_register (GstPlugin *plugin)
|
||||
{
|
||||
ClapperEnhancerProxyList *global_proxies = clapper_get_global_enhancer_proxies ();
|
||||
GstCaps *reg_caps;
|
||||
guint i, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (global_proxies);
|
||||
gboolean res;
|
||||
|
||||
reg_caps = gst_static_caps_get (&clapper_claps_caps);
|
||||
res = gst_type_find_register (plugin, "clapper-claps",
|
||||
GST_RANK_MARGINAL + 1, (GstTypeFindFunction) clapper_claps_type_find,
|
||||
"claps", reg_caps, NULL, NULL);
|
||||
gst_clear_caps (®_caps);
|
||||
|
||||
for (i = 0; i < n_proxies; ++i) {
|
||||
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (global_proxies, i);
|
||||
|
||||
if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_PLAYLISTABLE)
|
||||
&& (clapper_enhancer_proxy_get_extra_data (proxy, "X-Data-Prefix")
|
||||
|| clapper_enhancer_proxy_get_extra_data (proxy, "X-Data-Contains")
|
||||
|| clapper_enhancer_proxy_get_extra_data (proxy, "X-Data-Regex"))) {
|
||||
if (!reg_caps)
|
||||
reg_caps = gst_static_caps_get (&clapper_playlist_caps);
|
||||
|
||||
res |= gst_type_find_register (plugin, clapper_enhancer_proxy_get_module_name (proxy),
|
||||
GST_RANK_MARGINAL + 1, (GstTypeFindFunction) clapper_playlist_type_find,
|
||||
NULL, reg_caps, proxy, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
gst_clear_caps (®_caps);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#define parent_class clapper_playlist_demux_parent_class
|
||||
G_DEFINE_TYPE (ClapperPlaylistDemux, clapper_playlist_demux, CLAPPER_TYPE_URI_BASE_DEMUX);
|
||||
GST_TYPE_FIND_REGISTER_DEFINE_CUSTOM (clapperplaylistdemux, type_find_register);
|
||||
GST_ELEMENT_REGISTER_DEFINE (clapperplaylistdemux, "clapperplaylistdemux",
|
||||
512, CLAPPER_TYPE_PLAYLIST_DEMUX);
|
||||
|
||||
static GListStore *
|
||||
_parse_uri_list (ClapperPlaylistDemux *self, GUri *uri, GstBuffer *buffer,
|
||||
GCancellable *cancellable, GError **error)
|
||||
{
|
||||
GListStore *playlist;
|
||||
GstMemory *mem;
|
||||
GstMapInfo info;
|
||||
const gchar *ptr, *end;
|
||||
|
||||
mem = gst_buffer_peek_memory (buffer, 0);
|
||||
if (!mem || !gst_memory_map (mem, &info, GST_MAP_READ)) {
|
||||
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
|
||||
"Could not read URI list buffer data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
playlist = g_list_store_new (CLAPPER_TYPE_MEDIA_ITEM);
|
||||
ptr = (gchar *) info.data;
|
||||
end = ptr + info.size;
|
||||
|
||||
while (ptr < end) {
|
||||
ClapperMediaItem *item = NULL;
|
||||
const gchar *nl = memchr (ptr, '\n', end - ptr);
|
||||
gsize len = nl ? nl - ptr : end - ptr;
|
||||
gchar *line;
|
||||
|
||||
if (g_cancellable_is_cancelled (cancellable))
|
||||
break;
|
||||
|
||||
line = g_strndup (ptr, len);
|
||||
GST_DEBUG_OBJECT (self, "Parsing line: %s", line);
|
||||
|
||||
if (gst_uri_is_valid (line)) {
|
||||
GST_DEBUG_OBJECT (self, "Found URI: %s", line);
|
||||
item = clapper_media_item_new (line);
|
||||
} else {
|
||||
gchar *base_uri, *res_uri;
|
||||
|
||||
base_uri = g_uri_to_string (uri);
|
||||
res_uri = g_uri_resolve_relative (base_uri, line, G_URI_FLAGS_ENCODED, error);
|
||||
g_free (base_uri);
|
||||
|
||||
if (res_uri) {
|
||||
GST_DEBUG_OBJECT (self, "Resolved URI: %s", res_uri);
|
||||
item = clapper_media_item_new (res_uri);
|
||||
g_free (res_uri);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (line);
|
||||
|
||||
if (G_UNLIKELY (*error != NULL)) {
|
||||
g_clear_object (&playlist);
|
||||
break;
|
||||
}
|
||||
|
||||
if (G_LIKELY (item != NULL))
|
||||
g_list_store_append (playlist, (GObject *) item);
|
||||
|
||||
/* Advance to the next line */
|
||||
ptr = nl ? (nl + 1) : end;
|
||||
}
|
||||
|
||||
gst_memory_unmap (mem, &info);
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_caps_have_media_type (GstCaps *caps, const gchar *media_type)
|
||||
{
|
||||
GstStructure *structure;
|
||||
gboolean is_media_type = FALSE;
|
||||
|
||||
if (caps && (structure = gst_caps_get_structure (caps, 0)))
|
||||
is_media_type = gst_structure_has_name (structure, media_type);
|
||||
|
||||
return is_media_type;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_playlist_demux_handle_caps (ClapperUriBaseDemux *uri_bd, GstCaps *caps)
|
||||
{
|
||||
ClapperPlaylistDemux *self = CLAPPER_PLAYLIST_DEMUX_CAST (uri_bd);
|
||||
|
||||
gst_caps_replace (&self->caps, caps);
|
||||
GST_DEBUG_OBJECT (self, "Set caps: %" GST_PTR_FORMAT, caps);
|
||||
}
|
||||
|
||||
static GList *
|
||||
_filter_playlistables (ClapperPlaylistDemux *self, GstCaps *caps, ClapperEnhancerProxyList *proxies)
|
||||
{
|
||||
GList *sublist = NULL;
|
||||
GstStructure *structure;
|
||||
ClapperEnhancerProxy *proxy;
|
||||
|
||||
if (caps && (structure = gst_caps_get_structure (self->caps, 0))) {
|
||||
const gchar *module_name = gst_structure_get_string (structure, "enhancer");
|
||||
|
||||
if (module_name && (proxy = clapper_enhancer_proxy_list_get_proxy_by_module (proxies, module_name)))
|
||||
sublist = g_list_append (sublist, proxy);
|
||||
}
|
||||
|
||||
return sublist;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
_handle_playlist (ClapperPlaylistDemux *self, GListStore *playlist, GCancellable *cancellable)
|
||||
{
|
||||
ClapperMediaItem *item = g_list_model_get_item (G_LIST_MODEL (playlist), 0);
|
||||
const gchar *uri;
|
||||
gboolean success;
|
||||
|
||||
if (G_UNLIKELY (item == NULL)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
|
||||
("This playlist appears to be empty"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
uri = clapper_media_item_get_uri (item);
|
||||
success = clapper_uri_base_demux_set_uri (CLAPPER_URI_BASE_DEMUX_CAST (self), uri, NULL);
|
||||
gst_object_unref (item);
|
||||
|
||||
if (G_UNLIKELY (!success)) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
|
||||
("Resolved item URI was rejected"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!g_cancellable_is_cancelled (cancellable)) {
|
||||
GstStructure *structure = gst_structure_new ("ClapperPlaylistParsed",
|
||||
"playlist", G_TYPE_LIST_STORE, playlist, NULL);
|
||||
|
||||
gst_element_post_message (GST_ELEMENT_CAST (self),
|
||||
gst_message_new_element (GST_OBJECT_CAST (self), structure));
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_playlist_demux_process_buffer (ClapperUriBaseDemux *uri_bd,
|
||||
GstBuffer *buffer, GCancellable *cancellable)
|
||||
{
|
||||
ClapperPlaylistDemux *self = CLAPPER_PLAYLIST_DEMUX_CAST (uri_bd);
|
||||
GstPad *sink_pad;
|
||||
GstQuery *query;
|
||||
GUri *uri = NULL;
|
||||
GListStore *playlist;
|
||||
GError *error = NULL;
|
||||
gboolean handled;
|
||||
|
||||
sink_pad = gst_element_get_static_pad (GST_ELEMENT_CAST (self), "sink");
|
||||
query = gst_query_new_uri ();
|
||||
|
||||
if (gst_pad_peer_query (sink_pad, query)) {
|
||||
gchar *query_uri;
|
||||
|
||||
gst_query_parse_uri (query, &query_uri);
|
||||
GST_DEBUG_OBJECT (self, "Source URI: %s", query_uri);
|
||||
|
||||
if (query_uri) {
|
||||
uri = g_uri_parse (query_uri, G_URI_FLAGS_ENCODED, NULL);
|
||||
g_free (query_uri);
|
||||
}
|
||||
}
|
||||
|
||||
gst_query_unref (query);
|
||||
gst_object_unref (sink_pad);
|
||||
|
||||
if (G_UNLIKELY (uri == NULL)) {
|
||||
GST_ERROR_OBJECT (self, "Could not query source URI");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (_caps_have_media_type (self->caps, CLAPPER_PLAYLIST_MEDIA_TYPE)) {
|
||||
ClapperEnhancerProxyList *proxies;
|
||||
GList *filtered_proxies;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
if (G_LIKELY (self->enhancer_proxies != NULL)) {
|
||||
GST_INFO_OBJECT (self, "Using enhancer proxies: %" GST_PTR_FORMAT, self->enhancer_proxies);
|
||||
proxies = gst_object_ref (self->enhancer_proxies);
|
||||
} else {
|
||||
/* Compat for old ClapperDiscoverer feature that does not set this property */
|
||||
GST_WARNING_OBJECT (self, "Falling back to using global enhancer proxy list!");
|
||||
proxies = gst_object_ref (clapper_get_global_enhancer_proxies ());
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (!self->director)
|
||||
self->director = clapper_enhancer_director_new ();
|
||||
|
||||
filtered_proxies = _filter_playlistables (self, self->caps, proxies);
|
||||
gst_object_unref (proxies);
|
||||
|
||||
playlist = clapper_enhancer_director_parse (self->director,
|
||||
filtered_proxies, uri, buffer, cancellable, &error);
|
||||
|
||||
g_clear_list (&filtered_proxies, gst_object_unref);
|
||||
} else if (_caps_have_media_type (self->caps, URI_LIST_MEDIA_TYPE)
|
||||
|| _caps_have_media_type (self->caps, CLAPPER_CLAPS_MEDIA_TYPE)) {
|
||||
playlist = _parse_uri_list (self, uri, buffer, cancellable, &error);
|
||||
} else { // Should never happen
|
||||
playlist = NULL;
|
||||
error = g_error_new (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
|
||||
"Unsupported media type in caps");
|
||||
}
|
||||
|
||||
g_uri_unref (uri);
|
||||
|
||||
if (!playlist) {
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
|
||||
("%s", error->message), (NULL));
|
||||
g_clear_error (&error);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
handled = _handle_playlist (self, playlist, cancellable);
|
||||
g_object_unref (playlist);
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_playlist_demux_set_enhancer_proxies (ClapperPlaylistDemux *self,
|
||||
ClapperEnhancerProxyList *enhancer_proxies)
|
||||
{
|
||||
GST_OBJECT_LOCK (self);
|
||||
gst_object_replace ((GstObject **) &self->enhancer_proxies,
|
||||
GST_OBJECT_CAST (enhancer_proxies));
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_playlist_demux_init (ClapperPlaylistDemux *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_playlist_demux_dispose (GObject *object)
|
||||
{
|
||||
ClapperPlaylistDemux *self = CLAPPER_PLAYLIST_DEMUX_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_playlist_demux_finalize (GObject *object)
|
||||
{
|
||||
ClapperPlaylistDemux *self = CLAPPER_PLAYLIST_DEMUX_CAST (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Finalize");
|
||||
|
||||
gst_clear_caps (&self->caps);
|
||||
gst_clear_object (&self->enhancer_proxies);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_playlist_demux_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperPlaylistDemux *self = CLAPPER_PLAYLIST_DEMUX_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ENHANCER_PROXIES:
|
||||
clapper_playlist_demux_set_enhancer_proxies (self, g_value_get_object (value));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_playlist_demux_class_init (ClapperPlaylistDemuxClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GstElementClass *gstelement_class = (GstElementClass *) klass;
|
||||
ClapperUriBaseDemuxClass *clapperuribd_class = (ClapperUriBaseDemuxClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperplaylistdemux", 0,
|
||||
"Clapper Playlist Demux");
|
||||
|
||||
gobject_class->set_property = clapper_playlist_demux_set_property;
|
||||
gobject_class->dispose = clapper_playlist_demux_dispose;
|
||||
gobject_class->finalize = clapper_playlist_demux_finalize;
|
||||
|
||||
clapperuribd_class->handle_caps = clapper_playlist_demux_handle_caps;
|
||||
clapperuribd_class->process_buffer = clapper_playlist_demux_process_buffer;
|
||||
|
||||
param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies",
|
||||
NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST,
|
||||
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class, "Clapper Playlist Demux",
|
||||
"Demuxer", "A custom demuxer for playlists",
|
||||
"Rafał Dzięgiel <rafostar.github@gmail.com>");
|
||||
}
|
@@ -24,15 +24,17 @@
|
||||
#include "../clapper-enhancer-proxy.h"
|
||||
#include "../clapper-enhancer-proxy-list-private.h"
|
||||
#include "../clapper-extractable.h"
|
||||
#include "../clapper-playlistable.h"
|
||||
|
||||
#include "clapper-plugin-private.h"
|
||||
#include "clapper-extractable-src-private.h"
|
||||
#include "clapper-uri-list-demux-private.h"
|
||||
#include "clapper-harvest-uri-demux-private.h"
|
||||
#include "clapper-playlist-demux-private.h"
|
||||
|
||||
gboolean
|
||||
clapper_gst_plugin_init (GstPlugin *plugin)
|
||||
{
|
||||
gboolean res = FALSE;
|
||||
gboolean res = TRUE;
|
||||
ClapperEnhancerProxyList *global_proxies;
|
||||
|
||||
gst_plugin_add_dependency_simple (plugin,
|
||||
@@ -42,10 +44,14 @@ clapper_gst_plugin_init (GstPlugin *plugin)
|
||||
global_proxies = clapper_get_global_enhancer_proxies ();
|
||||
|
||||
/* Avoid registering an URI handler without schemes */
|
||||
if (clapper_enhancer_proxy_list_has_proxy_with_interface (global_proxies, CLAPPER_TYPE_EXTRACTABLE))
|
||||
res |= GST_ELEMENT_REGISTER (clapperextractablesrc, plugin);
|
||||
if (clapper_enhancer_proxy_list_has_proxy_with_interface (global_proxies, CLAPPER_TYPE_EXTRACTABLE)) {
|
||||
res &= (GST_ELEMENT_REGISTER (clapperextractablesrc, plugin)
|
||||
&& GST_ELEMENT_REGISTER (clapperharvesturidemux, plugin));
|
||||
}
|
||||
|
||||
res |= GST_ELEMENT_REGISTER (clapperurilistdemux, plugin);
|
||||
/* Type find will only register if there are playlistable enhancers */
|
||||
if (GST_TYPE_FIND_REGISTER (clapperplaylistdemux, plugin))
|
||||
GST_ELEMENT_REGISTER (clapperplaylistdemux, plugin);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
48
src/lib/clapper/gst/clapper-uri-base-demux-private.h
Normal file
48
src/lib/clapper/gst/clapper-uri-base-demux-private.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/* 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, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <gio/gio.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gstbin.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CLAPPER_TYPE_URI_BASE_DEMUX (clapper_uri_base_demux_get_type())
|
||||
#define CLAPPER_URI_BASE_DEMUX_CAST(obj) ((ClapperUriBaseDemux *)(obj))
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
G_DECLARE_DERIVABLE_TYPE (ClapperUriBaseDemux, clapper_uri_base_demux, CLAPPER, URI_BASE_DEMUX, GstBin)
|
||||
|
||||
struct _ClapperUriBaseDemuxClass
|
||||
{
|
||||
GstBinClass parent_class;
|
||||
|
||||
gboolean (* process_buffer) (ClapperUriBaseDemux *uri_bd, GstBuffer *buffer, GCancellable *cancellable);
|
||||
|
||||
void (* handle_caps) (ClapperUriBaseDemux *uri_bd, GstCaps *caps);
|
||||
|
||||
void (* handle_custom_event) (ClapperUriBaseDemux *uri_bd, GstEvent *event);
|
||||
};
|
||||
|
||||
gboolean clapper_uri_base_demux_set_uri (ClapperUriBaseDemux *uri_bd, const gchar *uri, const gchar *blacklisted_el);
|
||||
|
||||
G_END_DECLS
|
408
src/lib/clapper/gst/clapper-uri-base-demux.c
Normal file
408
src/lib/clapper/gst/clapper-uri-base-demux.c
Normal file
@@ -0,0 +1,408 @@
|
||||
/* 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, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gst/base/gstadapter.h>
|
||||
|
||||
#include "clapper-uri-base-demux-private.h"
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_uri_base_demux_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
typedef struct _ClapperUriBaseDemuxPrivate ClapperUriBaseDemuxPrivate;
|
||||
|
||||
struct _ClapperUriBaseDemuxPrivate
|
||||
{
|
||||
GstAdapter *input_adapter;
|
||||
|
||||
GstElement *uri_handler;
|
||||
GstElement *typefind;
|
||||
|
||||
GstPad *typefind_src;
|
||||
|
||||
GCancellable *cancellable;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const gchar *search_proto;
|
||||
const gchar *blacklisted_el;
|
||||
} ClapperUriBaseDemuxFilterData;
|
||||
|
||||
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_SOMETIMES,
|
||||
GST_STATIC_CAPS_ANY);
|
||||
|
||||
#define parent_class clapper_uri_base_demux_parent_class
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (ClapperUriBaseDemux, clapper_uri_base_demux, GST_TYPE_BIN);
|
||||
|
||||
static gboolean
|
||||
remove_sometimes_pad_cb (GstElement *element, GstPad *pad, ClapperUriBaseDemux *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_base_demux_reset (ClapperUriBaseDemux *self)
|
||||
{
|
||||
ClapperUriBaseDemuxPrivate *priv = clapper_uri_base_demux_get_instance_private (self);
|
||||
GstElement *element = GST_ELEMENT_CAST (self);
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
GST_LOG_OBJECT (self, "Resetting cancellable");
|
||||
|
||||
g_cancellable_cancel (priv->cancellable);
|
||||
g_object_unref (priv->cancellable);
|
||||
priv->cancellable = g_cancellable_new ();
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
gst_element_foreach_pad (element, (GstElementForeachPadFunc) remove_sometimes_pad_cb, NULL);
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
clapper_uri_base_demux_change_state (GstElement *element, GstStateChange transition)
|
||||
{
|
||||
ClapperUriBaseDemux *self = CLAPPER_URI_BASE_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_base_demux_reset (self);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_feature_filter (GstPluginFeature *feature, ClapperUriBaseDemuxFilterData *filter_data)
|
||||
{
|
||||
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 || g_strcmp0 (feature_name, filter_data->blacklisted_el) == 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], filter_data->search_proto) == 0)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GstElement *
|
||||
_make_handler_for_uri (ClapperUriBaseDemux *self, const gchar *uri, const gchar *blacklisted_el)
|
||||
{
|
||||
GstElement *element = NULL;
|
||||
GList *factories, *f;
|
||||
ClapperUriBaseDemuxFilterData filter_data;
|
||||
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);
|
||||
|
||||
filter_data.search_proto = protocol;
|
||||
filter_data.blacklisted_el = blacklisted_el;
|
||||
|
||||
factories = gst_registry_feature_filter (gst_registry_get (),
|
||||
(GstPluginFeatureFilter) _feature_filter, FALSE, &filter_data);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
gboolean
|
||||
clapper_uri_base_demux_set_uri (ClapperUriBaseDemux *self, const gchar *uri, const gchar *blacklisted_el)
|
||||
{
|
||||
ClapperUriBaseDemuxPrivate *priv = clapper_uri_base_demux_get_instance_private (self);
|
||||
GstPad *uri_handler_src, *typefind_sink, *src_ghostpad;
|
||||
GstPadLinkReturn pad_link_ret;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Stream URI: %s", uri);
|
||||
|
||||
if (priv->uri_handler) {
|
||||
GST_DEBUG_OBJECT (self, "Trying to reuse existing URI handler");
|
||||
|
||||
if (gst_uri_handler_set_uri (GST_URI_HANDLER (priv->uri_handler), uri, NULL)) {
|
||||
GST_DEBUG_OBJECT (self, "Reused existing URI handler");
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (self, "Could not reuse existing URI handler");
|
||||
|
||||
if (priv->typefind_src) {
|
||||
gst_element_remove_pad (GST_ELEMENT_CAST (self), priv->typefind_src);
|
||||
gst_clear_object (&priv->typefind_src);
|
||||
}
|
||||
|
||||
gst_bin_remove (GST_BIN_CAST (self), priv->uri_handler);
|
||||
gst_bin_remove (GST_BIN_CAST (self), priv->typefind);
|
||||
|
||||
priv->uri_handler = NULL;
|
||||
priv->typefind = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!priv->uri_handler) {
|
||||
GST_DEBUG_OBJECT (self, "Creating new URI handler element");
|
||||
|
||||
priv->uri_handler = _make_handler_for_uri (self, uri, blacklisted_el);
|
||||
|
||||
if (G_UNLIKELY (!priv->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", uri), (NULL));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gst_bin_add (GST_BIN_CAST (self), priv->uri_handler);
|
||||
|
||||
priv->typefind = gst_element_factory_make ("typefind", NULL);
|
||||
gst_bin_add (GST_BIN_CAST (self), priv->typefind);
|
||||
|
||||
uri_handler_src = gst_element_get_static_pad (priv->uri_handler, "src");
|
||||
typefind_sink = gst_element_get_static_pad (priv->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);
|
||||
|
||||
priv->typefind_src = gst_element_get_static_pad (priv->typefind, "src");
|
||||
|
||||
src_ghostpad = gst_ghost_pad_new_from_template ("src", priv->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_element_sync_state_with_parent (priv->typefind);
|
||||
gst_element_sync_state_with_parent (priv->uri_handler);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_uri_base_demux_sink_event (GstPad *pad, GstObject *parent, GstEvent *event)
|
||||
{
|
||||
ClapperUriBaseDemux *self = CLAPPER_URI_BASE_DEMUX_CAST (parent);
|
||||
|
||||
switch (GST_EVENT_TYPE (event)) {
|
||||
case GST_EVENT_CAPS:{
|
||||
ClapperUriBaseDemuxClass *uri_bd_class = CLAPPER_URI_BASE_DEMUX_GET_CLASS (self);
|
||||
|
||||
if (uri_bd_class->handle_caps) {
|
||||
GstCaps *caps;
|
||||
|
||||
gst_event_parse_caps (event, &caps);
|
||||
|
||||
if (gst_caps_is_fixed (caps))
|
||||
uri_bd_class->handle_caps (self, caps);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_EVENT_EOS:{
|
||||
ClapperUriBaseDemuxPrivate *priv = clapper_uri_base_demux_get_instance_private (self);
|
||||
GCancellable *cancellable;
|
||||
GstBuffer *buffer;
|
||||
gsize size;
|
||||
gboolean success;
|
||||
|
||||
size = gst_adapter_available (priv->input_adapter);
|
||||
|
||||
if (size == 0) {
|
||||
GST_WARNING_OBJECT (self, "Received EOS without URI data");
|
||||
break;
|
||||
}
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
cancellable = g_object_ref (priv->cancellable);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
buffer = gst_adapter_take_buffer (priv->input_adapter, size);
|
||||
success = CLAPPER_URI_BASE_DEMUX_GET_CLASS (self)->process_buffer (self, buffer, cancellable);
|
||||
gst_buffer_unref (buffer);
|
||||
g_object_unref (cancellable);
|
||||
|
||||
if (success) {
|
||||
gst_event_unref (event);
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_EVENT_CUSTOM_DOWNSTREAM_STICKY:{
|
||||
ClapperUriBaseDemuxClass *uri_bd_class = CLAPPER_URI_BASE_DEMUX_GET_CLASS (self);
|
||||
|
||||
if (uri_bd_class->handle_custom_event)
|
||||
uri_bd_class->handle_custom_event (self, event);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return gst_pad_event_default (pad, parent, event);
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
clapper_uri_base_demux_sink_chain (GstPad *pad, GstObject *parent, GstBuffer *buffer)
|
||||
{
|
||||
ClapperUriBaseDemux *self = CLAPPER_URI_BASE_DEMUX_CAST (parent);
|
||||
ClapperUriBaseDemuxPrivate *priv = clapper_uri_base_demux_get_instance_private (self);
|
||||
|
||||
gst_adapter_push (priv->input_adapter, buffer);
|
||||
GST_DEBUG_OBJECT (self, "Received buffer, total collected: %" G_GSIZE_FORMAT " bytes",
|
||||
gst_adapter_available (priv->input_adapter));
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_uri_base_demux_init (ClapperUriBaseDemux *self)
|
||||
{
|
||||
ClapperUriBaseDemuxPrivate *priv = clapper_uri_base_demux_get_instance_private (self);
|
||||
|
||||
priv->input_adapter = gst_adapter_new ();
|
||||
priv->cancellable = g_cancellable_new ();
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_uri_base_demux_constructed (GObject *object)
|
||||
{
|
||||
ClapperUriBaseDemux *self = CLAPPER_URI_BASE_DEMUX_CAST (object);
|
||||
GstPad *sink_pad;
|
||||
|
||||
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_base_demux_sink_event));
|
||||
gst_pad_set_chain_function (sink_pad,
|
||||
GST_DEBUG_FUNCPTR (clapper_uri_base_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");
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_uri_base_demux_finalize (GObject *object)
|
||||
{
|
||||
ClapperUriBaseDemux *self = CLAPPER_URI_BASE_DEMUX_CAST (object);
|
||||
ClapperUriBaseDemuxPrivate *priv = clapper_uri_base_demux_get_instance_private (self);
|
||||
|
||||
g_object_unref (priv->input_adapter);
|
||||
g_object_unref (priv->cancellable);
|
||||
gst_clear_object (&priv->typefind_src);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_uri_base_demux_class_init (ClapperUriBaseDemuxClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GstElementClass *gstelement_class = (GstElementClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperuribasedemux", 0,
|
||||
"Clapper URI Base Demux");
|
||||
|
||||
gobject_class->constructed = clapper_uri_base_demux_constructed;
|
||||
gobject_class->finalize = clapper_uri_base_demux_finalize;
|
||||
|
||||
gstelement_class->change_state = clapper_uri_base_demux_change_state;
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
|
||||
}
|
@@ -1,461 +0,0 @@
|
||||
/* Clapper Playback Library
|
||||
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see
|
||||
* <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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, "clapperextractablesrc") == 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>");
|
||||
}
|
@@ -119,6 +119,7 @@ clapper_headers = [
|
||||
'clapper-marker.h',
|
||||
'clapper-media-item.h',
|
||||
'clapper-player.h',
|
||||
'clapper-playlistable.h',
|
||||
'clapper-queue.h',
|
||||
'clapper-reactable.h',
|
||||
'clapper-stream.h',
|
||||
@@ -147,6 +148,7 @@ clapper_sources = [
|
||||
'clapper-media-item.c',
|
||||
'clapper-playbin-bus.c',
|
||||
'clapper-player.c',
|
||||
'clapper-playlistable.c',
|
||||
'clapper-queue.c',
|
||||
'clapper-reactable.c',
|
||||
'clapper-reactables-manager.c',
|
||||
@@ -156,11 +158,14 @@ clapper_sources = [
|
||||
'clapper-threaded-object.c',
|
||||
'clapper-timeline.c',
|
||||
'clapper-utils.c',
|
||||
'clapper-version.c',
|
||||
'clapper-video-stream.c',
|
||||
'gst/clapper-plugin.c',
|
||||
'gst/clapper-extractable-src.c',
|
||||
'gst/clapper-enhancer-director.c',
|
||||
'gst/clapper-uri-list-demux.c',
|
||||
'gst/clapper-uri-base-demux.c',
|
||||
'gst/clapper-harvest-uri-demux.c',
|
||||
'gst/clapper-playlist-demux.c',
|
||||
'../shared/clapper-shared-utils.c',
|
||||
]
|
||||
clapper_c_args = [
|
||||
|
@@ -411,24 +411,18 @@ gst_clapper_sink_navigation_send_event (GstNavigation *navigation,
|
||||
{
|
||||
GstClapperSink *sink = GST_CLAPPER_SINK_CAST (navigation);
|
||||
GstEvent *event;
|
||||
GstPad *pad;
|
||||
|
||||
GST_TRACE_OBJECT (sink, "Navigation event: %" GST_PTR_FORMAT, structure);
|
||||
event = gst_event_new_navigation (structure);
|
||||
event = gst_event_new_navigation (structure); // transfer full
|
||||
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
|
||||
|
||||
if (G_LIKELY (event)) {
|
||||
GstPad *pad;
|
||||
if (G_LIKELY (pad != NULL)) {
|
||||
if (!gst_pad_send_event (pad, event)) // transfer full
|
||||
GST_LOG_OBJECT (sink, "Upstream did not handle navigation event");
|
||||
|
||||
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
|
||||
|
||||
if (G_LIKELY (pad)) {
|
||||
if (!gst_pad_send_event (pad, gst_event_ref (event))) {
|
||||
/* If upstream didn't handle the event we'll post a message with it
|
||||
* for the application in case it wants to do something with it */
|
||||
gst_element_post_message (GST_ELEMENT_CAST (sink),
|
||||
gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
|
||||
}
|
||||
gst_object_unref (pad);
|
||||
}
|
||||
gst_object_unref (pad);
|
||||
} else {
|
||||
gst_event_unref (event);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user