Merge pull request #579 from Rafostar/audio

clapper-gtk: Add audio widget
This commit is contained in:
Rafał Dzięgiel
2025-08-03 14:23:20 +02:00
committed by GitHub
22 changed files with 1269 additions and 364 deletions

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

View File

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

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

View 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

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -52,6 +52,8 @@ void clapper_app_bus_post_object_desc_signal (ClapperAppBus *app_bus, GstObject
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

View File

@@ -46,6 +46,7 @@ enum
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
};
@@ -58,6 +59,7 @@ static ClapperBusQuark _structure_quarks[] = {
{"simple-signal", 0},
{"object-desc-signal", 0},
{"desc-with-details-signal", 0},
{"message", 0},
{"error-signal", 0},
{NULL, 0}
};
@@ -276,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,
@@ -326,6 +375,8 @@ clapper_app_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G
_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))

View File

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

View File

@@ -111,6 +111,7 @@ enum
SIGNAL_SEEK_DONE,
SIGNAL_DOWNLOAD_COMPLETE,
SIGNAL_MISSING_PLUGIN,
SIGNAL_MESSAGE,
SIGNAL_WARNING,
SIGNAL_ERROR,
SIGNAL_LAST
@@ -2985,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

View File

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