From 1c0049ec2bccdc1673addfb88615f8d325ba563f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 19 Jun 2025 08:32:54 +0200 Subject: [PATCH 1/2] clapper: Make timeline insert/remove work from any thread Detect and auto switch thread to main if done from a different one. With this, apps can still continue to implement thread switch and doing multiple insertions/deletions within single main thread invoke or simply call this function from a different thread for convenience. --- src/lib/clapper/clapper-timeline-private.h | 6 + src/lib/clapper/clapper-timeline.c | 63 +++++----- src/lib/clapper/clapper-timeline.h | 2 +- src/lib/clapper/clapper-utils-private.h | 8 ++ src/lib/clapper/clapper-utils.c | 129 +++++++++++++-------- 5 files changed, 135 insertions(+), 73 deletions(-) diff --git a/src/lib/clapper/clapper-timeline-private.h b/src/lib/clapper/clapper-timeline-private.h index 5e8ab7c4..f10b9f97 100644 --- a/src/lib/clapper/clapper-timeline-private.h +++ b/src/lib/clapper/clapper-timeline-private.h @@ -34,4 +34,10 @@ gboolean clapper_timeline_set_toc (ClapperTimeline *timeline, GstToc *toc, gbool G_GNUC_INTERNAL void clapper_timeline_refresh (ClapperTimeline *timeline); +G_GNUC_INTERNAL +void clapper_timeline_insert_marker_internal (ClapperTimeline *timeline, ClapperMarker *marker); + +G_GNUC_INTERNAL +void clapper_timeline_remove_marker_internal (ClapperTimeline *timeline, ClapperMarker *marker); + G_END_DECLS diff --git a/src/lib/clapper/clapper-timeline.c b/src/lib/clapper/clapper-timeline.c index b03e326d..9fa7425e 100644 --- a/src/lib/clapper/clapper-timeline.c +++ b/src/lib/clapper/clapper-timeline.c @@ -30,6 +30,7 @@ #include "clapper-player-private.h" #include "clapper-reactables-manager-private.h" #include "clapper-features-manager-private.h" +#include "clapper-utils-private.h" #define GST_CAT_DEFAULT clapper_timeline_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -187,25 +188,12 @@ _take_marker_unlocked (ClapperTimeline *self, ClapperMarker *marker) return g_sequence_iter_get_position (iter); } -/** - * clapper_timeline_insert_marker: - * @timeline: a #ClapperTimeline - * @marker: a #ClapperMarker - * - * Insert the #ClapperMarker into @timeline. - * - * Returns: %TRUE if inserted, %FALSE if marker was - * already inserted into timeline. - */ -gboolean -clapper_timeline_insert_marker (ClapperTimeline *self, ClapperMarker *marker) +void +clapper_timeline_insert_marker_internal (ClapperTimeline *self, ClapperMarker *marker) { gboolean success; gint position = 0; - g_return_val_if_fail (CLAPPER_IS_TIMELINE (self), FALSE); - g_return_val_if_fail (CLAPPER_IS_MARKER (marker), FALSE); - GST_OBJECT_LOCK (self); if ((success = !g_sequence_lookup (self->markers_seq, marker, @@ -220,30 +208,34 @@ clapper_timeline_insert_marker (ClapperTimeline *self, ClapperMarker *marker) clapper_timeline_post_item_updated (self); } - - return success; } /** - * clapper_timeline_remove_marker: + * clapper_timeline_insert_marker: * @timeline: a #ClapperTimeline * @marker: a #ClapperMarker * - * Removes #ClapperMarker from the timeline. - * - * If marker was not in the @timeline, this function will do nothing, - * so it is safe to call if unsure. + * Insert the #ClapperMarker into @timeline. */ void -clapper_timeline_remove_marker (ClapperTimeline *self, ClapperMarker *marker) +clapper_timeline_insert_marker (ClapperTimeline *self, ClapperMarker *marker) +{ + g_return_if_fail (CLAPPER_IS_TIMELINE (self)); + g_return_if_fail (CLAPPER_IS_MARKER (marker)); + + if (g_main_context_is_owner (g_main_context_default ())) + clapper_timeline_insert_marker_internal (self, marker); + else + clapper_utils_timeline_insert_on_main_sync (self, marker); +} + +void +clapper_timeline_remove_marker_internal (ClapperTimeline *self, ClapperMarker *marker) { GSequenceIter *iter; gint position = 0; gboolean success = FALSE; - g_return_if_fail (CLAPPER_IS_TIMELINE (self)); - g_return_if_fail (CLAPPER_IS_MARKER (marker)); - GST_OBJECT_LOCK (self); if ((iter = g_sequence_lookup (self->markers_seq, marker, @@ -264,6 +256,25 @@ clapper_timeline_remove_marker (ClapperTimeline *self, ClapperMarker *marker) } } +/** + * clapper_timeline_remove_marker: + * @timeline: a #ClapperTimeline + * @marker: a #ClapperMarker + * + * Removes #ClapperMarker from the timeline if present. + */ +void +clapper_timeline_remove_marker (ClapperTimeline *self, ClapperMarker *marker) +{ + g_return_if_fail (CLAPPER_IS_TIMELINE (self)); + g_return_if_fail (CLAPPER_IS_MARKER (marker)); + + if (g_main_context_is_owner (g_main_context_default ())) + clapper_timeline_remove_marker_internal (self, marker); + else + clapper_utils_timeline_remove_on_main_sync (self, marker); +} + /** * clapper_timeline_get_marker: * @timeline: a #ClapperTimeline diff --git a/src/lib/clapper/clapper-timeline.h b/src/lib/clapper/clapper-timeline.h index fa621192..022560f4 100644 --- a/src/lib/clapper/clapper-timeline.h +++ b/src/lib/clapper/clapper-timeline.h @@ -38,7 +38,7 @@ CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperTimeline, clapper_timeline, CLAPPER, TIMELINE, GstObject) CLAPPER_API -gboolean clapper_timeline_insert_marker (ClapperTimeline *timeline, ClapperMarker *marker); +void clapper_timeline_insert_marker (ClapperTimeline *timeline, ClapperMarker *marker); CLAPPER_API void clapper_timeline_remove_marker (ClapperTimeline *timeline, ClapperMarker *marker); diff --git a/src/lib/clapper/clapper-utils-private.h b/src/lib/clapper/clapper-utils-private.h index 6c391dbf..49e695db 100644 --- a/src/lib/clapper/clapper-utils-private.h +++ b/src/lib/clapper/clapper-utils-private.h @@ -26,6 +26,8 @@ #include "clapper-utils.h" #include "clapper-queue.h" #include "clapper-media-item.h" +#include "clapper-timeline.h" +#include "clapper-marker.h" G_BEGIN_DECLS @@ -44,6 +46,12 @@ void clapper_utils_queue_remove_on_main_sync (ClapperQueue *queue, ClapperMediaI G_GNUC_INTERNAL void clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue); +G_GNUC_INTERNAL +void clapper_utils_timeline_insert_on_main_sync (ClapperTimeline *timeline, ClapperMarker *marker); + +G_GNUC_INTERNAL +void clapper_utils_timeline_remove_on_main_sync (ClapperTimeline *timeline, ClapperMarker *marker); + G_GNUC_INTERNAL void clapper_utils_prop_notify_on_main_sync (GObject *object, GParamSpec *pspec); diff --git a/src/lib/clapper/clapper-utils.c b/src/lib/clapper/clapper-utils.c index ad090117..9477fe74 100644 --- a/src/lib/clapper/clapper-utils.c +++ b/src/lib/clapper/clapper-utils.c @@ -17,6 +17,7 @@ */ #include "clapper-utils-private.h" +#include "clapper-timeline-private.h" #include "../shared/clapper-shared-utils-private.h" #define GST_CAT_DEFAULT clapper_utils_debug @@ -24,19 +25,21 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); typedef enum { - CLAPPER_UTILS_QUEUE_ALTER_APPEND = 1, - CLAPPER_UTILS_QUEUE_ALTER_INSERT, - CLAPPER_UTILS_QUEUE_ALTER_REMOVE, - CLAPPER_UTILS_QUEUE_ALTER_CLEAR -} ClapperUtilsQueueAlterMethod; + CLAPPER_UTILS_LIST_ALTER_QUEUE_APPEND = 1, + CLAPPER_UTILS_LIST_ALTER_QUEUE_INSERT, + CLAPPER_UTILS_LIST_ALTER_QUEUE_REMOVE, + CLAPPER_UTILS_LIST_ALTER_QUEUE_CLEAR, + CLAPPER_UTILS_LIST_ALTER_TIMELINE_INSERT, + CLAPPER_UTILS_LIST_ALTER_TIMELINE_REMOVE +} ClapperUtilsListAlterMethod; typedef struct { - ClapperQueue *queue; - ClapperMediaItem *item; - ClapperMediaItem *after_item; - ClapperUtilsQueueAlterMethod method; -} ClapperUtilsQueueAlterData; + GListModel *list; + GObject *item; + GObject *after_item; + ClapperUtilsListAlterMethod method; +} ClapperUtilsListAlterData; typedef struct { @@ -51,27 +54,26 @@ clapper_utils_initialize (void) "Clapper Utilities"); } -static ClapperUtilsQueueAlterData * -clapper_utils_queue_alter_data_new (ClapperQueue *queue, - ClapperMediaItem *item, ClapperMediaItem *after_item, - ClapperUtilsQueueAlterMethod method) +static ClapperUtilsListAlterData * +clapper_utils_list_alter_data_new (GListModel *list, GObject *item, + GObject *after_item, ClapperUtilsListAlterMethod method) { - ClapperUtilsQueueAlterData *data = g_new (ClapperUtilsQueueAlterData, 1); + ClapperUtilsListAlterData *data = g_new (ClapperUtilsListAlterData, 1); - data->queue = queue; + data->list = list; data->item = item; data->after_item = after_item; data->method = method; - GST_TRACE ("Created queue alter data: %p", data); + GST_TRACE ("Created list alter data: %p", data); return data; } static void -clapper_utils_queue_alter_data_free (ClapperUtilsQueueAlterData *data) +clapper_utils_list_alter_data_free (ClapperUtilsListAlterData *data) { - GST_TRACE ("Freeing queue alter data: %p", data); + GST_TRACE ("Freeing list alter data: %p", data); g_free (data); } @@ -98,35 +100,48 @@ clapper_utils_prop_notify_data_free (ClapperUtilsPropNotifyData *data) } static gpointer -clapper_utils_queue_alter_on_main (ClapperUtilsQueueAlterData *data) +clapper_utils_list_alter_on_main (ClapperUtilsListAlterData *data) { GST_DEBUG ("Queue alter invoked"); switch (data->method) { - case CLAPPER_UTILS_QUEUE_ALTER_APPEND: - clapper_queue_add_item (data->queue, data->item); + case CLAPPER_UTILS_LIST_ALTER_QUEUE_APPEND: + clapper_queue_add_item (CLAPPER_QUEUE_CAST (data->list), + CLAPPER_MEDIA_ITEM_CAST (data->item)); break; - case CLAPPER_UTILS_QUEUE_ALTER_INSERT:{ + case CLAPPER_UTILS_LIST_ALTER_QUEUE_INSERT:{ guint index; /* If we have "after_item" then we need to insert after it, otherwise prepend */ if (data->after_item) { - if (clapper_queue_find_item (data->queue, data->after_item, &index)) + if (clapper_queue_find_item (CLAPPER_QUEUE_CAST (data->list), + CLAPPER_MEDIA_ITEM_CAST (data->after_item), &index)) { index++; - else // If not found, just append at the end - index = -1; + } else { + index = -1; // if not found, just append at the end + } } else { index = 0; } - clapper_queue_insert_item (data->queue, data->item, index); + clapper_queue_insert_item (CLAPPER_QUEUE_CAST (data->list), + CLAPPER_MEDIA_ITEM_CAST (data->item), index); break; } - case CLAPPER_UTILS_QUEUE_ALTER_REMOVE: - clapper_queue_remove_item (data->queue, data->item); + case CLAPPER_UTILS_LIST_ALTER_QUEUE_REMOVE: + clapper_queue_remove_item (CLAPPER_QUEUE_CAST (data->list), + CLAPPER_MEDIA_ITEM_CAST (data->item)); break; - case CLAPPER_UTILS_QUEUE_ALTER_CLEAR: - clapper_queue_clear (data->queue); + case CLAPPER_UTILS_LIST_ALTER_QUEUE_CLEAR: + clapper_queue_clear (CLAPPER_QUEUE_CAST (data->list)); + break; + case CLAPPER_UTILS_LIST_ALTER_TIMELINE_INSERT: + clapper_timeline_insert_marker_internal (CLAPPER_TIMELINE_CAST (data->list), + CLAPPER_MARKER_CAST (data->item)); + break; + case CLAPPER_UTILS_LIST_ALTER_TIMELINE_REMOVE: + clapper_timeline_remove_marker_internal (CLAPPER_TIMELINE_CAST (data->list), + CLAPPER_MARKER_CAST (data->item)); break; default: g_assert_not_reached (); @@ -146,13 +161,13 @@ clapper_utils_prop_notify_on_main (ClapperUtilsPropNotifyData *data) } static inline void -clapper_utils_queue_alter_invoke_on_main_sync_take (ClapperUtilsQueueAlterData *data) +clapper_utils_list_alter_invoke_on_main_sync_take (ClapperUtilsListAlterData *data) { GST_DEBUG ("Invoking queue alter on main..."); clapper_shared_utils_context_invoke_sync_full (g_main_context_default (), - (GThreadFunc) clapper_utils_queue_alter_on_main, data, - (GDestroyNotify) clapper_utils_queue_alter_data_free); + (GThreadFunc) clapper_utils_list_alter_on_main, data, + (GDestroyNotify) clapper_utils_list_alter_data_free); GST_DEBUG ("Queue alter invoke finished"); } @@ -160,34 +175,56 @@ clapper_utils_queue_alter_invoke_on_main_sync_take (ClapperUtilsQueueAlterData * void clapper_utils_queue_append_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item) { - ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue, - item, NULL, CLAPPER_UTILS_QUEUE_ALTER_APPEND); - clapper_utils_queue_alter_invoke_on_main_sync_take (data); + ClapperUtilsListAlterData *data = clapper_utils_list_alter_data_new ( + (GListModel *) queue, (GObject *) item, NULL, + CLAPPER_UTILS_LIST_ALTER_QUEUE_APPEND); + clapper_utils_list_alter_invoke_on_main_sync_take (data); } void clapper_utils_queue_insert_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item, ClapperMediaItem *after_item) { - ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue, - item, after_item, CLAPPER_UTILS_QUEUE_ALTER_INSERT); - clapper_utils_queue_alter_invoke_on_main_sync_take (data); + ClapperUtilsListAlterData *data = clapper_utils_list_alter_data_new ( + (GListModel *) queue, (GObject *) item, (GObject *) after_item, + CLAPPER_UTILS_LIST_ALTER_QUEUE_INSERT); + clapper_utils_list_alter_invoke_on_main_sync_take (data); } void clapper_utils_queue_remove_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item) { - ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue, - item, NULL, CLAPPER_UTILS_QUEUE_ALTER_REMOVE); - clapper_utils_queue_alter_invoke_on_main_sync_take (data); + ClapperUtilsListAlterData *data = clapper_utils_list_alter_data_new ( + (GListModel *) queue, (GObject *) item, NULL, + CLAPPER_UTILS_LIST_ALTER_QUEUE_REMOVE); + clapper_utils_list_alter_invoke_on_main_sync_take (data); } void clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue) { - ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue, - NULL, NULL, CLAPPER_UTILS_QUEUE_ALTER_CLEAR); - clapper_utils_queue_alter_invoke_on_main_sync_take (data); + ClapperUtilsListAlterData *data = clapper_utils_list_alter_data_new ( + (GListModel *) queue, NULL, NULL, + CLAPPER_UTILS_LIST_ALTER_QUEUE_CLEAR); + clapper_utils_list_alter_invoke_on_main_sync_take (data); +} + +void +clapper_utils_timeline_insert_on_main_sync (ClapperTimeline *timeline, ClapperMarker *marker) +{ + ClapperUtilsListAlterData *data = clapper_utils_list_alter_data_new ( + (GListModel *) timeline, (GObject *) marker, NULL, + CLAPPER_UTILS_LIST_ALTER_TIMELINE_INSERT); + clapper_utils_list_alter_invoke_on_main_sync_take (data); +} + +void +clapper_utils_timeline_remove_on_main_sync (ClapperTimeline *timeline, ClapperMarker *marker) +{ + ClapperUtilsListAlterData *data = clapper_utils_list_alter_data_new ( + (GListModel *) timeline, (GObject *) marker, NULL, + CLAPPER_UTILS_LIST_ALTER_TIMELINE_REMOVE); + clapper_utils_list_alter_invoke_on_main_sync_take (data); } void From 266c588db9fdf648797dc9d0d5f77e2de2da9e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 19 Jun 2025 08:34:46 +0200 Subject: [PATCH 2/2] clapper-gtk: seek-bar: Make custom markers colorful --- src/lib/clapper-gtk/clapper-gtk-seek-bar.c | 73 ++++++++++++++++++++++ src/lib/clapper-gtk/css/styles.css | 9 +++ 2 files changed, 82 insertions(+) diff --git a/src/lib/clapper-gtk/clapper-gtk-seek-bar.c b/src/lib/clapper-gtk/clapper-gtk-seek-bar.c index 36fa57c3..4a919d14 100644 --- a/src/lib/clapper-gtk/clapper-gtk-seek-bar.c +++ b/src/lib/clapper-gtk/clapper-gtk-seek-bar.c @@ -395,6 +395,47 @@ _update_duration_label (ClapperGtkSeekBar *self, gdouble duration) gtk_adjustment_set_upper (adjustment, duration); } +static gboolean +_find_marks_in_widget (GtkWidget *widget, GtkWidget **top_marks, GtkWidget **bottom_marks) +{ + GtkWidget *child; + + if (g_strcmp0 (gtk_widget_get_css_name (widget), "marks") == 0) { + if (gtk_widget_has_css_class (widget, "top")) + *top_marks = widget; + else if (gtk_widget_has_css_class (widget, "bottom")) + *bottom_marks = widget; + + /* Its unexpected to have marks within marks, + * so do not iterate children of marks widget */ + return (*top_marks && *bottom_marks); + } + + child = gtk_widget_get_first_child (widget); + + while (child != NULL) { + if (_find_marks_in_widget (child, top_marks, bottom_marks)) + return TRUE; + + child = gtk_widget_get_next_sibling (child); + } + + return FALSE; +} + +static gboolean +_find_last_mark_in_marks (GtkWidget *marks, GtkWidget **last_mark) +{ + GtkWidget *widget = gtk_widget_get_last_child (marks); + + if (widget && g_strcmp0 (gtk_widget_get_css_name (widget), "mark") == 0) { + *last_mark = widget; + return TRUE; + } + + return FALSE; +} + static void _update_scale_marks (ClapperGtkSeekBar *self, ClapperTimeline *timeline) { @@ -423,11 +464,43 @@ _update_scale_marks (ClapperGtkSeekBar *self, ClapperTimeline *timeline) for (i = 0; i < n_markers; ++i) { ClapperMarker *marker = clapper_timeline_get_marker (timeline, i); + ClapperMarkerType marker_type = clapper_marker_get_marker_type (marker); gdouble start = clapper_marker_get_start (marker); gtk_scale_add_mark (GTK_SCALE (self->scale), start, GTK_POS_TOP, NULL); gtk_scale_add_mark (GTK_SCALE (self->scale), start, GTK_POS_BOTTOM, NULL); + if (marker_type >= CLAPPER_MARKER_TYPE_CUSTOM_1) { + GtkWidget *top_marks = NULL, *bottom_marks = NULL; + GtkWidget *top_mark = NULL, *bottom_mark = NULL; + + if (_find_marks_in_widget (self->scale, &top_marks, &bottom_marks) + && _find_last_mark_in_marks (top_marks, &top_mark) + && _find_last_mark_in_marks (bottom_marks, &bottom_mark)) { + const gchar *custom_name; + + switch (marker_type) { + case CLAPPER_MARKER_TYPE_CUSTOM_1: + custom_name = "custom1"; + break; + case CLAPPER_MARKER_TYPE_CUSTOM_2: + custom_name = "custom2"; + break; + case CLAPPER_MARKER_TYPE_CUSTOM_3: + custom_name = "custom3"; + break; + default: + custom_name = NULL; + break; + } + + if (G_LIKELY (custom_name != NULL)) { + gtk_widget_add_css_class (top_mark, custom_name); + gtk_widget_add_css_class (bottom_mark, custom_name); + } + } + } + gst_object_unref (marker); } diff --git a/src/lib/clapper-gtk/css/styles.css b/src/lib/clapper-gtk/css/styles.css index 0c67f446..a5604aaa 100644 --- a/src/lib/clapper-gtk/css/styles.css +++ b/src/lib/clapper-gtk/css/styles.css @@ -129,6 +129,15 @@ clapper-gtk-seek-bar label { margin-left: 2px; margin-right: 2px; } +clapper-gtk-seek-bar scale marks .custom1 indicator { + color: tomato; +} +clapper-gtk-seek-bar scale marks .custom2 indicator { + color: goldenrod; +} +clapper-gtk-seek-bar scale marks .custom3 indicator { + color: limegreen; +} clapper-gtk-extra-menu-button popover .spinsidebutton { min-width: 28px;