18 Commits

Author SHA1 Message Date
Rafał Dzięgiel
ff3df7a8bf doc: clapper: Fix typo 2025-08-08 17:52:50 +02:00
Rafał Dzięgiel
31c230dde6 doc: clapper-gtk: Add migrating to 0.10 guide 2025-08-08 10:14:47 +02:00
Rafał Dzięgiel
91b7a0f1a5 Merge pull request #583 from Rafostar/additional-docs
doc: clapper: Additional documentation about enhancers
2025-08-07 22:50:50 +02:00
Rafał Dzięgiel
8d002397df doc: clapper: Additional documentation about enhancers 2025-08-07 22:35:38 +02:00
Rafał Dzięgiel
b9e98e2fdb Merge pull request #581 from Rafostar/flatpak-enhancers
flatpak: Reverse enhancers extension preference order
2025-08-04 20:31:52 +02:00
Rafał Dzięgiel
555b93451e flatpak: Reverse enhancers extension preference order
It seems that Flatpak selects first available branch from the end, not start
2025-08-04 20:13:42 +02:00
Rafał Dzięgiel
cdfac76d66 Merge pull request #580 from Rafostar/versioning
lib: Add runtime version information
2025-08-03 20:11:59 +02:00
Rafał Dzięgiel
ed13d53db9 clapper-gtk: Add runtime version information
Allows apps to check the library version app
is run against, contrary to compiled against
2025-08-03 15:18:44 +02:00
Rafał Dzięgiel
acf2b75f27 clapper: Add runtime version information
Allows apps to check the library version app
is run against, contrary to compiled against
2025-08-03 15:12:28 +02:00
Rafał Dzięgiel
d9c8534bb7 Merge pull request #579 from Rafostar/audio
clapper-gtk: Add audio widget
2025-08-03 14:23:20 +02:00
Rafał Dzięgiel
292b339e8a clapper-app: Use actions from AV widget
Replace all usage of "video" actions with "av" as the former one is deprecated
2025-08-03 13:36:34 +02:00
Rafał Dzięgiel
ddfedddce5 clapper-app: Use player from AV widget
Replace all usage of "clapper_gtk_video_get_player" with
"clapper_gtk_av_get_player" as the former one is deprecated
2025-08-02 20:57:03 +02:00
Rafał Dzięgiel
743097060e gst: sink: Remove unused posting of navigation messages
We only need to post events in the pipeline for navigation to work.
This was never used for anything and it generates a lot of noise
in the pipeline bus when cursor is moving over video.
2025-08-02 20:50:26 +02:00
Rafał Dzięgiel
b0b15cec5c clapper: Add "message" signal to the player
A detailed signal that allows for applications to receive element
messages posted on the pipeline bus. Useful if app cares about
some custom message that player normally does not handle.

For example audio player might want to read "level" messages in
order to show current audio level or implement visualizations.
2025-08-02 20:50:24 +02:00
Rafał Dzięgiel
b9a8b28a1f examples: Add audio player python example
A simple audio player made using "ClapperGtkAudio"
widget with python3 GI bindings
2025-08-02 20:50:22 +02:00
Rafał Dzięgiel
4c8c76c8f7 clapper-gtk: Introduce "ClapperGtkAudio" widget
A widget similar to video but for audio playback only. Widgets from
ClapperGtk library like buttons, seek bar, etc. can be used in it
for building an audio player with ease.
2025-08-02 20:50:20 +02:00
Rafał Dzięgiel
a5db6f701d clapper-gtk: Use AV widget actions directly
Actions of video widget are being deprecated in favour of
the same actions in the recently added AV base class
2025-08-02 20:50:18 +02:00
Rafał Dzięgiel
4301a9a9fa clapper-gtk: video: Split code into subclass
Create a base class called "ClapperGtkAv" and subclass it into "ClapperGtkVideo".
This allows to have player and session inhibit logic in base class and share it
with other subclassed widgets.
2025-08-02 20:50:10 +02:00
39 changed files with 1868 additions and 366 deletions

View File

@@ -56,6 +56,7 @@ base_url = "https://github.com/Rafostar/clapper/tree/master/"
[extra]
# The same order will be used when generating the index
content_files = [
"migrating-to-010.md",
]
content_images = [
"images/clapper-logo.svg",

View File

@@ -6,6 +6,10 @@ clappergtk_toml = configure_file(
install_dir: join_paths(datadir, 'doc', 'clapper-gtk'),
)
extra_md_files = [
'migrating-to-010.md',
]
custom_target('clapper-gtk-doc',
input: [
clappergtk_toml,
@@ -24,6 +28,7 @@ custom_target('clapper-gtk-doc',
'--content-dir=@0@'.format(meson.current_source_dir()),
'@INPUT1@',
],
depend_files: extra_md_files,
build_by_default: true,
install: true,
install_dir: join_paths(datadir, 'doc'),

View File

@@ -0,0 +1,22 @@
Title: Migrating to ClapperGtk 0.10
Slug: migrating-to-010
### AV widget
Code of [class@ClapperGtk.Video] was split into a base class [class@ClapperGtk.Av] from which
[class@ClapperGtk.Video] and newly added [class@ClapperGtk.Audio] widgets are made.
This code split implies following changes:
* Properties `auto-inhibit`, `inhibited` and `player` were moved into AV base class, since these
are usually used without explicit need to specify object class they belong to, this change should
not affect most use-cases.
* Methods to get above properties are now in AV, but also left in video widget for compatibility purposes.
* Built-in widget actions starting with `video.` prefix are deprecated (also left for compatibility).
Implementations that used them are encouraged to use `av.` actions now. All actions from video widget were
ported to AV widget as they were, so updating your app should be as easy as changing this action prefix.
### Other Info
Above describes changes to GTK integration library, for the playback library
check out [other migration guide](../clapper/migrating-to-010.html).

View File

@@ -0,0 +1,153 @@
Title: Clapper Enhancers
Slug: clapper-enhancers
### Overview
Clapper Enhancers are special plugins loaded through `libpeas` for Clapper library.
The idea is to enhance it (including applications that use Clapper) with new
capabilities that do stuff outside of `GStreamer` scope of things without increasing
number of dependencies of Clapper itself (especially for features that not everyone needs).
To avoid confusion with term "plugins" that `GStreamer` uses, the name "enhancers"
was choosen instead.
In addition to writing enhancers in pure `C`, they can be written in any programmable
language that is supported by `libpeas` and works with Clapper library bindings
(examples being `Python`, `GJS` and `Vala`). This makes it possible for individual users
to add their own custom functionalities that they want to use individually.
### Types
Clapper gives three interfaces for writing enhancers for different things, these are:
- [Extractable](extractable-enhancers.html)
- [Playlistable](playlistable-enhancers.html)
- [Reactable](reactable-enhancers.html)
### Basics
Each enhancer is a `libpeas` compatible module. As such it follows its requirements for
writing plugins and is a [class@GObject.Object] sublass implementing one or more of interfaces
that Clapper library provides.
Due to the plugins nature, written enhancer can be either submitted to the main [Clapper Enhancers
repository](https://github.com/Rafostar/clapper-enhancers) when it seems useful for more than a single
application. Alternatively, it can be shipped as part your application. Users can also write their
own/custom local enhancer plugins.
### Loading Enhancers
Clapper will try to lazy load enhancer modules, meaning they will not be loaded unless they are used.
As an additional safety precaution, enhancers can be disallowed from their instances being created with
[property@Clapper.EnhancerProxy:target-creation-allowed]. Enhancers that operate on-demand
(when supported URI is given) such as [iface@Clapper.Extractable] and [iface@Clapper.Playlistable]
are allowed (enabled) by default, while [iface@Clapper.Reactable] are not.
Environment variables:
* `CLAPPER_ENHANCERS_PATH` - override for default path where Clapper loads enhancers from
* `CLAPPER_ENHANCERS_EXTRA_PATH` - additional path to scan for enhancer plugins
While both allow to specify multiple directories (with `:` separation), applications and
users should mostly use extra path in order to add their own enhancers from non-standard
installation directory. They can override default path in case where they want to forbid
using stock enhancers for some reason.
### Enhancer Proxies
In order to create enhancers when needed and use them only within thread they were created in,
Clapper manages enhancer instances on its own. It gives applications proxy objects for
browsing and configuring them.
Only after Clapper library is initialized, you can get either the global (application scope) list
([class@Clapper.EnhancerProxyList]) of enhancers proxies with [func@Clapper.get_global_enhancer_proxies]
or player scope with [method@Clapper.Player.get_enhancer_proxies].
Properties set on the global list will carry over to all created player instances afterwards.
While these set on a list from a player are applied to enhancers created by this player only.
You can use that to your advantage to only allow creation of some type of enhancer in only some
player instances.
### Properties
Some enhancers might want to expose some configuration. For this cases they should install
[class@GObject.Object] properties in their class. They must only use fundamental types from the list below:
* boolean
* int
* uint
* double
* string
They should include one or more of [flags@Clapper.EnhancerParamFlags].
Properties in object can have `global`, `local`, neither or both flags at once.
The ones with [Clapper.EnhancerParamFlags.GLOBAL] are for user to configure. They should
be exposed (ideally in the UI) and used for things that make sense to be set for all
applications at once (e.g. supported media codec by user device, preferred language, etc.).
On the other hand, properties with [Clapper.EnhancerParamFlags.LOCAL] are for application scope
usage only. They never carry over to other apps, nor they can be set on global enhancers list.
Instead they are configured per player instance.
When property is neither `global` or `local` it is considered to be plugin internal property.
Clapper will never access them, as such they can be of any type (not limited to above list).
This also makes properties that are already installed in base classes of subclassed object
not appear in the UI.
When both flags are set, property will initially use globally set value by user while still
allowing application to override this value locally per player instance.
Extra flags like [Clapper.EnhancerParamFlags.FILEPATH] and [Clapper.EnhancerParamFlags.DIRPATH]
can be used (usually with `string` type of property) in order to let application know that this
field holds a file/directory path and allow it to present file selection dialog to the user instead
of text entry.
### Configuring Enhancers
Applications browse properties of enhancer via [method@Clapper.EnhancerProxy.get_target_properties]
which returns a list of [class@GObject.ParamSpec]. By inspecting their `flags` application can know
whether property is `global`, `local` or both.
Use [method@Clapper.EnhancerProxy.get_settings] to get a [class@Gio.Settings] with `global` properties
to read and write (note that only users should be able to change them, thus you might want to bind these
into some UI widgets for that). These can be (with user consent) set on either proxy from global proxies
list or the player one.
Use [method@Clapper.EnhancerProxy.set_locally] to set `local` properties. These are meant for applications
to freely customize as they please. Remember that you can only set them on a enhancer proxy object belonging
to some player instance and not the global one.
### Plugin Info File
An enhancer plugin should be placed within directory that includes its [Peas] plugin
description file. It should be a text file with a `.plugin` extension and contain at least
following content:
```
[Plugin]
Module=example_enhancer
Name=Example Enhancer
Description=This enhancer serves as an example
Version=0.0.1
```
* `Module` - module entry file name. It also acts as enhancer ID and should be unique for each.
It is recommended to use app/custom prefix in its name.
* `Name` - module friendly name for displaying in UI and such.
* `Description` - description to present to the user for what it does.
* `Version` - enhancer version. In order to lazy load enhancers, Clapper will cache each
enhancer data and olny reload it if version changes, so keep this always updated.
If module is written in interpretable programming language it must also contain `Loader` key
with interpreter name (e.g. `Loader=python`).
Some enhancer interfaces require additional fields to be put in this file. They are described
in the requirements of each one in their documentation pages that are listed in the
[Types section](clapper-enhancers.html#types).
### Adding Enhancers to Flatpak App
If you are using Clapper as part of a `Flatpak` application, you can get all the enhancers from their main repo as an extension
(info [here](https://github.com/flathub/com.github.rafostar.Clapper?tab=readme-ov-file#comgithubrafostarclapperenhancers)),
thus you do not need to build them yourself.

View File

@@ -66,6 +66,11 @@ base_url = "https://github.com/Rafostar/clapper/tree/master/"
[extra]
# The same order will be used when generating the index
content_files = [
"clapper-enhancers.md",
"extractable-enhancers.md",
"playlistable-enhancers.md",
"reactable-enhancers.md",
"migrating-to-010.md",
]
content_images = [
"images/clapper-logo.svg",

View File

@@ -0,0 +1,76 @@
Title: Extractable Enhancers
Slug: extractable-enhancers
### Overview
[iface@Clapper.Extractable] is an interface to implement enhancers that need to
resolve given URI into data that `GStreamer` will be able to play. While not
limited to, main use-case is for media information extraction.
While implementer is free to write whole extraction by hand, he can alternatively
integrate some 3rd party library that does most of this work. In such case, extractable
enhancer is more of a wrapper for integrating said library. This is especially useful
if library that you want to use is written in different programming language than Clapper is.
For the basics about writing enhancers see [Clapper Enhancers](clapper-enhancers.html).
### Requirements
Additional fields for `.plugin` info file: `X-Schemes` and `X-Hosts`. The former one should
hold the `;` separated list of supported URI schemes, while the latter is for hostnames.
Example:
```
X-Schemes=https;expl
X-Hosts=example.com;other.example.com
```
With this in place, enhancer will be loaded and used for URIs that match combinations
of one of the schemes and hosts. The special rule is that when using some custom URI
scheme other than `http(s)`, `X-Hosts` can be skipped since such URI explicitly
says to use this module.
Considering all of the above, this enhancer would try to extract URIs like:
* `https://example.com/video_id=ABCD`
* `expl://video.it?id=ABCD`
It would not act however for:
* `https://video.it?id=ABCD`
* `qwerty://other.example.com/video_id=ABCD`
An enhancer of this type must implement [vfunc@Clapper.Extractable.extract] virtual method.
### Harvesting data
When [vfunc@Clapper.Extractable.extract] is called, newly made [class@Clapper.Harvest]
is passed as this function argument. Implementation is responsible for filling it with
data (see [method@Clapper.Harvest.fill]) that can be played. Such content can be either
a resolved URI to actual media file or some streaming manifest (like `HLS` or `DASH`).
During extract function, implementation might optionally add media tags such as title
(which will be merged with tags of [class@Clapper.MediaItem]) and extra HTTP request
headers if any are required to access this content.
### Multiple items extraction
It is possible to handle URIs which would normally return more than one media item to play.
Examples being playlists, search queries, related videos, etc.
This can be handled in two ways, depending on set media type:
1. `text/uri-list`
2. `application/clapper-playlist`
If you use the first option, harvest should be filled with idividual URIs one per line.
Clapper will use its built-in URI list parser to create a media item for each URI and
place them in its playback queue. This is equivalent of creating [class@Clapper.MediaItem]
for each of these URIs without setting any tags in it initially.
The second option requires for this enhancer to also implement [iface@Clapper.Playlistable]
interface. In this case, you can fill harvest with ANY kind of data considering that
[vfunc@Clapper.Playlistable.parse] of your own enhancer will be used with the data you
passed. With this, you can add more info to media items such as extra tags, timeline markers
or subtitle URI. Useful if your extractor extracts both URIs and tags in one go.

View File

@@ -6,6 +6,14 @@ clapper_toml = configure_file(
install_dir: join_paths(datadir, 'doc', 'clapper'),
)
extra_md_files = [
'clapper-enhancers.md',
'extractable-enhancers.md',
'playlistable-enhancers.md',
'reactable-enhancers.md',
'migrating-to-010.md',
]
custom_target('clapper-doc',
input: [
clapper_toml,
@@ -23,6 +31,7 @@ custom_target('clapper-doc',
'--content-dir=@0@'.format(meson.current_source_dir()),
'@INPUT1@',
],
depend_files: extra_md_files,
build_by_default: true,
install: true,
install_dir: join_paths(datadir, 'doc'),

View File

@@ -0,0 +1,44 @@
Title: Migrating to Clapper 0.10
Slug: migrating-to-010
### Replace Features with Enhancers
Clapper 0.10 deprecates [class@Clapper.Feature] objects in favour [Clapper Enhancers](clapper-enhancers.html)
used via [class@Clapper.EnhancerProxy].
Old [class@Clapper.Feature] objects (including `mpris`, `discoverer` and `server`) are left for compatibility
reasons, but all apps using them are advised to migrate to enhancer plugins which already surpassed former
ones in what can be achieved with them.
Since these are in the form of plugins scanned during init, one of the differences is that you now check
their availability at runtime instead of compile time like before.
Something like this:
```c
#if CLAPPER_HAVE_MPRIS
ClapperFeature *feature = CLAPPER_FEATURE (clapper_mpris_new (
mpris_name, APP_NAME, APP_ID));
clapper_player_add_feature (player, feature);
gst_object_unref (feature);
#endif
```
Can be implemented like this:
```c
ClapperEnhancerProxyList *proxies = clapper_player_get_enhancer_proxies (player);
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_get_proxy_by_module (proxies, "clapper-mpris");
if (proxy) {
clapper_enhancer_proxy_set_locally (proxy,
"own-name", mpris_name,
"identity", APP_NAME,
"desktop-entry", APP_ID, NULL);
clapper_enhancer_proxy_set_target_creation_allowed (proxy, TRUE);
gst_object_unref (proxy);
}
```
For more information how to use enhancers and how to write your own,
see [Clapper Enhancers](clapper-enhancers.html) documentation.

View File

@@ -0,0 +1,39 @@
Title: Playlistable Enhancers
Slug: playlistable-enhancers
### Overview
[iface@Clapper.Playlistable] is an interface to implement playlist parsers.
Allows to expand Clapper library with an ability to read data from which one
or more media items should be created.
To load playlist within Clapper, just create a new media item which has an URI
leading to data that playlistable enhancer will act upon. After parsing, that item
will be merged with first parsed item and the rest will be inserted into queue after
its position.
Essentially, such enhancer inserts items from a playlist into playback queue upon
which Clapper operates. It can also handle nested playlits (a playlist URI within
another playlist) with unlimited amount of nested levels.
For the basics about writing enhancers see [Clapper Enhancers](clapper-enhancers.html).
### Requirements
Additional fields for `.plugin` info file:
* `X-Data-Prefix` - describe text that data should start with
* `X-Data-Contains` - data must contain given phrase
* `X-Data-Regex` - regular expression to run on data
These are used by `typefinder` to determine whether given data is a playlist for
this enhancer to handle. At least one of the above must be added to plugin info file.
An enhancer of this type must implement [vfunc@Clapper.Playlistable.parse] virtual method.
### Parsing data
When [vfunc@Clapper.Playlistable.parse] is called, an empty [class@Gio.ListStore] is
passed as this function argument. Implementation is responsible for parsing data, creating
[class@Clapper.MediaItem] objects and adding them to that list store. While doing so, it
can also populate each media item tags, timeline markers and/or set subtitle URI.

View File

@@ -0,0 +1,23 @@
Title: Reactable Enhancers
Slug: reactable-enhancers
### Overview
[iface@Clapper.Reactable] is an interface to implement enhancers that react to the
playback and/or events that should influence it.
Such enhancer can work not only in a way that triggers external actions due to some
playback events, but also in reverse - alters playback or its queue due to some
external event. It can do so by getting a hold of the player that given enhancer
instance reacts to with [method@Clapper.Reactable.get_player].
For the basics about writing enhancers see [Clapper Enhancers](clapper-enhancers.html).
### Requirements
An enhancer of this type should implement any of the [iface@Clapper.Reactable] virtual
methods that it needs.
Note that when implementing [vfunc@Clapper.Reactable.queue_item_removed] you probably
also want to implement [vfunc@Clapper.Reactable.queue_cleared] as the former is not
called for each item when clearing the whole queue for performance reasons.

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

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

View File

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

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

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

View File

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

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

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

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

View File

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

View File

@@ -158,6 +158,7 @@ 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',

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