mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 23:32:04 +02:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2076309aaa | ||
|
79618edd1e |
2
.github/workflows/flatpak.yml
vendored
2
.github/workflows/flatpak.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: "Flatpak"
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: bilelmoussaoui/flatpak-github-actions:gnome-41
|
||||
image: bilelmoussaoui/flatpak-github-actions:gnome-40
|
||||
options: --privileged
|
||||
strategy:
|
||||
matrix:
|
||||
|
@@ -6,4 +6,4 @@ imports.package.init({
|
||||
prefix: '@prefix@',
|
||||
libdir: '@libdir@',
|
||||
});
|
||||
imports.package.run(imports.src.main);
|
||||
imports.package.run(imports.src.main@ID_POSTFIX@);
|
||||
|
@@ -1,19 +1,25 @@
|
||||
bin_conf = configuration_data()
|
||||
clapper_apps = ['', 'Remote', 'Daemon']
|
||||
|
||||
bin_conf.set('GJS', find_program('gjs').path())
|
||||
bin_conf.set('PACKAGE_NAME', meson.project_name())
|
||||
bin_conf.set('PACKAGE_VERSION', meson.project_version())
|
||||
bin_conf.set('prefix', get_option('prefix'))
|
||||
bin_conf.set('libdir', libdir)
|
||||
foreach id_postfix : clapper_apps
|
||||
app_postfix = (id_postfix != '') ? '.' + id_postfix : ''
|
||||
|
||||
configure_file(
|
||||
input: 'com.github.rafostar.Clapper.in',
|
||||
output: 'com.github.rafostar.Clapper',
|
||||
configuration: bin_conf,
|
||||
install: true,
|
||||
install_dir: bindir,
|
||||
install_mode: 'rwxr-xr-x'
|
||||
)
|
||||
bin_conf = configuration_data()
|
||||
bin_conf.set('GJS', find_program('gjs').path())
|
||||
bin_conf.set('PACKAGE_NAME', meson.project_name())
|
||||
bin_conf.set('PACKAGE_VERSION', meson.project_version())
|
||||
bin_conf.set('ID_POSTFIX', id_postfix)
|
||||
bin_conf.set('prefix', get_option('prefix'))
|
||||
bin_conf.set('libdir', libdir)
|
||||
|
||||
configure_file(
|
||||
input: 'com.github.rafostar.Clapper.in',
|
||||
output: 'com.github.rafostar.Clapper' + app_postfix,
|
||||
configuration: bin_conf,
|
||||
install: true,
|
||||
install_dir: bindir,
|
||||
install_mode: 'rwxr-xr-x'
|
||||
)
|
||||
endforeach
|
||||
|
||||
clapper_symlink_cmd = 'ln -fs @0@ $DESTDIR@1@'.format(
|
||||
'com.github.rafostar.Clapper',
|
||||
|
@@ -10,9 +10,6 @@ scrolledwindow scrollbar.vertical slider {
|
||||
}
|
||||
|
||||
/* Adwaita is missing osd ListBox */
|
||||
.clapperplaylist {
|
||||
background: none;
|
||||
}
|
||||
.clapperplaylist row {
|
||||
border-radius: 5px;
|
||||
}
|
||||
@@ -31,6 +28,9 @@ scrolledwindow scrollbar.vertical slider {
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.osd .clapperplaylist {
|
||||
background: none;
|
||||
}
|
||||
.osd .clapperplaylist row image {
|
||||
-gtk-icon-shadow: none;
|
||||
}
|
||||
@@ -223,9 +223,6 @@ scale trough slider {
|
||||
.fullscreen.tvmode .positionscale marks.bottom {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.fullscreen.tvmode .positionscale trough {
|
||||
border-radius: 3px;
|
||||
}
|
||||
.fullscreen.tvmode .positionscale trough highlight {
|
||||
border-radius: 3px;
|
||||
min-height: 20px;
|
||||
|
@@ -103,6 +103,16 @@
|
||||
<summary>Set PlayFlags for playbin</summary>
|
||||
</key>
|
||||
|
||||
<!-- Gtuber -->
|
||||
<key name="yt-adaptive-enabled" type="b">
|
||||
<default>false</default>
|
||||
<summary>Enable to use adaptive streaming</summary>
|
||||
</key>
|
||||
<key name="yt-quality-type" type="i">
|
||||
<default>1</default>
|
||||
<summary>Max online video quality type</summary>
|
||||
</key>
|
||||
|
||||
<!-- Other -->
|
||||
<key name="window-size" type="s">
|
||||
<default>'[800, 490]'</default>
|
||||
|
@@ -48,34 +48,6 @@
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="0.4.1" date="2021-12-20">
|
||||
<description>
|
||||
<p>Fixes:</p>
|
||||
<ul>
|
||||
<li>Compatibility with more recent libadwaita versions</li>
|
||||
<li>Toggle mute with M button alone</li>
|
||||
<li>Allow handling YouTube with external GStreamer plugins</li>
|
||||
<li>Fix catching errors when reading clipboard</li>
|
||||
<li>Fix missing translator-credits</li>
|
||||
<li>Fix missing gio-unix-2.0 dep</li>
|
||||
<li>Fix playback pausing when entering fullscreen with touchscreen</li>
|
||||
<li>Fix GST_PLUGIN_FEATURE_RANK env usage</li>
|
||||
<li>Fix video/audio decoder change detection</li>
|
||||
<li>Merge global video tags instead replacing them</li>
|
||||
<li>Few other misc bug fixes</li>
|
||||
</ul>
|
||||
<p>New translations:</p>
|
||||
<ul>
|
||||
<li>Chinese Simplified</li>
|
||||
<li>Czech</li>
|
||||
<li>Hungarian</li>
|
||||
<li>Portuguese</li>
|
||||
<li>Portuguese, Brazilian</li>
|
||||
<li>Russian</li>
|
||||
<li>Spanish</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.4.0" date="2021-09-12">
|
||||
<description>
|
||||
<p>Changes:</p>
|
||||
|
344
lib/gst/clapper/gstclapper.c
vendored
344
lib/gst/clapper/gstclapper.c
vendored
@@ -152,7 +152,7 @@ struct _GstClapper
|
||||
GstBus *bus;
|
||||
GstState target_state, current_state;
|
||||
gboolean is_live;
|
||||
GSource *tick_source;
|
||||
GSource *tick_source, *ready_timeout_source;
|
||||
GstClockTime cached_duration;
|
||||
|
||||
gdouble rate;
|
||||
@@ -253,9 +253,6 @@ static void gst_clapper_audio_info_update (GstClapper * self,
|
||||
static void gst_clapper_subtitle_info_update (GstClapper * self,
|
||||
GstClapperStreamInfo * stream_info);
|
||||
|
||||
static gboolean find_active_decoder_with_stream_id (GstClapper * self,
|
||||
GstElementFactoryListType type, const gchar * stream_id);
|
||||
|
||||
/* For playbin3 */
|
||||
static void gst_clapper_streams_info_create_from_collection (GstClapper * self,
|
||||
GstClapperMediaInfo * media_info, GstStreamCollection * collection);
|
||||
@@ -1138,6 +1135,44 @@ remove_tick_source (GstClapper * self)
|
||||
self->tick_source = NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ready_timeout_cb (gpointer user_data)
|
||||
{
|
||||
GstClapper *self = user_data;
|
||||
|
||||
if (self->target_state <= GST_STATE_READY) {
|
||||
GST_DEBUG_OBJECT (self, "Setting pipeline to NULL state");
|
||||
self->target_state = GST_STATE_NULL;
|
||||
self->current_state = GST_STATE_NULL;
|
||||
gst_element_set_state (self->playbin, GST_STATE_NULL);
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
add_ready_timeout_source (GstClapper * self)
|
||||
{
|
||||
if (self->ready_timeout_source)
|
||||
return;
|
||||
|
||||
self->ready_timeout_source = g_timeout_source_new_seconds (60);
|
||||
g_source_set_callback (self->ready_timeout_source,
|
||||
(GSourceFunc) ready_timeout_cb, self, NULL);
|
||||
g_source_attach (self->ready_timeout_source, self->context);
|
||||
}
|
||||
|
||||
static void
|
||||
remove_ready_timeout_source (GstClapper * self)
|
||||
{
|
||||
if (!self->ready_timeout_source)
|
||||
return;
|
||||
|
||||
g_source_destroy (self->ready_timeout_source);
|
||||
g_source_unref (self->ready_timeout_source);
|
||||
self->ready_timeout_source = NULL;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstClapper *clapper;
|
||||
@@ -1182,6 +1217,7 @@ emit_error (GstClapper * self, GError * err)
|
||||
g_error_free (err);
|
||||
|
||||
remove_tick_source (self);
|
||||
remove_ready_timeout_source (self);
|
||||
|
||||
self->target_state = GST_STATE_NULL;
|
||||
self->current_state = GST_STATE_NULL;
|
||||
@@ -1826,17 +1862,6 @@ media_info_update (GstClapper * self, GstClapperMediaInfo * info)
|
||||
"image_sample: %p", info->title, info->container, info->image_sample);
|
||||
}
|
||||
|
||||
static void
|
||||
merge_tags (GstTagList **my_tags, GstTagList *tags)
|
||||
{
|
||||
if (*my_tags) {
|
||||
*my_tags = gst_tag_list_make_writable (*my_tags);
|
||||
gst_tag_list_insert (*my_tags, tags, GST_TAG_MERGE_REPLACE);
|
||||
} else {
|
||||
*my_tags = gst_tag_list_ref (tags);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
|
||||
{
|
||||
@@ -1852,12 +1877,17 @@ tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
|
||||
if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_GLOBAL) {
|
||||
g_mutex_lock (&self->lock);
|
||||
if (self->media_info) {
|
||||
merge_tags (&self->media_info->tags, tags);
|
||||
if (self->media_info->tags)
|
||||
gst_tag_list_unref (self->media_info->tags);
|
||||
self->media_info->tags = gst_tag_list_ref (tags);
|
||||
media_info_update (self, self->media_info);
|
||||
g_mutex_unlock (&self->lock);
|
||||
} else {
|
||||
merge_tags (&self->global_tags, tags);
|
||||
if (self->global_tags)
|
||||
gst_tag_list_unref (self->global_tags);
|
||||
self->global_tags = gst_tag_list_ref (tags);
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
||||
|
||||
gst_tag_list_unref (tags);
|
||||
@@ -2020,7 +2050,6 @@ streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
|
||||
{
|
||||
GstClapper *self = GST_CLAPPER (user_data);
|
||||
GstStreamCollection *collection = NULL;
|
||||
gchar *video_sid, *audio_sid;
|
||||
guint i, len;
|
||||
|
||||
gst_message_parse_streams_selected (msg, &collection);
|
||||
@@ -2069,22 +2098,7 @@ streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
|
||||
|
||||
*current_sid = g_strdup (stream_id);
|
||||
}
|
||||
|
||||
video_sid = g_strdup (self->video_sid);
|
||||
audio_sid = g_strdup (self->audio_sid);
|
||||
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
if (video_sid) {
|
||||
find_active_decoder_with_stream_id (self, GST_ELEMENT_FACTORY_TYPE_DECODER
|
||||
| GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO, video_sid);
|
||||
g_free (video_sid);
|
||||
}
|
||||
if (audio_sid) {
|
||||
find_active_decoder_with_stream_id (self, GST_ELEMENT_FACTORY_TYPE_DECODER
|
||||
| GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, audio_sid);
|
||||
g_free (audio_sid);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@@ -2995,12 +3009,11 @@ decoder_changed_signal_data_free (DecoderChangedSignalData * data)
|
||||
|
||||
static void
|
||||
emit_decoder_changed (GstClapper * self, gchar * decoder_name,
|
||||
GstElementFactoryListType type)
|
||||
gboolean is_video)
|
||||
{
|
||||
GstClapperSignalDispatcherFunc func = NULL;
|
||||
|
||||
if ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) ==
|
||||
GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) {
|
||||
if (is_video) {
|
||||
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
|
||||
signals[SIGNAL_VIDEO_DECODER_CHANGED], 0, NULL, NULL, NULL) != 0 &&
|
||||
g_strcmp0 (self->last_vdecoder, decoder_name) != 0) {
|
||||
@@ -3008,8 +3021,7 @@ emit_decoder_changed (GstClapper * self, gchar * decoder_name,
|
||||
g_free (self->last_vdecoder);
|
||||
self->last_vdecoder = g_strdup (decoder_name);
|
||||
}
|
||||
} else if ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) ==
|
||||
GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) {
|
||||
} else {
|
||||
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
|
||||
signals[SIGNAL_AUDIO_DECODER_CHANGED], 0, NULL, NULL, NULL) != 0 &&
|
||||
g_strcmp0 (self->last_adecoder, decoder_name) != 0) {
|
||||
@@ -3030,138 +3042,6 @@ emit_decoder_changed (GstClapper * self, gchar * decoder_name,
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
iterate_decoder_pads (GstClapper * self, GstElement * element,
|
||||
const gchar * stream_id, GstElementFactoryListType type)
|
||||
{
|
||||
GstIterator *iter;
|
||||
GValue value = { 0, };
|
||||
gboolean found = FALSE;
|
||||
|
||||
iter = gst_element_iterate_src_pads (element);
|
||||
|
||||
while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) {
|
||||
GstPad *decoder_pad = g_value_get_object (&value);
|
||||
gchar *decoder_stream_id = gst_pad_get_stream_id (decoder_pad);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Decoder stream: %s", decoder_stream_id);
|
||||
|
||||
/* In case of playbin3, pad may not be active yet */
|
||||
if ((found = (g_strcmp0 (decoder_stream_id, stream_id) == 0
|
||||
|| (!decoder_stream_id && self->use_playbin3)))) {
|
||||
GstElementFactory *factory;
|
||||
gchar *plugin_name;
|
||||
|
||||
factory = gst_element_get_factory (element);
|
||||
plugin_name = gst_object_get_name (GST_OBJECT_CAST (factory));
|
||||
|
||||
if (plugin_name) {
|
||||
GST_DEBUG_OBJECT (self, "Found decoder: %s", plugin_name);
|
||||
emit_decoder_changed (self, plugin_name, type);
|
||||
|
||||
g_free (plugin_name);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (decoder_stream_id);
|
||||
g_value_unset (&value);
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
gst_iterator_free (iter);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
find_active_decoder_with_stream_id (GstClapper * self, GstElementFactoryListType type,
|
||||
const gchar * stream_id)
|
||||
{
|
||||
GstIterator *iter;
|
||||
GValue value = { 0, };
|
||||
gboolean found = FALSE;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Searching for decoder with stream: %s", stream_id);
|
||||
|
||||
iter = gst_bin_iterate_recurse (GST_BIN (self->playbin));
|
||||
|
||||
while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) {
|
||||
GstElement *element = g_value_get_object (&value);
|
||||
GstElementFactory *factory = gst_element_get_factory (element);
|
||||
|
||||
if (factory && gst_element_factory_list_is_type (factory, type))
|
||||
found = iterate_decoder_pads (self, element, stream_id, type);
|
||||
|
||||
g_value_unset (&value);
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
gst_iterator_free (iter);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static void
|
||||
update_current_decoder (GstClapper *self, GstElementFactoryListType type)
|
||||
{
|
||||
GstIterator *iter;
|
||||
GValue value = { 0, };
|
||||
|
||||
iter = gst_bin_iterate_all_by_element_factory_name (
|
||||
GST_BIN (self->playbin), "input-selector");
|
||||
|
||||
while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) {
|
||||
GstElement *element = g_value_get_object (&value);
|
||||
GstPad *active_pad;
|
||||
gboolean found = FALSE;
|
||||
|
||||
g_object_get (G_OBJECT (element), "active-pad", &active_pad, NULL);
|
||||
|
||||
if (active_pad) {
|
||||
gchar *stream_id;
|
||||
|
||||
stream_id = gst_pad_get_stream_id (active_pad);
|
||||
gst_object_unref (active_pad);
|
||||
|
||||
if (stream_id) {
|
||||
found = find_active_decoder_with_stream_id (self, type, stream_id);
|
||||
g_free (stream_id);
|
||||
}
|
||||
}
|
||||
|
||||
g_value_unset (&value);
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
gst_iterator_free (iter);
|
||||
}
|
||||
|
||||
static void
|
||||
current_video_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
|
||||
GstClapper * self)
|
||||
{
|
||||
GstElementFactoryListType type = GST_ELEMENT_FACTORY_TYPE_DECODER
|
||||
| GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO;
|
||||
|
||||
update_current_decoder (self, type);
|
||||
}
|
||||
|
||||
static void
|
||||
current_audio_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
|
||||
GstClapper * self)
|
||||
{
|
||||
GstElementFactoryListType type = GST_ELEMENT_FACTORY_TYPE_DECODER
|
||||
| GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO;
|
||||
|
||||
update_current_decoder (self, type);
|
||||
}
|
||||
|
||||
static void
|
||||
element_setup_cb (GstElement * playbin, GstElement * element, GstClapper * self)
|
||||
{
|
||||
@@ -3174,6 +3054,13 @@ element_setup_cb (GstElement * playbin, GstElement * element, GstClapper * self)
|
||||
if (plugin_name) {
|
||||
GST_INFO_OBJECT (self, "Plugin setup: %s", plugin_name);
|
||||
|
||||
if (gst_element_factory_list_is_type (factory,
|
||||
GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO))
|
||||
emit_decoder_changed (self, plugin_name, TRUE);
|
||||
else if (gst_element_factory_list_is_type (factory,
|
||||
GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO))
|
||||
emit_decoder_changed (self, plugin_name, FALSE);
|
||||
|
||||
/* TODO: Set plugin props */
|
||||
}
|
||||
g_free (plugin_name);
|
||||
@@ -3342,11 +3229,6 @@ gst_clapper_main (gpointer data)
|
||||
G_CALLBACK (audio_tags_changed_cb), self);
|
||||
g_signal_connect (self->playbin, "text-tags-changed",
|
||||
G_CALLBACK (subtitle_tags_changed_cb), self);
|
||||
|
||||
g_signal_connect (self->playbin, "notify::current-video",
|
||||
G_CALLBACK (current_video_notify_cb), self);
|
||||
g_signal_connect (self->playbin, "notify::current-audio",
|
||||
G_CALLBACK (current_audio_notify_cb), self);
|
||||
}
|
||||
|
||||
g_signal_connect (self->playbin, "notify::volume",
|
||||
@@ -3373,6 +3255,7 @@ gst_clapper_main (gpointer data)
|
||||
gst_object_unref (bus);
|
||||
|
||||
remove_tick_source (self);
|
||||
remove_ready_timeout_source (self);
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
if (self->media_info) {
|
||||
@@ -3441,104 +3324,6 @@ gst_clapper_has_plugin_with_features (const gchar * name)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_feature_name (gchar * str, const gchar ** feature)
|
||||
{
|
||||
if (!str)
|
||||
return FALSE;
|
||||
|
||||
g_strstrip (str);
|
||||
|
||||
if (str[0] != '\0') {
|
||||
*feature = str;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_feature_rank (gchar * str, GstRank * rank)
|
||||
{
|
||||
if (!str)
|
||||
return FALSE;
|
||||
|
||||
g_strstrip (str);
|
||||
|
||||
if (g_ascii_isdigit (str[0])) {
|
||||
unsigned long l;
|
||||
char *endptr;
|
||||
l = strtoul (str, &endptr, 10);
|
||||
if (endptr > str && endptr[0] == 0) {
|
||||
*rank = (GstRank) l;
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
} else if (g_ascii_strcasecmp (str, "NONE") == 0) {
|
||||
*rank = GST_RANK_NONE;
|
||||
} else if (g_ascii_strcasecmp (str, "MARGINAL") == 0) {
|
||||
*rank = GST_RANK_MARGINAL;
|
||||
} else if (g_ascii_strcasecmp (str, "SECONDARY") == 0) {
|
||||
*rank = GST_RANK_SECONDARY;
|
||||
} else if (g_ascii_strcasecmp (str, "PRIMARY") == 0) {
|
||||
*rank = GST_RANK_PRIMARY;
|
||||
} else if (g_ascii_strcasecmp (str, "MAX") == 0) {
|
||||
*rank = (GstRank) G_MAXINT;
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_env_feature_rank_update (void)
|
||||
{
|
||||
const gchar *env;
|
||||
gchar **split, **walk;
|
||||
|
||||
env = g_getenv ("GST_PLUGIN_FEATURE_RANK");
|
||||
|
||||
if (!env)
|
||||
return;
|
||||
|
||||
split = g_strsplit (env, ",", 0);
|
||||
|
||||
for (walk = split; *walk; walk++) {
|
||||
if (strchr (*walk, ':')) {
|
||||
gchar **values;
|
||||
|
||||
values = g_strsplit (*walk, ":", 2);
|
||||
if (values[0] && values[1]) {
|
||||
GstRank rank;
|
||||
const gchar *name;
|
||||
|
||||
if (parse_feature_name (values[0], &name)
|
||||
&& parse_feature_rank (values[1], &rank)) {
|
||||
GstPluginFeature *feature;
|
||||
|
||||
feature = gst_registry_find_feature (gst_registry_get (), name,
|
||||
GST_TYPE_ELEMENT_FACTORY);
|
||||
if (feature) {
|
||||
GstRank old_rank;
|
||||
|
||||
old_rank = gst_plugin_feature_get_rank (feature);
|
||||
if (old_rank != rank) {
|
||||
gst_plugin_feature_set_rank (feature, rank);
|
||||
GST_DEBUG ("Updated rank from env: %i -> %i for %s", old_rank, rank, name);
|
||||
}
|
||||
gst_object_unref (feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_strfreev (values);
|
||||
}
|
||||
}
|
||||
|
||||
g_strfreev (split);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_prepare_gstreamer (void)
|
||||
{
|
||||
@@ -3561,9 +3346,6 @@ gst_clapper_prepare_gstreamer (void)
|
||||
gst_clapper_set_feature_rank ("v4l2slvp8dec", GST_RANK_NONE);
|
||||
}
|
||||
|
||||
/* After setting defaults, update them from ENV */
|
||||
_env_feature_rank_update ();
|
||||
|
||||
gst_clapper_gstreamer_prepared = TRUE;
|
||||
GST_DEBUG ("GStreamer plugins prepared");
|
||||
}
|
||||
@@ -3640,6 +3422,7 @@ gst_clapper_play_internal (gpointer user_data)
|
||||
}
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
remove_ready_timeout_source (self);
|
||||
self->target_state = GST_STATE_PLAYING;
|
||||
|
||||
if (self->current_state < GST_STATE_PAUSED)
|
||||
@@ -3707,6 +3490,7 @@ gst_clapper_pause_internal (gpointer user_data)
|
||||
|
||||
tick_cb (self);
|
||||
remove_tick_source (self);
|
||||
remove_ready_timeout_source (self);
|
||||
|
||||
self->target_state = GST_STATE_PAUSED;
|
||||
|
||||
@@ -3786,11 +3570,13 @@ gst_clapper_stop_internal (GstClapper * self, gboolean transient)
|
||||
tick_cb (self);
|
||||
remove_tick_source (self);
|
||||
|
||||
add_ready_timeout_source (self);
|
||||
|
||||
self->target_state = GST_STATE_NULL;
|
||||
self->current_state = GST_STATE_NULL;
|
||||
self->current_state = GST_STATE_READY;
|
||||
self->is_live = FALSE;
|
||||
gst_bus_set_flushing (self->bus, TRUE);
|
||||
gst_element_set_state (self->playbin, GST_STATE_NULL);
|
||||
gst_element_set_state (self->playbin, GST_STATE_READY);
|
||||
gst_bus_set_flushing (self->bus, FALSE);
|
||||
change_state (self, transient && self->app_state != GST_CLAPPER_STATE_STOPPED
|
||||
? GST_CLAPPER_STATE_BUFFERING : GST_CLAPPER_STATE_STOPPED);
|
||||
|
31
lib/gst/clapper/meson.build
vendored
31
lib/gst/clapper/meson.build
vendored
@@ -34,13 +34,36 @@ gstclapper_defines = [
|
||||
'-DGST_USE_UNSTABLE_API',
|
||||
'-DHAVE_GTK_GL',
|
||||
]
|
||||
gtk_deps = [gstgl_dep, gstglproto_dep]
|
||||
have_gtk_gl_windowing = false
|
||||
|
||||
if not get_option('lib')
|
||||
subdir_done()
|
||||
gtk4_dep = dependency('gtk4', required: true)
|
||||
|
||||
if not gtk4_dep.version().version_compare('>=4.0.0')
|
||||
error('GTK4 version on this system is too old')
|
||||
endif
|
||||
|
||||
if not gir.found()
|
||||
error('Clapper lib requires GI bindings to be compiled')
|
||||
if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx)
|
||||
gtk_x11_dep = dependency('gtk4-x11', required: false)
|
||||
if gtk_x11_dep.found()
|
||||
gtk_deps += gtk_x11_dep
|
||||
if gst_gl_have_platform_glx
|
||||
gtk_deps += gstglx11_dep
|
||||
endif
|
||||
have_gtk_gl_windowing = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if gst_gl_have_window_wayland and gst_gl_have_platform_egl
|
||||
gtk_wayland_dep = dependency('gtk4-wayland', required: false)
|
||||
if gtk_wayland_dep.found()
|
||||
gtk_deps += [gtk_wayland_dep, gstglwayland_dep]
|
||||
have_gtk_gl_windowing = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if gst_gl_have_platform_egl
|
||||
gtk_deps += gstglegl_dep
|
||||
endif
|
||||
|
||||
if not have_gtk_gl_windowing
|
||||
|
1
lib/gst/meson.build
vendored
1
lib/gst/meson.build
vendored
@@ -1,2 +1 @@
|
||||
subdir('clapper')
|
||||
subdir('plugin')
|
||||
|
681
lib/gst/plugin/gstclappersink.c
vendored
681
lib/gst/plugin/gstclappersink.c
vendored
@@ -1,681 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020-2022 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 Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstclappersink.h"
|
||||
#include "gstgtkutils.h"
|
||||
|
||||
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
||||
#define DEFAULT_PAR_N 0
|
||||
#define DEFAULT_PAR_D 1
|
||||
|
||||
#define SINK_FORMATS \
|
||||
"{ BGR, RGB, BGRA, RGBA, ABGR, ARGB, RGBx, BGRx, RGBA64_LE, RGBA64_BE, NV12 }"
|
||||
|
||||
#define GST_CLAPPER_GL_SINK_CAPS \
|
||||
"video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), " \
|
||||
"format = (string)" SINK_FORMATS ", " \
|
||||
"width = " GST_VIDEO_SIZE_RANGE ", " \
|
||||
"height = " GST_VIDEO_SIZE_RANGE ", " \
|
||||
"framerate = " GST_VIDEO_FPS_RANGE ", " \
|
||||
"texture-target = (string) { 2D, external-oes } " \
|
||||
" ; " \
|
||||
"video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "," \
|
||||
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION "), " \
|
||||
"format = (string)" SINK_FORMATS ", " \
|
||||
"width = " GST_VIDEO_SIZE_RANGE ", " \
|
||||
"height = " GST_VIDEO_SIZE_RANGE ", " \
|
||||
"framerate = " GST_VIDEO_FPS_RANGE ", " \
|
||||
"texture-target = (string) { 2D, external-oes } "
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_WIDGET,
|
||||
PROP_FORCE_ASPECT_RATIO,
|
||||
PROP_PIXEL_ASPECT_RATIO,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_clapper_sink);
|
||||
#define GST_CAT_DEFAULT gst_debug_clapper_sink
|
||||
|
||||
static GstStaticPadTemplate gst_clapper_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (
|
||||
GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("memory:DMABuf", SINK_FORMATS) ";"
|
||||
GST_CLAPPER_GL_SINK_CAPS ";"
|
||||
GST_VIDEO_CAPS_MAKE (SINK_FORMATS)));
|
||||
|
||||
static void gst_clapper_sink_navigation_interface_init (
|
||||
GstNavigationInterface *iface);
|
||||
|
||||
#define gst_clapper_sink_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstClapperSink, gst_clapper_sink,
|
||||
GST_TYPE_VIDEO_SINK,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
|
||||
gst_clapper_sink_navigation_interface_init);
|
||||
GST_DEBUG_CATEGORY_INIT (gst_debug_clapper_sink,
|
||||
"clappersink", 0, "Clapper Sink"));
|
||||
GST_ELEMENT_REGISTER_DEFINE (clappersink, "clappersink", GST_RANK_NONE,
|
||||
GST_TYPE_CLAPPER_SINK);
|
||||
|
||||
static void gst_clapper_sink_finalize (GObject *object);
|
||||
static void gst_clapper_sink_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *param_spec);
|
||||
static void gst_clapper_sink_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *param_spec);
|
||||
|
||||
static gboolean gst_clapper_sink_propose_allocation (GstBaseSink *bsink,
|
||||
GstQuery *query);
|
||||
static gboolean gst_clapper_sink_start (GstBaseSink *bsink);
|
||||
static gboolean gst_clapper_sink_stop (GstBaseSink *bsink);
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_clapper_sink_change_state (GstElement *element, GstStateChange transition);
|
||||
|
||||
static void gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer,
|
||||
GstClockTime *start, GstClockTime *end);
|
||||
static GstCaps * gst_clapper_sink_get_caps (GstBaseSink *bsink,
|
||||
GstCaps *filter);
|
||||
static gboolean gst_clapper_sink_set_caps (GstBaseSink *bsink,
|
||||
GstCaps *caps);
|
||||
static GstFlowReturn gst_clapper_sink_show_frame (GstVideoSink *bsink,
|
||||
GstBuffer *buffer);
|
||||
|
||||
static void
|
||||
gst_clapper_sink_class_init (GstClapperSinkClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSinkClass *gstbasesink_class;
|
||||
GstVideoSinkClass *gstvideosink_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesink_class = (GstBaseSinkClass *) klass;
|
||||
gstvideosink_class = (GstVideoSinkClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_clapper_sink_set_property;
|
||||
gobject_class->get_property = gst_clapper_sink_get_property;
|
||||
gobject_class->finalize = gst_clapper_sink_finalize;
|
||||
|
||||
//gst_gtk_install_shared_properties (gobject_class);
|
||||
|
||||
gstelement_class->change_state = gst_clapper_sink_change_state;
|
||||
|
||||
gstbasesink_class->get_caps = gst_clapper_sink_get_caps;
|
||||
gstbasesink_class->set_caps = gst_clapper_sink_set_caps;
|
||||
gstbasesink_class->get_times = gst_clapper_sink_get_times;
|
||||
gstbasesink_class->propose_allocation = gst_clapper_sink_propose_allocation;
|
||||
gstbasesink_class->start = gst_clapper_sink_start;
|
||||
gstbasesink_class->stop = gst_clapper_sink_stop;
|
||||
|
||||
gstvideosink_class->show_frame = gst_clapper_sink_show_frame;
|
||||
|
||||
gst_element_class_set_metadata (gstelement_class,
|
||||
"Clapper Video Sink",
|
||||
"Sink/Video", "A GTK4 video sink used by Clapper media player",
|
||||
"Rafał Dzięgiel <rafostar.github@gmail.com>");
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&gst_clapper_sink_template);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_sink_init (GstClapperSink *self)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
|
||||
gobject_class = (GObjectClass *) GST_CLAPPER_SINK_GET_CLASS (self);
|
||||
|
||||
/* HACK: install here instead of class init to avoid GStreamer
|
||||
* plugin scanner GObject type conflicts with older GTK versions */
|
||||
if (!g_object_class_find_property (gobject_class, "widget")) {
|
||||
g_object_class_install_property (gobject_class, PROP_WIDGET,
|
||||
g_param_spec_object ("widget", "GTK Widget",
|
||||
"The GtkWidget to place in the widget hierarchy "
|
||||
"(must only be get from the GTK main thread)",
|
||||
GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
|
||||
self->par_n = DEFAULT_PAR_N;
|
||||
self->par_d = DEFAULT_PAR_D;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_sink_finalize (GObject *object)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK (object);
|
||||
|
||||
GST_TRACE ("Finalize");
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
if (self->window && self->window_destroy_id)
|
||||
g_signal_handler_disconnect (self->window, self->window_destroy_id);
|
||||
//if (self->widget && self->widget_destroy_id)
|
||||
// g_signal_handler_disconnect (self->widget, self->widget_destroy_id);
|
||||
|
||||
g_clear_object (&self->obj);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
widget_destroy_cb (GtkWidget *widget, GstClapperSink *self)
|
||||
{
|
||||
GST_OBJECT_LOCK (self);
|
||||
g_clear_object (&self->obj);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
static void
|
||||
window_destroy_cb (GtkWidget *window, GstClapperSink *self)
|
||||
{
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
if (self->obj) {
|
||||
if (self->widget_destroy_id) {
|
||||
GtkWidget *widget;
|
||||
|
||||
widget = gtk_clapper_object_get_widget (self->obj);
|
||||
|
||||
g_signal_handler_disconnect (widget, self->widget_destroy_id);
|
||||
self->widget_destroy_id = 0;
|
||||
}
|
||||
g_clear_object (&self->obj);
|
||||
}
|
||||
self->window = NULL;
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
gst_clapper_sink_get_widget (GstClapperSink *self)
|
||||
{
|
||||
if (G_UNLIKELY (self->obj == NULL)) {
|
||||
/* Ensure GTK is initialized */
|
||||
if (!gtk_init_check ()) {
|
||||
GST_ERROR_OBJECT (self, "Could not ensure GTK initialization");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->obj = gtk_clapper_object_new ();
|
||||
|
||||
/* Take the floating ref, otherwise the destruction of the container will
|
||||
* make this widget disappear possibly before we are done. */
|
||||
//g_object_ref_sink (self->obj);
|
||||
|
||||
//self->widget_destroy_id = g_signal_connect (widget,
|
||||
// "destroy", G_CALLBACK (widget_destroy_cb), self);
|
||||
|
||||
/* Back pointer */
|
||||
gtk_clapper_object_set_element (
|
||||
GTK_CLAPPER_OBJECT (self->obj), GST_ELEMENT (self));
|
||||
}
|
||||
|
||||
return gtk_clapper_object_get_widget (self->obj);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_sink_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_WIDGET:{
|
||||
GObject *widget = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
if (G_LIKELY (self->obj != NULL))
|
||||
widget = G_OBJECT (gtk_clapper_object_get_widget (self->obj));
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (G_UNLIKELY (widget == NULL)) {
|
||||
widget = gst_gtk_invoke_on_main (
|
||||
(GThreadFunc) gst_clapper_sink_get_widget, self);
|
||||
}
|
||||
|
||||
g_value_set_object (value, widget);
|
||||
break;
|
||||
}
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
g_value_set_boolean (value, self->force_aspect_ratio);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gst_value_set_fraction (value, self->par_n, self->par_d);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_sink_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
self->force_aspect_ratio = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
self->par_n = gst_value_get_fraction_numerator (value);
|
||||
self->par_d = gst_value_get_fraction_denominator (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_sink_navigation_send_event (GstNavigation *navigation,
|
||||
GstStructure *structure)
|
||||
{
|
||||
GstClapperSink *sink = GST_CLAPPER_SINK_CAST (navigation);
|
||||
GstEvent *event;
|
||||
|
||||
GST_TRACE_OBJECT (sink, "Navigation event: %" GST_PTR_FORMAT, structure);
|
||||
event = gst_event_new_navigation (structure);
|
||||
|
||||
if (G_LIKELY (GST_IS_EVENT (event))) {
|
||||
GstPad *pad;
|
||||
|
||||
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
|
||||
|
||||
if (G_LIKELY (GST_IS_PAD (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_event_unref (event);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_sink_navigation_interface_init (GstNavigationInterface *iface)
|
||||
{
|
||||
iface->send_event = gst_clapper_sink_navigation_send_event;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK (bsink);
|
||||
GstBufferPool *pool = NULL;
|
||||
GstStructure *config;
|
||||
GstCaps *caps;
|
||||
GstVideoInfo info;
|
||||
guint size;
|
||||
gboolean need_pool;
|
||||
GstStructure *allocation_meta = NULL;
|
||||
gint display_width, display_height;
|
||||
|
||||
//if (!self->display || !self->context)
|
||||
// return FALSE;
|
||||
|
||||
gst_query_parse_allocation (query, &caps, &need_pool);
|
||||
|
||||
if (!caps)
|
||||
goto no_caps;
|
||||
|
||||
if (!gst_video_info_from_caps (&info, caps))
|
||||
goto invalid_caps;
|
||||
|
||||
/* Normal size of a frame */
|
||||
size = GST_VIDEO_INFO_SIZE (&info);
|
||||
|
||||
if (need_pool) {
|
||||
GST_DEBUG_OBJECT (self, "Creating new pool");
|
||||
|
||||
pool = gst_buffer_pool_new ();
|
||||
config = gst_buffer_pool_get_config (pool);
|
||||
|
||||
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
|
||||
|
||||
if (!gst_buffer_pool_set_config (pool, config)) {
|
||||
gst_object_unref (pool);
|
||||
goto config_failed;
|
||||
}
|
||||
}
|
||||
|
||||
/* We need at least 3 buffers because we keep around the current one
|
||||
* for memory to stay valid during resizing and hold on to the pending one */
|
||||
gst_query_add_allocation_pool (query, pool, size, 3, 0);
|
||||
if (pool)
|
||||
gst_object_unref (pool);
|
||||
|
||||
/* FIXME: Read calculated display sizes from widget */
|
||||
display_width = GST_VIDEO_INFO_WIDTH (&info);
|
||||
display_height = GST_VIDEO_INFO_HEIGHT (&info);
|
||||
|
||||
if (display_width != 0 && display_height != 0) {
|
||||
GST_DEBUG_OBJECT (self, "Sending alloc query with size %dx%d",
|
||||
display_width, display_height);
|
||||
allocation_meta = gst_structure_new ("GstVideoOverlayCompositionMeta",
|
||||
"width", G_TYPE_UINT, display_width,
|
||||
"height", G_TYPE_UINT, display_height, NULL);
|
||||
}
|
||||
|
||||
gst_query_add_allocation_meta (query,
|
||||
GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, allocation_meta);
|
||||
|
||||
if (allocation_meta)
|
||||
gst_structure_free (allocation_meta);
|
||||
|
||||
/* We also support various metadata */
|
||||
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
no_caps:
|
||||
GST_DEBUG_OBJECT (bsink, "No caps specified");
|
||||
return FALSE;
|
||||
|
||||
invalid_caps:
|
||||
GST_DEBUG_OBJECT (bsink, "Invalid caps specified");
|
||||
return FALSE;
|
||||
|
||||
config_failed:
|
||||
GST_DEBUG_OBJECT (bsink, "Failed to set config");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_sink_start_on_main (GstClapperSink *self)
|
||||
{
|
||||
GtkWidget *widget;
|
||||
|
||||
/* Make sure widget is created */
|
||||
if (!(widget = gst_clapper_sink_get_widget (self)))
|
||||
return FALSE;
|
||||
|
||||
/* After this point, self->obj will always be set */
|
||||
|
||||
if (!GTK_IS_ROOT (gtk_widget_get_root (widget))) {
|
||||
GtkWidget *toplevel, *parent;
|
||||
gchar *win_title;
|
||||
|
||||
if ((parent = gtk_widget_get_parent (widget))) {
|
||||
GtkWidget *temp_parent;
|
||||
|
||||
while ((temp_parent = gtk_widget_get_parent (parent)))
|
||||
parent = temp_parent;
|
||||
}
|
||||
toplevel = (parent) ? parent : widget;
|
||||
|
||||
/* User did not add widget its own UI, let's popup a new GtkWindow to
|
||||
* make "gst-launch-1.0" work. */
|
||||
self->window = (GtkWindow *) gtk_window_new ();
|
||||
|
||||
win_title = g_strdup_printf ("Clapper Sink - GTK %u.%u.%u Window",
|
||||
gtk_get_major_version (),
|
||||
gtk_get_minor_version (),
|
||||
gtk_get_micro_version ());
|
||||
|
||||
gtk_window_set_default_size (self->window, 640, 480);
|
||||
gtk_window_set_title (self->window, win_title);
|
||||
gtk_window_set_child (self->window, toplevel);
|
||||
|
||||
g_free (win_title);
|
||||
|
||||
self->window_destroy_id = g_signal_connect (self->window,
|
||||
"destroy", G_CALLBACK (window_destroy_cb), self);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_sink_start (GstBaseSink *bsink)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK (bsink);
|
||||
GtkClapperObject *obj = NULL;
|
||||
|
||||
if (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_clapper_sink_start_on_main, self)))
|
||||
return FALSE;
|
||||
|
||||
//widget = GTK_CLAPPER_WIDGET (self->widget);
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
if (self->obj)
|
||||
obj = g_object_ref (self->obj);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (G_UNLIKELY (obj == NULL)) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Clapper widget does not exist"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!gtk_clapper_object_init_winsys (obj)) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Failed to initialize OpenGL with GTK"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
/*
|
||||
if (!clapper_sink->display)
|
||||
clapper_sink->display = gtk_clapper_gl_widget_get_display (clapper_widget);
|
||||
if (!clapper_sink->context)
|
||||
clapper_sink->context = gtk_clapper_gl_widget_get_context (clapper_widget);
|
||||
if (!clapper_sink->gtk_context)
|
||||
clapper_sink->gtk_context = gtk_clapper_gl_widget_get_gtk_context (clapper_widget);
|
||||
|
||||
if (!clapper_sink->display || !clapper_sink->context || !clapper_sink->gtk_context) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Failed to retrieve OpenGL context from GTK"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gst_gl_element_propagate_display_context (GST_ELEMENT (bsink),
|
||||
clapper_sink->display);
|
||||
*/
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_sink_stop_on_main (GstClapperSink *self)
|
||||
{
|
||||
if (self->window) {
|
||||
gtk_window_destroy (self->window);
|
||||
self->window = NULL;
|
||||
//self->widget = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_sink_stop (GstBaseSink *bsink)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
|
||||
|
||||
if (G_UNLIKELY (self->window != NULL)) {
|
||||
return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_clapper_sink_stop_on_main, self);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_window_show_all_and_unref (GtkWindow *window)
|
||||
{
|
||||
gtk_window_present (window);
|
||||
g_object_unref (window);
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_clapper_sink_change_state (GstElement *element, GstStateChange transition)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK_CAST (element);
|
||||
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Changing state: %s => %s",
|
||||
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
|
||||
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
if (G_UNLIKELY (ret == GST_STATE_CHANGE_FAILURE))
|
||||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:{
|
||||
GtkWindow *window = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
if (self->window)
|
||||
window = g_object_ref (self->window);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
if (window) {
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_gtk_window_show_all_and_unref, window);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
GST_OBJECT_LOCK (self);
|
||||
if (G_LIKELY (self->obj != NULL))
|
||||
gtk_clapper_object_set_buffer (self->obj, NULL);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer,
|
||||
GstClockTime *start, GstClockTime *end)
|
||||
{
|
||||
if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
|
||||
*start = GST_BUFFER_TIMESTAMP (buffer);
|
||||
|
||||
if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
|
||||
*end = *start + GST_BUFFER_DURATION (buffer);
|
||||
} else {
|
||||
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
|
||||
|
||||
if (GST_VIDEO_INFO_FPS_N (&self->v_info) > 0) {
|
||||
*end = *start + gst_util_uint64_scale_int (GST_SECOND,
|
||||
GST_VIDEO_INFO_FPS_D (&self->v_info),
|
||||
GST_VIDEO_INFO_FPS_N (&self->v_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_clapper_sink_get_caps (GstBaseSink *bsink, GstCaps *filter)
|
||||
{
|
||||
GstCaps *tmp = NULL;
|
||||
GstCaps *result = NULL;
|
||||
|
||||
tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
|
||||
|
||||
if (filter) {
|
||||
GST_DEBUG_OBJECT (bsink, "Intersecting with filter caps %" GST_PTR_FORMAT,
|
||||
filter);
|
||||
|
||||
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
|
||||
gst_caps_unref (tmp);
|
||||
} else {
|
||||
result = tmp;
|
||||
}
|
||||
|
||||
//result = gst_gl_overlay_compositor_add_caps (result);
|
||||
|
||||
GST_DEBUG_OBJECT (bsink, "Returning caps: %" GST_PTR_FORMAT, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_sink_set_caps (GstBaseSink *bsink, GstCaps *caps)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
|
||||
|
||||
GST_DEBUG ("Set caps: %" GST_PTR_FORMAT, caps);
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
if (!gst_video_info_from_caps (&self->v_info, caps)) {
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (G_UNLIKELY (self->obj == NULL)) {
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
||||
("%s", "Output widget was destroyed"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!gtk_clapper_object_set_format (self->obj, &self->v_info)) {
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_clapper_sink_show_frame (GstVideoSink *vsink, GstBuffer *buffer)
|
||||
{
|
||||
GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink);
|
||||
|
||||
GST_TRACE ("Rendering buffer: %p", buffer);
|
||||
GST_OBJECT_LOCK (self);
|
||||
|
||||
if (G_UNLIKELY (self->obj == NULL)) {
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
||||
("%s", "Output widget was destroyed"), (NULL));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
gtk_clapper_object_set_buffer (self->obj, buffer);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
74
lib/gst/plugin/gstclappersink.h
vendored
74
lib/gst/plugin/gstclappersink.h
vendored
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020-2022 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 Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "gtkclapperobject.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_CLAPPER_SINK (gst_clapper_sink_get_type ())
|
||||
#define GST_IS_CLAPPER_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_SINK))
|
||||
#define GST_IS_CLAPPER_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_SINK))
|
||||
#define GST_CLAPPER_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass))
|
||||
#define GST_CLAPPER_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass))
|
||||
#define GST_CLAPPER_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSink))
|
||||
#define GST_CLAPPER_SINK_CAST(obj) ((GstClapperSink*)(obj))
|
||||
|
||||
typedef struct _GstClapperSink GstClapperSink;
|
||||
typedef struct _GstClapperSinkClass GstClapperSinkClass;
|
||||
|
||||
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperSink, gst_object_unref)
|
||||
#endif
|
||||
|
||||
struct _GstClapperSink
|
||||
{
|
||||
GstVideoSink parent;
|
||||
|
||||
GstVideoInfo v_info;
|
||||
|
||||
GtkClapperObject *obj;
|
||||
GtkWindow *window;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
gint par_n, par_d;
|
||||
|
||||
gint display_width, display_height;
|
||||
gulong widget_destroy_id, window_destroy_id;
|
||||
};
|
||||
|
||||
struct _GstClapperSinkClass
|
||||
{
|
||||
GstVideoSinkClass parent_class;
|
||||
};
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE (clappersink);
|
||||
|
||||
GType gst_clapper_sink_get_type (void);
|
||||
|
||||
G_END_DECLS
|
71
lib/gst/plugin/gstgtkutils.c
vendored
71
lib/gst/plugin/gstgtkutils.c
vendored
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "gstgtkutils.h"
|
||||
|
||||
struct invoke_context
|
||||
{
|
||||
GThreadFunc func;
|
||||
gpointer data;
|
||||
GMutex lock;
|
||||
GCond cond;
|
||||
gboolean fired;
|
||||
|
||||
gpointer res;
|
||||
};
|
||||
|
||||
static gboolean
|
||||
gst_gtk_invoke_func (struct invoke_context *info)
|
||||
{
|
||||
g_mutex_lock (&info->lock);
|
||||
info->res = info->func (info->data);
|
||||
info->fired = TRUE;
|
||||
g_cond_signal (&info->cond);
|
||||
g_mutex_unlock (&info->lock);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gpointer
|
||||
gst_gtk_invoke_on_main (GThreadFunc func, gpointer data)
|
||||
{
|
||||
GMainContext *main_context = g_main_context_default ();
|
||||
struct invoke_context info;
|
||||
|
||||
g_mutex_init (&info.lock);
|
||||
g_cond_init (&info.cond);
|
||||
info.fired = FALSE;
|
||||
info.func = func;
|
||||
info.data = data;
|
||||
|
||||
g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func,
|
||||
&info);
|
||||
|
||||
g_mutex_lock (&info.lock);
|
||||
while (!info.fired)
|
||||
g_cond_wait (&info.cond, &info.lock);
|
||||
g_mutex_unlock (&info.lock);
|
||||
|
||||
g_mutex_clear (&info.lock);
|
||||
g_cond_clear (&info.cond);
|
||||
|
||||
return info.res;
|
||||
}
|
26
lib/gst/plugin/gstgtkutils.h
vendored
26
lib/gst/plugin/gstgtkutils.h
vendored
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data);
|
38
lib/gst/plugin/gstplugin.c
vendored
38
lib/gst/plugin/gstplugin.c
vendored
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstclappersink.h"
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin *plugin)
|
||||
{
|
||||
gboolean res = FALSE;
|
||||
|
||||
res |= GST_ELEMENT_REGISTER (clappersink, plugin);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
|
||||
clapper, "Clapper elements", plugin_init, VERSION, "LGPL",
|
||||
GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
960
lib/gst/plugin/gtkclapperobject.c
vendored
960
lib/gst/plugin/gtkclapperobject.c
vendored
@@ -1,960 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2022 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 Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
#include <gst/allocators/gstdmabuf.h>
|
||||
|
||||
#include "gtkclapperobject.h"
|
||||
#include "gstgtkutils.h"
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
#include <gdk/x11/gdkx.h>
|
||||
#if GST_GL_HAVE_PLATFORM_EGL
|
||||
#include <gst/gl/egl/gstgldisplay_egl.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
#include <gdk/wayland/gdkwayland.h>
|
||||
#include <gst/gl/wayland/gstgldisplay_wayland.h>
|
||||
#endif
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_EGL
|
||||
#include <gst/gl/egl/gsteglimage.h>
|
||||
#endif
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_clapper_object);
|
||||
#define GST_CAT_DEFAULT gst_debug_clapper_object
|
||||
|
||||
static void gtk_clapper_object_paintable_iface_init (GdkPaintableInterface *iface);
|
||||
static void gtk_clapper_object_finalize (GObject *object);
|
||||
|
||||
static const GLfloat vertices[] = {
|
||||
1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
|
||||
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
|
||||
1.0f, -1.0f, 0.0f, 1.0f, 1.0f
|
||||
};
|
||||
static const GLushort indices[] = {
|
||||
0, 1, 2, 0, 2, 3
|
||||
};
|
||||
|
||||
/* GTK4 renders things upside down ¯\_(ツ)_/¯ */
|
||||
static const gfloat vertical_flip_matrix[] = {
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, -1.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f,
|
||||
};
|
||||
|
||||
#define gtk_clapper_object_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkClapperObject, gtk_clapper_object, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_clapper_object_paintable_iface_init)
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkclapperobject", 0,
|
||||
"GTK Clapper Object"));
|
||||
|
||||
static void
|
||||
gtk_clapper_object_class_init (GtkClapperObjectClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
|
||||
gobject_class->finalize = gtk_clapper_object_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_clapper_object_init (GtkClapperObject *self)
|
||||
{
|
||||
self->last_pos_x = 0;
|
||||
self->last_pos_y = 0;
|
||||
|
||||
self->picture = (GtkPicture *) gtk_picture_new ();
|
||||
|
||||
/* We cannot do textures of 0x0px size */
|
||||
gtk_widget_set_size_request (GTK_WIDGET (self->picture), 1, 1);
|
||||
|
||||
/* Center instead of fill to not draw empty space into framebuffer */
|
||||
gtk_widget_set_halign (GTK_WIDGET (self->picture), GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_valign (GTK_WIDGET (self->picture), GTK_ALIGN_CENTER);
|
||||
|
||||
gtk_picture_set_paintable (self->picture, GDK_PAINTABLE (self));
|
||||
|
||||
gst_video_info_init (&self->v_info);
|
||||
gst_video_info_init (&self->pending_v_info);
|
||||
|
||||
g_weak_ref_init (&self->element, NULL);
|
||||
g_mutex_init (&self->lock);
|
||||
|
||||
self->gst_tex_target = GST_GL_TEXTURE_TARGET_EXTERNAL_OES;
|
||||
self->gl_tex_target = gst_gl_texture_target_to_gl (self->gst_tex_target);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_clapper_object_finalize (GObject *object)
|
||||
{
|
||||
GtkClapperObject *self = GTK_CLAPPER_OBJECT (object);
|
||||
|
||||
if (self->draw_id)
|
||||
g_source_remove (self->draw_id);
|
||||
|
||||
gst_buffer_replace (&self->pending_buffer, NULL);
|
||||
gst_buffer_replace (&self->buffer, NULL);
|
||||
|
||||
g_mutex_clear (&self->lock);
|
||||
g_weak_ref_clear (&self->element);
|
||||
|
||||
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
|
||||
}
|
||||
|
||||
static void
|
||||
_gdk_gl_context_set_active (GtkClapperObject *self, gboolean activate)
|
||||
{
|
||||
/* We wrap around a GDK context, so we need to make
|
||||
* both GTK and GStreamer aware of its active state */
|
||||
if (activate) {
|
||||
gdk_gl_context_make_current (self->gdk_context);
|
||||
gst_gl_context_activate (self->wrapped_context, TRUE);
|
||||
} else {
|
||||
gst_gl_context_activate (self->wrapped_context, FALSE);
|
||||
gdk_gl_context_clear_current ();
|
||||
}
|
||||
}
|
||||
|
||||
static GdkMemoryFormat
|
||||
video_format_to_gdk_memory_format (GstVideoFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case GST_VIDEO_FORMAT_BGR:
|
||||
return GDK_MEMORY_B8G8R8;
|
||||
case GST_VIDEO_FORMAT_RGB:
|
||||
return GDK_MEMORY_R8G8B8;
|
||||
case GST_VIDEO_FORMAT_BGRA:
|
||||
return GDK_MEMORY_B8G8R8A8;
|
||||
case GST_VIDEO_FORMAT_RGBA:
|
||||
return GDK_MEMORY_R8G8B8A8;
|
||||
case GST_VIDEO_FORMAT_ABGR:
|
||||
return GDK_MEMORY_A8B8G8R8;
|
||||
case GST_VIDEO_FORMAT_ARGB:
|
||||
return GDK_MEMORY_A8R8G8B8;
|
||||
case GST_VIDEO_FORMAT_BGRx:
|
||||
return GDK_MEMORY_B8G8R8A8_PREMULTIPLIED;
|
||||
case GST_VIDEO_FORMAT_RGBx:
|
||||
return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
|
||||
case GST_VIDEO_FORMAT_RGBA64_LE:
|
||||
case GST_VIDEO_FORMAT_RGBA64_BE:
|
||||
return GDK_MEMORY_R16G16B16A16_PREMULTIPLIED;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
/* Number not belonging to any format */
|
||||
return GDK_MEMORY_N_FORMATS;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_clapper_object_bind_buffer (GtkClapperObject *self)
|
||||
{
|
||||
const GstGLFuncs *gl = self->wrapped_context->gl_vtable;
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, self->vertex_buffer);
|
||||
|
||||
/* Load the vertex position */
|
||||
gl->VertexAttribPointer (self->attr_position, 3, GL_FLOAT, GL_FALSE,
|
||||
5 * sizeof (GLfloat), (void *) 0);
|
||||
|
||||
/* Load the texture coordinate */
|
||||
gl->VertexAttribPointer (self->attr_texture, 2, GL_FLOAT, GL_FALSE,
|
||||
5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
|
||||
|
||||
gl->EnableVertexAttribArray (self->attr_position);
|
||||
gl->EnableVertexAttribArray (self->attr_texture);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_clapper_object_unbind_buffer (GtkClapperObject *self)
|
||||
{
|
||||
const GstGLFuncs *gl = self->wrapped_context->gl_vtable;
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
||||
gl->DisableVertexAttribArray (self->attr_position);
|
||||
gl->DisableVertexAttribArray (self->attr_texture);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_clapper_object_init_redisplay (GtkClapperObject *self)
|
||||
{
|
||||
GstGLSLStage *frag_stage, *vert_stage;
|
||||
GError *error = NULL;
|
||||
gchar *frag_str;
|
||||
const GstGLFuncs *gl;
|
||||
|
||||
if (self->gst_tex_target != GST_GL_TEXTURE_TARGET_EXTERNAL_OES)
|
||||
return;
|
||||
|
||||
if (!((vert_stage = gst_glsl_stage_new_with_string (self->wrapped_context,
|
||||
GL_VERTEX_SHADER, GST_GLSL_VERSION_NONE,
|
||||
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
|
||||
gst_gl_shader_string_vertex_mat4_vertex_transform)))) {
|
||||
GST_ERROR ("Failed to retrieve vertex shader for texture target");
|
||||
return;
|
||||
}
|
||||
|
||||
frag_str = gst_gl_shader_string_fragment_external_oes_get_default (
|
||||
self->wrapped_context, GST_GLSL_VERSION_NONE,
|
||||
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY);
|
||||
frag_stage = gst_glsl_stage_new_with_string (self->wrapped_context,
|
||||
GL_FRAGMENT_SHADER, GST_GLSL_VERSION_NONE,
|
||||
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY, frag_str);
|
||||
|
||||
g_free (frag_str);
|
||||
|
||||
if (!frag_stage) {
|
||||
GST_ERROR ("Failed to retrieve fragment shader for texture target");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!((self->shader = gst_gl_shader_new_link_with_stages (self->wrapped_context,
|
||||
&error, vert_stage, frag_stage, NULL)))) {
|
||||
GST_ERROR ("Failed to initialize shader: %s", error->message);
|
||||
|
||||
g_clear_error (&error);
|
||||
gst_object_unref (vert_stage);
|
||||
gst_object_unref (frag_stage);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self->attr_position =
|
||||
gst_gl_shader_get_attribute_location (self->shader, "a_position");
|
||||
self->attr_texture =
|
||||
gst_gl_shader_get_attribute_location (self->shader, "a_texcoord");
|
||||
|
||||
gl = self->wrapped_context->gl_vtable;
|
||||
|
||||
if (gl->GenVertexArrays) {
|
||||
gl->GenVertexArrays (1, &self->vao);
|
||||
gl->BindVertexArray (self->vao);
|
||||
}
|
||||
|
||||
gl->GenBuffers (1, &self->vertex_buffer);
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, self->vertex_buffer);
|
||||
gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices, GL_STATIC_DRAW);
|
||||
|
||||
if (gl->GenVertexArrays) {
|
||||
gtk_clapper_object_bind_buffer (self);
|
||||
gl->BindVertexArray (0);
|
||||
}
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
||||
self->initiated = TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_dmabuf_into_texture (GtkClapperObject *self, gint *fds, gsize *offsets)
|
||||
{
|
||||
GstEGLImage *image;
|
||||
const GstGLFuncs *gl;
|
||||
|
||||
image = gst_egl_image_from_dmabuf_direct_target (self->wrapped_context,
|
||||
fds, offsets, &self->v_info, self->gst_tex_target);
|
||||
|
||||
/* If HW colorspace conversion failed and there is only one
|
||||
* plane, we can just make it into single EGLImage as is */
|
||||
if (!image && GST_VIDEO_INFO_N_PLANES (&self->v_info) == 1)
|
||||
image = gst_egl_image_from_dmabuf (self->wrapped_context,
|
||||
fds[0], &self->v_info, 0, offsets[0]);
|
||||
|
||||
/* Still no image? Give up then */
|
||||
if (!image)
|
||||
return FALSE;
|
||||
|
||||
gl = self->wrapped_context->gl_vtable;
|
||||
|
||||
if (!self->texture_id)
|
||||
gl->GenTextures (1, &self->texture_id);
|
||||
|
||||
gl->BindTexture (self->gl_tex_target, self->texture_id);
|
||||
|
||||
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
gl->EGLImageTargetTexture2D (self->gl_tex_target, gst_egl_image_get_image (image));
|
||||
|
||||
gl->BindTexture (GL_TEXTURE_2D, 0);
|
||||
gst_egl_image_unref (image);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_ext_texture_into_2d (GtkClapperObject *self, guint tex_width, guint tex_height)
|
||||
{
|
||||
GLuint framebuffer, new_texture_id;
|
||||
GLenum status;
|
||||
const GstGLFuncs *gl;
|
||||
|
||||
if (!self->initiated)
|
||||
gtk_clapper_object_init_redisplay (self);
|
||||
|
||||
gl = self->wrapped_context->gl_vtable;
|
||||
|
||||
gl->GenFramebuffers (1, &framebuffer);
|
||||
gl->BindFramebuffer (GL_FRAMEBUFFER, framebuffer);
|
||||
|
||||
gl->GenTextures (1, &new_texture_id);
|
||||
gl->BindTexture (GL_TEXTURE_2D, new_texture_id);
|
||||
|
||||
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
gl->TexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, tex_width, tex_height, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
gl->FramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D, new_texture_id, 0);
|
||||
|
||||
status = gl->CheckFramebufferStatus (GL_FRAMEBUFFER);
|
||||
if (G_UNLIKELY (status != GL_FRAMEBUFFER_COMPLETE)) {
|
||||
GST_ERROR ("Invalid framebuffer status: %u", status);
|
||||
|
||||
gl->BindTexture (GL_TEXTURE_2D, 0);
|
||||
gl->DeleteTextures (1, &new_texture_id);
|
||||
|
||||
gl->BindFramebuffer (GL_FRAMEBUFFER, 0);
|
||||
gl->DeleteFramebuffers (1, &framebuffer);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gl->Viewport (0, 0, tex_width, tex_height);
|
||||
|
||||
gst_gl_shader_use (self->shader);
|
||||
|
||||
if (gl->BindVertexArray)
|
||||
gl->BindVertexArray (self->vao);
|
||||
|
||||
gtk_clapper_object_bind_buffer (self);
|
||||
|
||||
gl->ActiveTexture (GL_TEXTURE0);
|
||||
gl->BindTexture (self->gl_tex_target, self->texture_id);
|
||||
|
||||
gst_gl_shader_set_uniform_1i (self->shader, "tex", 0);
|
||||
gst_gl_shader_set_uniform_matrix_4fv (self->shader,
|
||||
"u_transformation", 1, FALSE, vertical_flip_matrix);
|
||||
|
||||
gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
|
||||
|
||||
if (gl->BindVertexArray)
|
||||
gl->BindVertexArray (0);
|
||||
else
|
||||
gtk_clapper_object_unbind_buffer (self);
|
||||
|
||||
gl->BindTexture (self->gl_tex_target, 0);
|
||||
|
||||
/* Replace external OES texture with new 2D one */
|
||||
gl->DeleteTextures (1, &self->texture_id);
|
||||
self->texture_id = new_texture_id;
|
||||
|
||||
gl->BindFramebuffer (GL_FRAMEBUFFER, 0);
|
||||
gl->DeleteFramebuffers (1, &framebuffer);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GdkTexture *
|
||||
gtk_clapper_object_import_dmabuf (GtkClapperObject *self, gint *fds, gsize *offsets)
|
||||
{
|
||||
GdkTexture *texture;
|
||||
guint tex_width, tex_height;
|
||||
|
||||
_gdk_gl_context_set_active (self, TRUE);
|
||||
|
||||
if (!_dmabuf_into_texture (self, fds, offsets)) {
|
||||
_gdk_gl_context_set_active (self, FALSE);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (self->gst_tex_target) {
|
||||
case GST_GL_TEXTURE_TARGET_2D:
|
||||
tex_width = GST_VIDEO_INFO_WIDTH (&self->v_info);
|
||||
tex_height = GST_VIDEO_INFO_HEIGHT (&self->v_info);
|
||||
break;
|
||||
case GST_GL_TEXTURE_TARGET_EXTERNAL_OES:{
|
||||
GtkWidget *widget = (GtkWidget *) self->picture;
|
||||
gint scale;
|
||||
|
||||
scale = gtk_widget_get_scale_factor (widget);
|
||||
tex_width = gtk_widget_get_width (widget) * scale;
|
||||
tex_height = gtk_widget_get_height (widget) * scale;
|
||||
|
||||
if (G_LIKELY (_ext_texture_into_2d (self, tex_width, tex_height)))
|
||||
break;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
texture = gdk_gl_texture_new (self->gdk_context,
|
||||
self->texture_id, tex_width, tex_height, NULL, NULL);
|
||||
|
||||
_gdk_gl_context_set_active (self, FALSE);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
typedef gboolean (*MemTypeCheckFunc) (gpointer data);
|
||||
|
||||
static gboolean
|
||||
buffer_memory_type_check (GstBuffer *buffer, MemTypeCheckFunc func)
|
||||
{
|
||||
guint i, n_mems;
|
||||
|
||||
n_mems = gst_buffer_n_memory (buffer);
|
||||
|
||||
for (i = 0; i < n_mems; i++) {
|
||||
if (!func (gst_buffer_peek_memory (buffer, i)))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return n_mems > 0;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
verify_dmabuf_memory (GtkClapperObject *self, guint n_planes,
|
||||
gint *fds, gsize *offsets)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < n_planes; i++) {
|
||||
GstMemory *memory;
|
||||
gsize plane_size, mem_skip;
|
||||
guint mem_idx, length;
|
||||
|
||||
plane_size = gst_gl_get_plane_data_size (&self->v_info, NULL, i);
|
||||
|
||||
if (!gst_buffer_find_memory (self->buffer,
|
||||
GST_VIDEO_INFO_PLANE_OFFSET (&self->v_info, i),
|
||||
plane_size, &mem_idx, &length, &mem_skip)) {
|
||||
GST_DEBUG_OBJECT (self, "Could not find memory %u", i);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* We can't have more then one DMABuf per plane */
|
||||
if (length != 1) {
|
||||
GST_DEBUG_OBJECT (self, "Data for plane %u spans %u memories",
|
||||
i, length);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
memory = gst_buffer_peek_memory (self->buffer, mem_idx);
|
||||
|
||||
offsets[i] = memory->offset + mem_skip;
|
||||
fds[i] = gst_dmabuf_memory_get_fd (memory);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GdkTexture *
|
||||
obtain_texture_from_current_buffer (GtkClapperObject *self)
|
||||
{
|
||||
GdkTexture *texture = NULL;
|
||||
GstVideoFrame frame;
|
||||
|
||||
/* DMABuf */
|
||||
if (buffer_memory_type_check (self->buffer, (MemTypeCheckFunc) gst_is_dmabuf_memory)) {
|
||||
gsize offsets[GST_VIDEO_MAX_PLANES];
|
||||
gint fds[GST_VIDEO_MAX_PLANES];
|
||||
guint n_planes;
|
||||
|
||||
n_planes = GST_VIDEO_INFO_N_PLANES (&self->v_info);
|
||||
|
||||
if (!verify_dmabuf_memory (self, n_planes, fds, offsets)) {
|
||||
GST_ERROR ("DMABuf memory is invalid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!((texture = gtk_clapper_object_import_dmabuf (self, fds, offsets))))
|
||||
GST_ERROR ("Could not create texture from DMABuf");
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/* GL Memory */
|
||||
if (buffer_memory_type_check (self->buffer, (MemTypeCheckFunc) gst_is_gl_memory)) {
|
||||
if (gst_video_frame_map (&frame, &self->v_info, self->buffer, GST_MAP_READ | GST_MAP_GL)) {
|
||||
|
||||
GST_FIXME_OBJECT (self, "Handle GstGLMemory");
|
||||
|
||||
texture = gdk_gl_texture_new (
|
||||
self->gdk_context,
|
||||
*(guint *) GST_VIDEO_FRAME_PLANE_DATA (&frame, 0),
|
||||
GST_VIDEO_FRAME_WIDTH (&frame),
|
||||
GST_VIDEO_FRAME_HEIGHT (&frame),
|
||||
NULL, NULL);
|
||||
|
||||
gst_video_frame_unmap (&frame);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/* RAW */
|
||||
if (gst_video_frame_map (&frame, &self->v_info, self->buffer, GST_MAP_READ)) {
|
||||
GBytes *bytes;
|
||||
|
||||
/* Our ref on a buffer together with 2 buffers pool ensures that
|
||||
* current buffer will not be freed while another one is prepared */
|
||||
bytes = g_bytes_new_with_free_func (
|
||||
GST_VIDEO_FRAME_PLANE_DATA (&frame, 0),
|
||||
GST_VIDEO_FRAME_HEIGHT (&frame) * GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0),
|
||||
NULL, NULL);
|
||||
|
||||
texture = gdk_memory_texture_new (
|
||||
GST_VIDEO_FRAME_WIDTH (&frame),
|
||||
GST_VIDEO_FRAME_HEIGHT (&frame),
|
||||
video_format_to_gdk_memory_format (GST_VIDEO_FRAME_FORMAT (&frame)),
|
||||
bytes,
|
||||
GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0));
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
gst_video_frame_unmap (&frame);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
calculate_display_par (GtkClapperObject *self, GstVideoInfo *info)
|
||||
{
|
||||
gboolean success;
|
||||
gint width, height;
|
||||
gint par_n, par_d;
|
||||
gint display_par_n, display_par_d;
|
||||
|
||||
width = GST_VIDEO_INFO_WIDTH (info);
|
||||
height = GST_VIDEO_INFO_HEIGHT (info);
|
||||
|
||||
par_n = GST_VIDEO_INFO_PAR_N (info);
|
||||
par_d = GST_VIDEO_INFO_PAR_D (info);
|
||||
|
||||
if (!par_n)
|
||||
par_n = 1;
|
||||
|
||||
/* User set props */
|
||||
if (self->par_n != 0 && self->par_d != 0) {
|
||||
display_par_n = self->par_n;
|
||||
display_par_d = self->par_d;
|
||||
} else {
|
||||
display_par_n = 1;
|
||||
display_par_d = 1;
|
||||
}
|
||||
|
||||
if ((success = gst_video_calculate_display_ratio (&self->display_ratio_num,
|
||||
&self->display_ratio_den, width, height, par_n, par_d, display_par_n,
|
||||
display_par_d))) {
|
||||
GST_LOG ("PAR: %u/%u, DAR: %u/%u", par_n, par_d, display_par_n, display_par_d);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static void
|
||||
update_display_size (GtkClapperObject *self)
|
||||
{
|
||||
guint display_ratio_num, display_ratio_den;
|
||||
gint width, height;
|
||||
|
||||
display_ratio_num = self->display_ratio_num;
|
||||
display_ratio_den = self->display_ratio_den;
|
||||
|
||||
width = GST_VIDEO_INFO_WIDTH (&self->v_info);
|
||||
height = GST_VIDEO_INFO_HEIGHT (&self->v_info);
|
||||
|
||||
if (height % display_ratio_den == 0) {
|
||||
GST_DEBUG ("Keeping video height");
|
||||
|
||||
self->display_width = (guint)
|
||||
gst_util_uint64_scale_int (height, display_ratio_num, display_ratio_den);
|
||||
self->display_height = height;
|
||||
} else if (width % display_ratio_num == 0) {
|
||||
GST_DEBUG ("Keeping video width");
|
||||
|
||||
self->display_width = width;
|
||||
self->display_height = (guint)
|
||||
gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
|
||||
} else {
|
||||
GST_DEBUG ("Approximating while keeping video height");
|
||||
|
||||
self->display_width = (guint)
|
||||
gst_util_uint64_scale_int (height, display_ratio_num, display_ratio_den);
|
||||
self->display_height = height;
|
||||
}
|
||||
|
||||
self->display_aspect_ratio = ((gdouble) self->display_width
|
||||
/ (gdouble) self->display_height);
|
||||
GST_DEBUG ("Scaling to %dx%d", self->display_width, self->display_height);
|
||||
}
|
||||
|
||||
static void
|
||||
update_paintable (GtkClapperObject *self, GdkPaintable *paintable)
|
||||
{
|
||||
/* No change, so discard the new one */
|
||||
if (self->paintable == paintable) {
|
||||
if (paintable)
|
||||
g_object_unref (paintable);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->paintable)
|
||||
g_object_unref (self->paintable);
|
||||
|
||||
self->paintable = paintable;
|
||||
|
||||
if (self->pending_resize) {
|
||||
update_display_size (self);
|
||||
gdk_paintable_invalidate_size ((GdkPaintable *) self);
|
||||
|
||||
self->pending_resize = FALSE;
|
||||
}
|
||||
|
||||
gdk_paintable_invalidate_contents ((GdkPaintable *) self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
draw_on_main_cb (GtkClapperObject *self)
|
||||
{
|
||||
GdkTexture *texture;
|
||||
|
||||
GTK_CLAPPER_OBJECT_LOCK (self);
|
||||
|
||||
/* Replace used buffer and set matching v_info */
|
||||
gst_buffer_replace (&self->buffer, self->pending_buffer);
|
||||
self->v_info = self->pending_v_info;
|
||||
|
||||
texture = obtain_texture_from_current_buffer (self);
|
||||
if (texture)
|
||||
update_paintable (self, (GdkPaintable *) texture);
|
||||
|
||||
self->draw_id = 0;
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_clapper_object_set_element (GtkClapperObject *self, GstElement *element)
|
||||
{
|
||||
g_weak_ref_set (&self->element, element);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_clapper_object_set_format (GtkClapperObject *self, GstVideoInfo *v_info)
|
||||
{
|
||||
GTK_CLAPPER_OBJECT_LOCK (self);
|
||||
|
||||
if (gst_video_info_is_equal (&self->pending_v_info, v_info)) {
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!calculate_display_par (self, v_info)) {
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
self->pending_resize = TRUE;
|
||||
self->pending_v_info = *v_info;
|
||||
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_clapper_object_set_buffer (GtkClapperObject *self, GstBuffer *buffer)
|
||||
{
|
||||
GstVideoMeta *meta = NULL;
|
||||
|
||||
GTK_CLAPPER_OBJECT_LOCK (self);
|
||||
|
||||
gst_buffer_replace (&self->pending_buffer, buffer);
|
||||
|
||||
if (self->draw_id) {
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->pending_buffer)
|
||||
meta = gst_buffer_get_video_meta (self->pending_buffer);
|
||||
|
||||
/* Update pending info from video meta */
|
||||
if (meta) {
|
||||
guint i;
|
||||
|
||||
GST_VIDEO_INFO_WIDTH (&self->pending_v_info) = meta->width;
|
||||
GST_VIDEO_INFO_HEIGHT (&self->pending_v_info) = meta->height;
|
||||
|
||||
for (i = 0; i < meta->n_planes; i++) {
|
||||
GST_VIDEO_INFO_PLANE_OFFSET (&self->pending_v_info, i) = meta->offset[i];
|
||||
GST_VIDEO_INFO_PLANE_STRIDE (&self->pending_v_info, i) = meta->stride[i];
|
||||
}
|
||||
}
|
||||
|
||||
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
|
||||
(GSourceFunc) draw_on_main_cb, self, NULL);
|
||||
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
}
|
||||
|
||||
GtkClapperObject *
|
||||
gtk_clapper_object_new (void)
|
||||
{
|
||||
return g_object_new (GTK_TYPE_CLAPPER_OBJECT, NULL);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtk_clapper_object_get_widget (GtkClapperObject *self)
|
||||
{
|
||||
return (GtkWidget *) self->picture;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
wrap_current_gl (GstGLDisplay *display, GstGLPlatform platform, GstGLContext **context)
|
||||
{
|
||||
GstGLAPI gl_api = GST_GL_API_NONE;
|
||||
guint gl_major = 0, gl_minor = 0;
|
||||
|
||||
gl_api = gst_gl_context_get_current_gl_api (platform, &gl_major, &gl_minor);
|
||||
|
||||
if (gl_api) {
|
||||
const gboolean is_es = gl_api & (GST_GL_API_GLES1 | GST_GL_API_GLES2);
|
||||
gchar *gl_api_str = gst_gl_api_to_string (gl_api);
|
||||
guintptr gl_handle = 0;
|
||||
|
||||
GST_INFO ("Using GL API: %s, ver: %d.%d", gl_api_str, gl_major, gl_minor);
|
||||
g_free (gl_api_str);
|
||||
|
||||
if (is_es && platform == GST_GL_PLATFORM_EGL && !g_getenv ("GST_GL_API")) {
|
||||
GST_DEBUG ("No GST_GL_API env and GTK is using EGL GLES2, enforcing it");
|
||||
gst_gl_display_filter_gl_api (display, GST_GL_API_GLES2);
|
||||
}
|
||||
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
if (gl_handle) {
|
||||
if ((*context = gst_gl_context_new_wrapped (display,
|
||||
gl_handle, platform, gl_api)))
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
retrieve_gl_context_on_main (GtkClapperObject *self)
|
||||
{
|
||||
GdkDisplay *gdk_display;
|
||||
GstGLPlatform platform = GST_GL_PLATFORM_NONE;
|
||||
GError *error = NULL;
|
||||
|
||||
gst_clear_object (&self->wrapped_context);
|
||||
g_clear_object (&self->gdk_context);
|
||||
|
||||
gtk_widget_realize (GTK_WIDGET (self->picture));
|
||||
if (!((self->gdk_context = gdk_surface_create_gl_context (gtk_native_get_surface (
|
||||
gtk_widget_get_native (GTK_WIDGET (self->picture))), &error)))) {
|
||||
GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s",
|
||||
error ? error->message : "No error set by Gdk");
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
|
||||
//gdk_gl_context_set_use_es (self->gdk_context, TRUE);
|
||||
//gdk_gl_context_realize (self->gdk_context, &error);
|
||||
|
||||
gdk_display = gdk_gl_context_get_display (self->gdk_context);
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
if (GDK_IS_X11_DISPLAY (gdk_display)) {
|
||||
gpointer display_ptr;
|
||||
#if GST_GL_HAVE_PLATFORM_EGL && GTK_CHECK_VERSION(4,3,1)
|
||||
display_ptr = gdk_x11_display_get_egl_display (gdk_display);
|
||||
if (display_ptr)
|
||||
self->display = (GstGLDisplay *)
|
||||
gst_gl_display_egl_new_with_egl_display (display_ptr);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
|
||||
struct wl_display *wayland_display =
|
||||
gdk_wayland_display_get_wl_display (gdk_display);
|
||||
self->display = (GstGLDisplay *)
|
||||
gst_gl_display_wayland_new_with_display (wayland_display);
|
||||
}
|
||||
#endif
|
||||
if (G_UNLIKELY (!self->display)) {
|
||||
GST_WARNING_OBJECT (self, "Unknown Gdk display!");
|
||||
self->display = gst_gl_display_new ();
|
||||
}
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_EGL
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
if (GST_IS_GL_DISPLAY_WAYLAND (self->display)) {
|
||||
platform = GST_GL_PLATFORM_EGL;
|
||||
GST_DEBUG ("Using EGL on Wayland");
|
||||
goto have_platform;
|
||||
}
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
if (GST_IS_GL_DISPLAY_EGL (self->display)) {
|
||||
platform = GST_GL_PLATFORM_EGL;
|
||||
GST_DEBUG ("Using EGL on x11");
|
||||
goto have_platform;
|
||||
}
|
||||
#endif
|
||||
#endif /* GST_GL_HAVE_PLATFORM_EGL */
|
||||
|
||||
GST_ERROR ("Unsupported GL platform");
|
||||
return;
|
||||
|
||||
have_platform:
|
||||
g_object_ref (self->gdk_context);
|
||||
gdk_gl_context_make_current (self->gdk_context);
|
||||
|
||||
if (!wrap_current_gl (self->display, platform, &self->wrapped_context)) {
|
||||
GST_WARNING ("Could not retrieve Gdk OpenGL context");
|
||||
return;
|
||||
}
|
||||
|
||||
GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT, self->wrapped_context);
|
||||
gst_gl_context_activate (self->wrapped_context, TRUE);
|
||||
|
||||
if (!gst_gl_context_fill_info (self->wrapped_context, &error)) {
|
||||
GST_ERROR ("Failed to retrieve Gdk context info: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
g_clear_object (&self->wrapped_context);
|
||||
}
|
||||
|
||||
/* Deactivate in both places */
|
||||
_gdk_gl_context_set_active (self, FALSE);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_clapper_object_init_winsys (GtkClapperObject *self)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
GTK_CLAPPER_OBJECT_LOCK (self);
|
||||
|
||||
if (self->display && self->gdk_context && self->wrapped_context) {
|
||||
GST_TRACE ("Have already initialized contexts");
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!self->wrapped_context) {
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) retrieve_gl_context_on_main, self);
|
||||
GTK_CLAPPER_OBJECT_LOCK (self);
|
||||
}
|
||||
|
||||
if (!GST_IS_GL_CONTEXT (self->wrapped_context)) {
|
||||
GST_FIXME ("Could not retrieve Gdk GL context");
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GTK_CLAPPER_OBJECT_UNLOCK (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* GdkPaintableInterface
|
||||
*/
|
||||
static void
|
||||
gtk_clapper_object_paintable_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot, gdouble width, gdouble height)
|
||||
{
|
||||
GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable);
|
||||
|
||||
if (self->paintable)
|
||||
gdk_paintable_snapshot (self->paintable, snapshot, width, height);
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_clapper_object_paintable_get_current_image (GdkPaintable *paintable)
|
||||
{
|
||||
GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable);
|
||||
|
||||
return (self->paintable)
|
||||
? g_object_ref (self->paintable)
|
||||
: gdk_paintable_new_empty (0, 0);
|
||||
}
|
||||
|
||||
static gint
|
||||
gtk_clapper_object_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
||||
{
|
||||
GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable);
|
||||
|
||||
return self->display_width;
|
||||
}
|
||||
|
||||
static gint
|
||||
gtk_clapper_object_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
||||
{
|
||||
GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable);
|
||||
|
||||
return self->display_height;
|
||||
}
|
||||
|
||||
static gdouble
|
||||
gtk_clapper_object_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
|
||||
{
|
||||
GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable);
|
||||
|
||||
return self->display_aspect_ratio;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_clapper_object_paintable_iface_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->snapshot = gtk_clapper_object_paintable_snapshot;
|
||||
iface->get_current_image = gtk_clapper_object_paintable_get_current_image;
|
||||
iface->get_intrinsic_width = gtk_clapper_object_paintable_get_intrinsic_width;
|
||||
iface->get_intrinsic_height = gtk_clapper_object_paintable_get_intrinsic_height;
|
||||
iface->get_intrinsic_aspect_ratio = gtk_clapper_object_paintable_get_intrinsic_aspect_ratio;
|
||||
}
|
139
lib/gst/plugin/gtkclapperobject.h
vendored
139
lib/gst/plugin/gtkclapperobject.h
vendored
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020-2022 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 Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_CLAPPER_OBJECT (gtk_clapper_object_get_type ())
|
||||
#define GTK_IS_CLAPPER_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CLAPPER_OBJECT))
|
||||
#define GTK_IS_CLAPPER_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CLAPPER_OBJECT))
|
||||
#define GTK_CLAPPER_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CLAPPER_OBJECT, GtkClapperObjectClass))
|
||||
#define GTK_CLAPPER_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CLAPPER_OBJECT, GtkClapperObjectClass))
|
||||
#define GTK_CLAPPER_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CLAPPER_OBJECT, GtkClapperObject))
|
||||
#define GTK_CLAPPER_OBJECT_CAST(obj) ((GtkClapperObject*)(obj))
|
||||
|
||||
#define GTK_CLAPPER_OBJECT_LOCK(w) g_mutex_lock(&((GtkClapperObject*)(w))->lock)
|
||||
#define GTK_CLAPPER_OBJECT_UNLOCK(w) g_mutex_unlock(&((GtkClapperObject*)(w))->lock)
|
||||
|
||||
typedef struct _GtkClapperObject GtkClapperObject;
|
||||
typedef struct _GtkClapperObjectClass GtkClapperObjectClass;
|
||||
|
||||
struct _GtkClapperObject
|
||||
{
|
||||
GObject parent;
|
||||
|
||||
GtkPicture *picture;
|
||||
GdkPaintable *paintable;
|
||||
|
||||
GstGLDisplay *display;
|
||||
GdkGLContext *gdk_context;
|
||||
GstGLContext *wrapped_context;
|
||||
GstGLContext *gst_context;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
gint par_n, par_d;
|
||||
gboolean keep_last_frame;
|
||||
|
||||
gint display_width;
|
||||
gint display_height;
|
||||
gdouble display_aspect_ratio;
|
||||
|
||||
/* Object dimensions */
|
||||
gint scaled_width;
|
||||
gint scaled_height;
|
||||
|
||||
/* Position coords */
|
||||
gdouble last_pos_x;
|
||||
gdouble last_pos_y;
|
||||
|
||||
gboolean negotiated;
|
||||
gboolean ignore_buffers;
|
||||
|
||||
GstBuffer *pending_buffer;
|
||||
GstBuffer *buffer;
|
||||
|
||||
GstVideoInfo pending_v_info;
|
||||
GstVideoInfo v_info;
|
||||
|
||||
guint texture_id, oes_texture_id, next_texture_id;
|
||||
|
||||
/* resize */
|
||||
gboolean pending_resize;
|
||||
guint display_ratio_num;
|
||||
guint display_ratio_den;
|
||||
|
||||
/*< private >*/
|
||||
GMutex lock;
|
||||
GWeakRef element;
|
||||
|
||||
/* event controllers */
|
||||
GtkEventController *key_controller;
|
||||
GtkEventController *motion_controller;
|
||||
GtkGesture *click_gesture;
|
||||
|
||||
/* Pending draw idles callback */
|
||||
guint draw_id;
|
||||
|
||||
|
||||
|
||||
|
||||
GstGLTextureTarget gst_tex_target;
|
||||
guint gl_tex_target;
|
||||
|
||||
|
||||
GstGLShader *shader;
|
||||
|
||||
GLuint vao;
|
||||
GLuint vertex_buffer;
|
||||
GLint attr_position;
|
||||
GLint attr_texture;
|
||||
|
||||
gboolean initiated;
|
||||
|
||||
|
||||
guint frame_buffer;
|
||||
};
|
||||
|
||||
struct _GtkClapperObjectClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
GType gtk_clapper_object_get_type (void);
|
||||
GtkClapperObject * gtk_clapper_object_new (void);
|
||||
GtkWidget * gtk_clapper_object_get_widget (GtkClapperObject *object);
|
||||
|
||||
gboolean gtk_clapper_object_init_winsys (GtkClapperObject *object);
|
||||
|
||||
gboolean gtk_clapper_object_set_format (GtkClapperObject *object, GstVideoInfo *v_info);
|
||||
void gtk_clapper_object_set_buffer (GtkClapperObject *object, GstBuffer *buffer);
|
||||
void gtk_clapper_object_set_element (GtkClapperObject *object, GstElement *element);
|
||||
|
||||
G_END_DECLS
|
63
lib/gst/plugin/meson.build
vendored
63
lib/gst/plugin/meson.build
vendored
@@ -1,63 +0,0 @@
|
||||
gst_plugins_libdir = join_paths(prefix, libdir, 'gstreamer-1.0')
|
||||
|
||||
gst_clapper_plugin_args = [
|
||||
'-DHAVE_CONFIG_H',
|
||||
'-DGST_USE_UNSTABLE_API',
|
||||
]
|
||||
gst_clapper_plugin_deps = [
|
||||
gtk4_dep,
|
||||
gst_dep,
|
||||
gstbase_dep,
|
||||
gstvideo_dep,
|
||||
gstallocators_dep,
|
||||
]
|
||||
|
||||
if get_option('default_library') == 'static'
|
||||
gst_clapper_plugin_args += ['-DGST_STATIC_COMPILATION']
|
||||
endif
|
||||
|
||||
gst_clapper_plugin_option = get_option('gst-plugin')
|
||||
if gst_clapper_plugin_option.disabled()
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
foreach dep : gst_clapper_plugin_deps
|
||||
if not dep.found()
|
||||
if gst_clapper_plugin_option.enabled()
|
||||
error('GStreamer plugin was enabled, but required dependencies were not found')
|
||||
endif
|
||||
subdir_done()
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if not have_gtk_gl_windowing
|
||||
if gst_clapper_plugin_option.enabled()
|
||||
error('GTK4 widget requires GL windowing')
|
||||
else
|
||||
subdir_done()
|
||||
endif
|
||||
endif
|
||||
|
||||
if not gtk4_dep.version().version_compare('>=4.6.0')
|
||||
if gst_clapper_plugin_option.enabled()
|
||||
error('GTK4 version on this system is too old, plugin needs 4.6.0+')
|
||||
else
|
||||
subdir_done()
|
||||
endif
|
||||
endif
|
||||
|
||||
gst_clapper_plugin_sources = [
|
||||
'gstclappersink.c',
|
||||
'gtkclapperobject.c',
|
||||
'gstgtkutils.c',
|
||||
'gstplugin.c',
|
||||
]
|
||||
|
||||
library('gstclapper',
|
||||
gst_clapper_plugin_sources,
|
||||
c_args: gst_clapper_plugin_args,
|
||||
include_directories: configinc,
|
||||
dependencies: gst_clapper_plugin_deps + gtk_deps,
|
||||
install: true,
|
||||
install_dir: gst_plugins_libdir,
|
||||
)
|
44
lib/meson.build
vendored
44
lib/meson.build
vendored
@@ -1,5 +1,5 @@
|
||||
glib_req = '>= 2.56.0'
|
||||
gst_req = '>= 1.19.1'
|
||||
gst_req = '>= 1.18.0'
|
||||
|
||||
api_version = '1.0'
|
||||
libversion = meson.project_version()
|
||||
@@ -200,8 +200,6 @@ gsttag_dep = dependency('gstreamer-tag-1.0', version: gst_req,
|
||||
fallback: ['gst-plugins-base', 'tag_dep'])
|
||||
gstvideo_dep = dependency('gstreamer-video-1.0', version: gst_req,
|
||||
fallback: ['gst-plugins-base', 'video_dep'])
|
||||
gstallocators_dep = dependency('gstreamer-allocators-1.0', version: gst_req,
|
||||
fallback : ['gst-plugins-base', 'allocators_dep'])
|
||||
|
||||
# GStreamer OpenGL
|
||||
gstgl_dep = dependency('gstreamer-gl-1.0', version: gst_req,
|
||||
@@ -253,49 +251,21 @@ giounix_dep = dependency('gio-unix-2.0', version: glib_req, fallback: ['glib', '
|
||||
|
||||
cdata.set('DISABLE_ORC', 1)
|
||||
cdata.set('GST_ENABLE_EXTRA_CHECKS', get_option('devel-checks'))
|
||||
cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', 'Unknown')
|
||||
|
||||
configinc = include_directories('.')
|
||||
libsinc = include_directories('gst')
|
||||
|
||||
gir = find_program('g-ir-scanner')
|
||||
gir = find_program('g-ir-scanner', required: true)
|
||||
if not gir.found()
|
||||
error('Clapper requires GI bindings to be compiled')
|
||||
endif
|
||||
|
||||
gir_init_section = ['--add-init-section=extern void gst_init(gint*,gchar**);' + \
|
||||
'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \
|
||||
'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \
|
||||
'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \
|
||||
'gst_init(NULL,NULL);', '--quiet'
|
||||
]
|
||||
|
||||
gtk_deps = [gstgl_dep, gstglproto_dep]
|
||||
have_gtk_gl_windowing = false
|
||||
|
||||
gtk4_dep = dependency('gtk4', required: true)
|
||||
|
||||
if not gtk4_dep.version().version_compare('>=4.0.0')
|
||||
error('GTK4 version on this system is too old')
|
||||
endif
|
||||
|
||||
if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx)
|
||||
gtk_x11_dep = dependency('gtk4-x11', required: false)
|
||||
if gtk_x11_dep.found()
|
||||
gtk_deps += gtk_x11_dep
|
||||
if gst_gl_have_platform_glx
|
||||
gtk_deps += gstglx11_dep
|
||||
endif
|
||||
have_gtk_gl_windowing = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if gst_gl_have_window_wayland and gst_gl_have_platform_egl
|
||||
gtk_wayland_dep = dependency('gtk4-wayland', required: false)
|
||||
if gtk_wayland_dep.found()
|
||||
gtk_deps += [gtk_wayland_dep, gstglwayland_dep]
|
||||
have_gtk_gl_windowing = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if gst_gl_have_platform_egl
|
||||
gtk_deps += gstglegl_dep
|
||||
endif
|
||||
|
||||
subdir('gst')
|
||||
configure_file(output: 'config.h', configuration: cdata)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
project('com.github.rafostar.Clapper', 'c', 'cpp',
|
||||
version: '0.4.1',
|
||||
version: '0.4.0',
|
||||
meson_version: '>= 0.50.0',
|
||||
license: 'GPL-3.0-or-later',
|
||||
default_options: [
|
||||
@@ -19,7 +19,7 @@ datadir = join_paths(get_option('prefix'), get_option('datadir'))
|
||||
pkglibdir = join_paths(libdir, meson.project_name())
|
||||
pkgdatadir = join_paths(datadir, meson.project_name())
|
||||
|
||||
if get_option('lib') or not get_option('gst-plugin').disabled()
|
||||
if get_option('lib')
|
||||
subdir('lib')
|
||||
endif
|
||||
|
||||
|
@@ -8,11 +8,6 @@ option('lib',
|
||||
value: true,
|
||||
description: 'Build GstClapper lib'
|
||||
)
|
||||
option('gst-plugin',
|
||||
type: 'feature',
|
||||
value: 'enabled',
|
||||
description: 'Build GStreamer plugin (includes GTK video sink element)'
|
||||
)
|
||||
option('devel-checks',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
|
@@ -32,7 +32,6 @@
|
||||
"flathub/lib/libdvdnav.json",
|
||||
"flathub/lib/libass.json",
|
||||
"flathub/lib/ffmpeg.json",
|
||||
"testing/libsoup3.json",
|
||||
"testing/gstreamer.json",
|
||||
"testing/gtuber.json",
|
||||
{
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"app-id": "com.github.rafostar.Clapper",
|
||||
"runtime": "org.gnome.Platform",
|
||||
"runtime-version": "41",
|
||||
"runtime-version": "40",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
"command": "com.github.rafostar.Clapper",
|
||||
"finish-args": [
|
||||
@@ -33,8 +33,13 @@
|
||||
"flathub/lib/libass.json",
|
||||
"flathub/lib/ffmpeg.json",
|
||||
"flathub/lib/uchardet.json",
|
||||
"testing/libsoup3.json",
|
||||
"flathub/gstreamer-1.0/gstreamer.json",
|
||||
"flathub/gstreamer-1.0/gst-plugins-base.json",
|
||||
"flathub/gstreamer-1.0/gst-plugins-good.json",
|
||||
"flathub/gstreamer-1.0/gst-plugins-bad.json",
|
||||
"flathub/gstreamer-1.0/gst-plugins-ugly.json",
|
||||
"flathub/gstreamer-1.0/gst-libav.json",
|
||||
"flathub/gstreamer-1.0/gstreamer-vaapi.json",
|
||||
"flathub/lib/gtk4.json",
|
||||
"flathub/lib/libadwaita.json",
|
||||
"testing/gtuber.json",
|
||||
|
Submodule pkgs/flatpak/flathub updated: 4b3bdfab8c...829ebe9930
@@ -23,7 +23,6 @@
|
||||
"-Dintrospection=enabled",
|
||||
"-Ddoc=disabled",
|
||||
"-Dgtk_doc=disabled",
|
||||
"-Dgpl=enabled",
|
||||
|
||||
"-Dgstreamer:benchmarks=disabled",
|
||||
"-Dgstreamer:gobject-cast-checks=disabled",
|
||||
|
@@ -2,9 +2,7 @@
|
||||
"name": "gtuber",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Dintrospection=disabled",
|
||||
"-Dvapi=disabled",
|
||||
"-Dgst-gtuber=enabled"
|
||||
"-Dvapi=disabled"
|
||||
],
|
||||
"cleanup": [
|
||||
"/include",
|
||||
|
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "libsoup3",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Dintrospection=enabled",
|
||||
"-Dvapi=disabled",
|
||||
"-Dtests=false",
|
||||
"-Dsysprof=disabled",
|
||||
"-Dhttp2_tests=disabled",
|
||||
"-Dpkcs11_tests=disabled"
|
||||
],
|
||||
"cleanup": [
|
||||
"/include",
|
||||
"/lib/pkgconfig"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.gnome.org/GNOME/libsoup.git",
|
||||
"tag": "3.0.4",
|
||||
"commit": "25a728020c4b53b5db4c4c675070e92f947fbd4d"
|
||||
}
|
||||
]
|
||||
}
|
@@ -24,10 +24,9 @@
|
||||
%global gtk4_version 4.0.0
|
||||
%global meson_version 0.50
|
||||
%global glib2_version 2.56.0
|
||||
%global soup_version 3.0.0
|
||||
|
||||
Name: clapper
|
||||
Version: 0.4.1
|
||||
Version: 0.4.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Simple and modern GNOME media player
|
||||
|
||||
@@ -60,7 +59,6 @@ BuildRequires: gstreamer-plugins-base-devel >= %{gst_version}
|
||||
BuildRequires: Mesa-libGLESv2-devel
|
||||
BuildRequires: Mesa-libGLESv3-devel
|
||||
|
||||
Requires: libsoup-devel >= %{soup_version}
|
||||
Requires: gstreamer >= %{gst_version}
|
||||
Requires: gstreamer-plugins-base >= %{gst_version}
|
||||
Requires: gstreamer-plugins-good >= %{gst_version}
|
||||
@@ -82,7 +80,6 @@ BuildRequires: mesa-libGLES-devel
|
||||
BuildRequires: mesa-libGLU-devel
|
||||
BuildRequires: mesa-libEGL-devel
|
||||
|
||||
Requires: libsoup3-devel
|
||||
Requires: gstreamer1 >= %{gst_version}
|
||||
Requires: gstreamer1-plugins-base >= %{gst_version}
|
||||
Requires: gstreamer1-plugins-good >= %{gst_version}
|
||||
@@ -132,12 +129,6 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop
|
||||
%{_libdir}/%{appname}/
|
||||
|
||||
%changelog
|
||||
* Thu Feb 17 2022 Rafostar <rafostar.github@gmail.com> - 0.4.1-2
|
||||
- Require libsoup3
|
||||
|
||||
* Mon Dec 20 2021 Rafostar <rafostar.github@gmail.com> - 0.4.1-1
|
||||
- New version
|
||||
|
||||
* Sun Sep 12 2021 Rafostar <rafostar.github@gmail.com> - 0.4.0-1
|
||||
- New version
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
ca cs de es fr hu it nl pl pt pt_BR ru sv zh_CN
|
||||
ca cs de es hu it nl pl pt_BR ru zh_CN
|
||||
|
4
po/af.po
4
po/af.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
4
po/ar.po
4
po/ar.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
8
po/ca.po
8
po/ca.po
@@ -18,12 +18,12 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Obre fitxers…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Obre fitxers..."
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Obre l'URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "Obre l'URI..."
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
|
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
8
po/cs.po
8
po/cs.po
@@ -18,12 +18,12 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Otevřít soubory…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Otevřít soubory..."
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Otevřít URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "Otevřít URI..."
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
|
4
po/da.po
4
po/da.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
8
po/de.po
8
po/de.po
@@ -18,12 +18,12 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Dateien öffnen…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Dateien öffnen..."
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Webquelle öffnen…"
|
||||
msgid "Open URI..."
|
||||
msgstr "Webquelle öffnen..."
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
|
4
po/el.po
4
po/el.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
46
po/es.po
46
po/es.po
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
|
||||
"PO-Revision-Date: 2022-01-16 16:58\n"
|
||||
"PO-Revision-Date: 2021-09-14 15:25\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -18,12 +18,12 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Abrir archivos…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Abrir archivos"
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Abrir URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "Abrir URI"
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
@@ -76,7 +76,7 @@ msgstr "Tocar"
|
||||
|
||||
#: ui/help-overlay.ui:39
|
||||
msgid "Quit"
|
||||
msgstr "Renunciar"
|
||||
msgstr "Abandonar"
|
||||
|
||||
#: ui/help-overlay.ui:47
|
||||
msgid "Media"
|
||||
@@ -96,7 +96,7 @@ msgstr "Lista de reproducción"
|
||||
|
||||
#: ui/help-overlay.ui:67
|
||||
msgid "Next item"
|
||||
msgstr "Elemento siguiente"
|
||||
msgstr "Artículo siguiente"
|
||||
|
||||
#: ui/help-overlay.ui:68
|
||||
msgid "Double tap (right side)"
|
||||
@@ -104,7 +104,7 @@ msgstr "Tocar doble (lado derecho)"
|
||||
|
||||
#: ui/help-overlay.ui:74
|
||||
msgid "Previous item"
|
||||
msgstr "Elemento anterior"
|
||||
msgstr "Artículo anterior"
|
||||
|
||||
#: ui/help-overlay.ui:75
|
||||
msgid "Double tap (left side)"
|
||||
@@ -112,7 +112,7 @@ msgstr "Tocar doble (lado izquierdo)"
|
||||
|
||||
#: ui/help-overlay.ui:81
|
||||
msgid "Change repeat mode"
|
||||
msgstr "Cambiar a modo repetir"
|
||||
msgstr "Cambiar a modo repetición"
|
||||
|
||||
#: ui/help-overlay.ui:87
|
||||
msgid "Export to file"
|
||||
@@ -132,7 +132,7 @@ msgstr "Pulsación larga | Clic derecho"
|
||||
|
||||
#: ui/help-overlay.ui:105
|
||||
msgid "Seek forward"
|
||||
msgstr "Buscar siguiente"
|
||||
msgstr "Buscar adelante "
|
||||
|
||||
#: ui/help-overlay.ui:106
|
||||
msgid "Swipe right | Scroll right"
|
||||
@@ -140,7 +140,7 @@ msgstr "Deslizar a derecha | Desplazar a derecha"
|
||||
|
||||
#: ui/help-overlay.ui:112
|
||||
msgid "Seek backward"
|
||||
msgstr "Buscar anterior"
|
||||
msgstr "Buscar a atrás"
|
||||
|
||||
#: ui/help-overlay.ui:113
|
||||
msgid "Swipe left | Scroll left"
|
||||
@@ -168,11 +168,11 @@ msgstr "Fijar a mudo"
|
||||
|
||||
#: ui/help-overlay.ui:139
|
||||
msgid "Next chapter"
|
||||
msgstr "Capítulo siguiente"
|
||||
msgstr "Siguiente capítulo"
|
||||
|
||||
#: ui/help-overlay.ui:145
|
||||
msgid "Previous chapter"
|
||||
msgstr "Capítulo anterior"
|
||||
msgstr "Anterior capítulo"
|
||||
|
||||
#: ui/preferences-plugin-ranking-subpage.ui:11
|
||||
msgid "Decoders"
|
||||
@@ -184,7 +184,7 @@ msgstr "Regresar a preferencias"
|
||||
|
||||
#: ui/preferences-window.ui:16
|
||||
msgid "Behavior"
|
||||
msgstr "Configuraciones"
|
||||
msgstr "Comportamiento"
|
||||
|
||||
#: ui/preferences-window.ui:19
|
||||
msgid "Auto fullscreen"
|
||||
@@ -192,7 +192,7 @@ msgstr "Pantalla completa automática"
|
||||
|
||||
#: ui/preferences-window.ui:20
|
||||
msgid "Enter fullscreen when playlist is replaced except floating mode"
|
||||
msgstr "Entra a pantalla completa cuando se reemplaza la lista de reproducción, excepto en modo flotante"
|
||||
msgstr "Fijar a pantalla completa cuando se reemplaza la lista de reproducción, excepto el modo flotante"
|
||||
|
||||
#: ui/preferences-window.ui:26
|
||||
msgid "Ask to resume recent media"
|
||||
@@ -216,7 +216,7 @@ msgstr "Nada por hacer"
|
||||
|
||||
#: ui/preferences-window.ui:45
|
||||
msgid "Freeze last frame"
|
||||
msgstr "Detener último fotograma"
|
||||
msgstr "Congelar el último fotograma"
|
||||
|
||||
#: ui/preferences-window.ui:46
|
||||
msgid "Close the app"
|
||||
@@ -232,7 +232,7 @@ msgstr "Valor inicial personalizado"
|
||||
|
||||
#: ui/preferences-window.ui:60
|
||||
msgid "Set custom volume at startup instead of restoring it"
|
||||
msgstr "Establece un volumen personalizado al inicio en lugar de restaurarlo"
|
||||
msgstr "Establecer un volumen personalizado al inicio en lugar de restaurarlo"
|
||||
|
||||
#: ui/preferences-window.ui:64
|
||||
msgid "Volume percentage"
|
||||
@@ -292,7 +292,7 @@ msgstr "Subtítulos"
|
||||
|
||||
#: ui/preferences-window.ui:144
|
||||
msgid "Default font"
|
||||
msgstr "Fuente por defecto"
|
||||
msgstr "Fuente predeterminada"
|
||||
|
||||
#: ui/preferences-window.ui:154
|
||||
msgid "Network"
|
||||
@@ -356,7 +356,7 @@ msgstr "Renderizar sombras de ventana"
|
||||
|
||||
#: ui/preferences-window.ui:242
|
||||
msgid "Disable to increase performance when windowed"
|
||||
msgstr "Deshabilitado aumenta el rendimiento cuando se abre en ventana"
|
||||
msgstr "Desactivar para aumentar el rendimiento cuando se abre en ventana"
|
||||
|
||||
#: ui/preferences-window.ui:253
|
||||
msgid "Plugin ranking"
|
||||
@@ -364,7 +364,7 @@ msgstr "Rango de enchufes"
|
||||
|
||||
#: ui/preferences-window.ui:254
|
||||
msgid "Alter default ranks of GStreamer plugins"
|
||||
msgstr "Altera los rangos predeterminados de los enchufes de GStreamer"
|
||||
msgstr "Alterar los rangos predeterminados de los enchufes de GStreamer"
|
||||
|
||||
#: ui/preferences-window.ui:259
|
||||
msgid "Use playbin3"
|
||||
@@ -380,7 +380,7 @@ msgstr "Experimental"
|
||||
|
||||
#: ui/preferences-window.ui:268
|
||||
msgid "Use PipeWire for audio output"
|
||||
msgstr "Usar PipeWire"
|
||||
msgstr "Usar PipeWire para la salida de audio"
|
||||
|
||||
#: src/buttons.js:201
|
||||
#, javascript-format
|
||||
@@ -389,7 +389,7 @@ msgstr "Decodificador: %s"
|
||||
|
||||
#: src/dialogs.js:152
|
||||
msgid "Enter or drop URI here"
|
||||
msgstr "Introducir la URI"
|
||||
msgstr "Intoducir la URI aquí"
|
||||
|
||||
#: src/dialogs.js:157
|
||||
msgid "Cancel"
|
||||
@@ -455,5 +455,5 @@ msgstr "Canales"
|
||||
|
||||
#: src/widget.js:261
|
||||
msgid "Disabled"
|
||||
msgstr "Deshabilitar"
|
||||
msgstr "Deshabilitado"
|
||||
|
||||
|
459
po/eu.po
459
po/eu.po
@@ -1,459 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
|
||||
"PO-Revision-Date: 2022-01-16 16:58\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Basque\n"
|
||||
"Language: eu_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: clapper\n"
|
||||
"X-Crowdin-Project-ID: 473374\n"
|
||||
"X-Crowdin-Language: eu\n"
|
||||
"X-Crowdin-File: /master/po/com.github.rafostar.Clapper.pot\n"
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:20
|
||||
msgid "Shortcuts"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:26
|
||||
msgid "About Clapper"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:27
|
||||
msgid "Speed"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
|
||||
#: ui/preferences-window.ui:215
|
||||
msgid "Normal"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
|
||||
msgid "General"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:13
|
||||
msgid "Show shortcuts"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:19
|
||||
msgid "Toggle fullscreen"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:20
|
||||
msgid "Double tap | Double click"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:26
|
||||
msgid "Leave fullscreen"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:32
|
||||
msgid "Reveal OSD (fullscreen only)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:33
|
||||
msgid "Tap"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:39
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:47
|
||||
msgid "Media"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:50
|
||||
msgid "Open files"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:56 src/dialogs.js:137
|
||||
msgid "Open URI"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:64
|
||||
msgid "Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:67
|
||||
msgid "Next item"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:68
|
||||
msgid "Double tap (right side)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:74
|
||||
msgid "Previous item"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:75
|
||||
msgid "Double tap (left side)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:81
|
||||
msgid "Change repeat mode"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:87
|
||||
msgid "Export to file"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
|
||||
msgid "Playback"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:98
|
||||
msgid "Toggle play"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:99
|
||||
msgid "Long press | Right click"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:105
|
||||
msgid "Seek forward"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:106
|
||||
msgid "Swipe right | Scroll right"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:112
|
||||
msgid "Seek backward"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:113
|
||||
msgid "Swipe left | Scroll left"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:119
|
||||
msgid "Volume up"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:120
|
||||
msgid "Swipe up | Scroll up"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:126
|
||||
msgid "Volume down"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:127
|
||||
msgid "Swipe down | Scroll down"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:133
|
||||
msgid "Toggle mute"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:139
|
||||
msgid "Next chapter"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:145
|
||||
msgid "Previous chapter"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-plugin-ranking-subpage.ui:11
|
||||
msgid "Decoders"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-plugin-ranking-subpage.ui:18
|
||||
msgid "Return to the preferences"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:16
|
||||
msgid "Behavior"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:19
|
||||
msgid "Auto fullscreen"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:20
|
||||
msgid "Enter fullscreen when playlist is replaced except floating mode"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:26
|
||||
msgid "Ask to resume recent media"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:32
|
||||
msgid "Float on all workspaces"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:33
|
||||
msgid "This option only works on GNOME"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:39
|
||||
msgid "After playback"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:44
|
||||
msgid "Do nothing"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:45
|
||||
msgid "Freeze last frame"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:46
|
||||
msgid "Close the app"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:56
|
||||
msgid "Volume"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:59
|
||||
msgid "Custom initial value"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:60
|
||||
msgid "Set custom volume at startup instead of restoring it"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:64
|
||||
msgid "Volume percentage"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:75
|
||||
msgid "Seeking"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:78
|
||||
msgid "Mode"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:84
|
||||
msgid "Accurate"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:85
|
||||
msgid "Fast"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:93
|
||||
msgid "Unit"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:98
|
||||
msgid "Second"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:99
|
||||
msgid "Minute"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:100
|
||||
msgid "Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:108
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:123
|
||||
msgid "Audio"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:126
|
||||
msgid "Offset in milliseconds"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:133
|
||||
msgid "Only native audio formats"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:141
|
||||
msgid "Subtitles"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:144
|
||||
msgid "Default font"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:154
|
||||
msgid "Network"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:158
|
||||
msgid "Client"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:161
|
||||
msgid "Progressive download buffering"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:169
|
||||
msgid "Server"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:172
|
||||
msgid "Control player remotely"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:176
|
||||
msgid "Listening port"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:183
|
||||
msgid "Run web application in background"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:184
|
||||
msgid "Requires GTK compiled with Broadway backend"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:190
|
||||
msgid "Web application port"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:204
|
||||
msgid "Prefer adaptive streaming"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:210
|
||||
msgid "Max quality"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:228
|
||||
msgid "Tweaks"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:232
|
||||
msgid "Appearance"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:235
|
||||
msgid "Dark theme"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:241
|
||||
msgid "Render window shadows"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:242
|
||||
msgid "Disable to increase performance when windowed"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:253
|
||||
msgid "Plugin ranking"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:254
|
||||
msgid "Alter default ranks of GStreamer plugins"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:259
|
||||
msgid "Use playbin3"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
|
||||
msgid "Requires player restart"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
|
||||
msgid "Experimental"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:268
|
||||
msgid "Use PipeWire for audio output"
|
||||
msgstr ""
|
||||
|
||||
#: src/buttons.js:201
|
||||
#, javascript-format
|
||||
msgid "Decoder: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:152
|
||||
msgid "Enter or drop URI here"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:157
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:158
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:226
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:227
|
||||
msgid "Completed"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:235
|
||||
msgid "Resume playback?"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:289
|
||||
#, javascript-format
|
||||
msgid "GTK version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:290
|
||||
#, javascript-format
|
||||
msgid "Adwaita version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:291
|
||||
#, javascript-format
|
||||
msgid "GStreamer version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:292
|
||||
#, javascript-format
|
||||
msgid "GJS version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:300
|
||||
msgid "A GNOME media player powered by GStreamer"
|
||||
msgstr ""
|
||||
|
||||
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
|
||||
#: src/dialogs.js:305
|
||||
msgid "translator-credits"
|
||||
msgstr ""
|
||||
|
||||
#: src/revealers.js:170
|
||||
#, javascript-format
|
||||
msgid "Ends at: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
|
||||
msgid "Undetermined"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:243
|
||||
msgid "Channels"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:261
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
4
po/fi.po
4
po/fi.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
222
po/fr.po
222
po/fr.po
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
|
||||
"PO-Revision-Date: 2022-01-18 20:57\n"
|
||||
"PO-Revision-Date: 2021-09-14 15:25\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
@@ -18,442 +18,442 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Ouvrir un fichier…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Ouvrir une URL…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
msgstr "Préférences"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:20
|
||||
msgid "Shortcuts"
|
||||
msgstr "Raccourcis clavier"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:26
|
||||
msgid "About Clapper"
|
||||
msgstr "Á propos de Clapper"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:27
|
||||
msgid "Speed"
|
||||
msgstr "Vitesse"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
|
||||
#: ui/preferences-window.ui:215
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
|
||||
msgid "General"
|
||||
msgstr "Général"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:13
|
||||
msgid "Show shortcuts"
|
||||
msgstr "Montrer les raccourcis"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:19
|
||||
msgid "Toggle fullscreen"
|
||||
msgstr "Basculer en plein écran"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:20
|
||||
msgid "Double tap | Double click"
|
||||
msgstr "Tapoter/cliquer deux fois"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:26
|
||||
msgid "Leave fullscreen"
|
||||
msgstr "Sortir du plein écran"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:32
|
||||
msgid "Reveal OSD (fullscreen only)"
|
||||
msgstr "Afficher les commandes de lecture (seulement en plein écran)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:33
|
||||
msgid "Tap"
|
||||
msgstr "Taper"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:39
|
||||
msgid "Quit"
|
||||
msgstr "Quitter"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:47
|
||||
msgid "Media"
|
||||
msgstr "Média"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:50
|
||||
msgid "Open files"
|
||||
msgstr "Ouvrir un fichier"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:56 src/dialogs.js:137
|
||||
msgid "Open URI"
|
||||
msgstr "Ouvrir une URL"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:64
|
||||
msgid "Playlist"
|
||||
msgstr "Playlist"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:67
|
||||
msgid "Next item"
|
||||
msgstr "Prochain média"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:68
|
||||
msgid "Double tap (right side)"
|
||||
msgstr "Tapoter deux fois (côté droit)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:74
|
||||
msgid "Previous item"
|
||||
msgstr "Média précédent"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:75
|
||||
msgid "Double tap (left side)"
|
||||
msgstr "Tapoter deux fois (côté gauche)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:81
|
||||
msgid "Change repeat mode"
|
||||
msgstr "Changer le mode de répétition"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:87
|
||||
msgid "Export to file"
|
||||
msgstr "Exporter vers un fichier"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
|
||||
msgid "Playback"
|
||||
msgstr "Lecture"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:98
|
||||
msgid "Toggle play"
|
||||
msgstr "Relancer/stopper la lecture"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:99
|
||||
msgid "Long press | Right click"
|
||||
msgstr "Longue pression | Clic droit"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:105
|
||||
msgid "Seek forward"
|
||||
msgstr "Avancer dans la lecture"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:106
|
||||
msgid "Swipe right | Scroll right"
|
||||
msgstr "Glisser/Faire défiler vers la droite"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:112
|
||||
msgid "Seek backward"
|
||||
msgstr "Reculer dans la lecture"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:113
|
||||
msgid "Swipe left | Scroll left"
|
||||
msgstr "Glisser/Faire défiler vers la gauche"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:119
|
||||
msgid "Volume up"
|
||||
msgstr "Augmenter le volume"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:120
|
||||
msgid "Swipe up | Scroll up"
|
||||
msgstr "Glisser/Défiler vers le haut"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:126
|
||||
msgid "Volume down"
|
||||
msgstr "Baisser le volume"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:127
|
||||
msgid "Swipe down | Scroll down"
|
||||
msgstr "Glisser/Défiler vers le bas"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:133
|
||||
msgid "Toggle mute"
|
||||
msgstr "Basculer le mode silencieux"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:139
|
||||
msgid "Next chapter"
|
||||
msgstr "Prochain chapitre"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:145
|
||||
msgid "Previous chapter"
|
||||
msgstr "Chapitre précédent"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-plugin-ranking-subpage.ui:11
|
||||
msgid "Decoders"
|
||||
msgstr "Décodeurs"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-plugin-ranking-subpage.ui:18
|
||||
msgid "Return to the preferences"
|
||||
msgstr "Retourner aux préférences"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:16
|
||||
msgid "Behavior"
|
||||
msgstr "Comportement"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:19
|
||||
msgid "Auto fullscreen"
|
||||
msgstr "Lecture automatique en plein écran"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:20
|
||||
msgid "Enter fullscreen when playlist is replaced except floating mode"
|
||||
msgstr "Basculer en plein écran quand la playlisyt est remplacée sauf si le mode flottant est activé"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:26
|
||||
msgid "Ask to resume recent media"
|
||||
msgstr "Demander pour reprendre à la position des médias récents"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:32
|
||||
msgid "Float on all workspaces"
|
||||
msgstr "Flotter sur tous les bureaux virtuels"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:33
|
||||
msgid "This option only works on GNOME"
|
||||
msgstr "Cette option ne marche qu'avec GNOME"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:39
|
||||
msgid "After playback"
|
||||
msgstr "Après la lecture"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:44
|
||||
msgid "Do nothing"
|
||||
msgstr "Ne rien faire"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:45
|
||||
msgid "Freeze last frame"
|
||||
msgstr "Geler la dernière image"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:46
|
||||
msgid "Close the app"
|
||||
msgstr "Fermer l'application"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:56
|
||||
msgid "Volume"
|
||||
msgstr "Volume"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:59
|
||||
msgid "Custom initial value"
|
||||
msgstr "Valeur initiale personnalisée"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:60
|
||||
msgid "Set custom volume at startup instead of restoring it"
|
||||
msgstr "Régler une valeur personnalisée du volume au démarrage au lien de restorer la valeur précédente"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:64
|
||||
msgid "Volume percentage"
|
||||
msgstr "Pourcentage du volume"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:75
|
||||
msgid "Seeking"
|
||||
msgstr "Avancement"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:78
|
||||
msgid "Mode"
|
||||
msgstr "Comportement"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:84
|
||||
msgid "Accurate"
|
||||
msgstr "Précis"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:85
|
||||
msgid "Fast"
|
||||
msgstr "Rapide"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:93
|
||||
msgid "Unit"
|
||||
msgstr "Unité des sauts"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:98
|
||||
msgid "Second"
|
||||
msgstr "Seconde"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:99
|
||||
msgid "Minute"
|
||||
msgstr "Minute"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:100
|
||||
msgid "Percentage"
|
||||
msgstr "Pourcentage"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:108
|
||||
msgid "Value"
|
||||
msgstr "Longueur du saut"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:123
|
||||
msgid "Audio"
|
||||
msgstr "Audio"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:126
|
||||
msgid "Offset in milliseconds"
|
||||
msgstr "Décalage en millisecondes"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:133
|
||||
msgid "Only native audio formats"
|
||||
msgstr "Seulement des formats audios natifs"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:141
|
||||
msgid "Subtitles"
|
||||
msgstr "Sous-titres"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:144
|
||||
msgid "Default font"
|
||||
msgstr "Police par défaut"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:154
|
||||
msgid "Network"
|
||||
msgstr "Réseau"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:158
|
||||
msgid "Client"
|
||||
msgstr "Client"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:161
|
||||
msgid "Progressive download buffering"
|
||||
msgstr "Téléchargement progressif dans le tampon"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:169
|
||||
msgid "Server"
|
||||
msgstr "Serveur"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:172
|
||||
msgid "Control player remotely"
|
||||
msgstr "Controler le lecteur à distance"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:176
|
||||
msgid "Listening port"
|
||||
msgstr "Écouter sur le port"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:183
|
||||
msgid "Run web application in background"
|
||||
msgstr "Lancer l'application web en arrière plan"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:184
|
||||
msgid "Requires GTK compiled with Broadway backend"
|
||||
msgstr "Requiert GTK compilé avec l'interface Broadway"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:190
|
||||
msgid "Web application port"
|
||||
msgstr "Port de l'application web"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:204
|
||||
msgid "Prefer adaptive streaming"
|
||||
msgstr "Préférer le streaming adaptatif"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:210
|
||||
msgid "Max quality"
|
||||
msgstr "Qualité maximale"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:228
|
||||
msgid "Tweaks"
|
||||
msgstr "Réglages"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:232
|
||||
msgid "Appearance"
|
||||
msgstr "Apparence"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:235
|
||||
msgid "Dark theme"
|
||||
msgstr "Thème sombre"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:241
|
||||
msgid "Render window shadows"
|
||||
msgstr "Afficher les ombres de la fenêtre"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:242
|
||||
msgid "Disable to increase performance when windowed"
|
||||
msgstr "Désactiver pour améliorer les performances quand fenêtré"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:253
|
||||
msgid "Plugin ranking"
|
||||
msgstr "Liste des plugins"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:254
|
||||
msgid "Alter default ranks of GStreamer plugins"
|
||||
msgstr "Changer les options par défaut de plugins GStreamer"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:259
|
||||
msgid "Use playbin3"
|
||||
msgstr "Utiliser playbin3"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
|
||||
msgid "Requires player restart"
|
||||
msgstr "Requiert le redémarrage du lecteur"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
|
||||
msgid "Experimental"
|
||||
msgstr "Expérimental"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:268
|
||||
msgid "Use PipeWire for audio output"
|
||||
msgstr "Utiliser PipeWire pour la sortie audio"
|
||||
msgstr ""
|
||||
|
||||
#: src/buttons.js:201
|
||||
#, javascript-format
|
||||
msgid "Decoder: %s"
|
||||
msgstr "Décodeur: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:152
|
||||
msgid "Enter or drop URI here"
|
||||
msgstr "Entrer ou déposer une URL ici"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:157
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:158
|
||||
msgid "Open"
|
||||
msgstr "Ouvrir"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:226
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:227
|
||||
msgid "Completed"
|
||||
msgstr "Terminé"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:235
|
||||
msgid "Resume playback?"
|
||||
msgstr "Reprendre la lecture?"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:289
|
||||
#, javascript-format
|
||||
msgid "GTK version: %s"
|
||||
msgstr "Version de GTK: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:290
|
||||
#, javascript-format
|
||||
msgid "Adwaita version: %s"
|
||||
msgstr "Version d'Adwaita: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:291
|
||||
#, javascript-format
|
||||
msgid "GStreamer version: %s"
|
||||
msgstr "Version de GStreamer: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:292
|
||||
#, javascript-format
|
||||
msgid "GJS version: %s"
|
||||
msgstr "Version de GJS: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:300
|
||||
msgid "A GNOME media player powered by GStreamer"
|
||||
msgstr "Un lecteur multimédia pour GNOME propulsé par GStreamer"
|
||||
msgstr ""
|
||||
|
||||
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
|
||||
#: src/dialogs.js:305
|
||||
msgid "translator-credits"
|
||||
msgstr "Robin Verdenal-Tallieux"
|
||||
msgstr ""
|
||||
|
||||
#: src/revealers.js:170
|
||||
#, javascript-format
|
||||
msgid "Ends at: %s"
|
||||
msgstr "Finit à: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
|
||||
msgid "Undetermined"
|
||||
msgstr "Indéterminé"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:243
|
||||
msgid "Channels"
|
||||
msgstr "Chaines"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:261
|
||||
msgid "Disabled"
|
||||
msgstr "Désactivé"
|
||||
msgstr ""
|
||||
|
||||
|
4
po/he.po
4
po/he.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
8
po/hu.po
8
po/hu.po
@@ -18,12 +18,12 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Fájlok megnyitása…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Fájlok megnyitása..."
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "URI megnyitása…"
|
||||
msgid "Open URI..."
|
||||
msgstr "URI megnyitása..."
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
|
8
po/it.po
8
po/it.po
@@ -18,12 +18,12 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Apri i File…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Apri i File..."
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Apri URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "Apri URI..."
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
|
4
po/ja.po
4
po/ja.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
4
po/ko.po
4
po/ko.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
4
po/nl.po
4
po/nl.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Bestanden openen…"
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "URI openen…"
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
4
po/no.po
4
po/no.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
8
po/pl.po
8
po/pl.po
@@ -18,12 +18,12 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Otwórz pliki…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Otwórz pliki..."
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Otwórz URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "Otwórz URI..."
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
|
48
po/pt.po
48
po/pt.po
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
|
||||
"PO-Revision-Date: 2021-10-21 00:29\n"
|
||||
"PO-Revision-Date: 2021-09-14 15:25\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Language: pt_PT\n"
|
||||
@@ -18,89 +18,89 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Abrir Arquivos…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Abrir URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
msgstr "Preferências"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:20
|
||||
msgid "Shortcuts"
|
||||
msgstr "Atalhos"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:26
|
||||
msgid "About Clapper"
|
||||
msgstr "Sobre o Clapper"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:27
|
||||
msgid "Speed"
|
||||
msgstr "Velocidade"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
|
||||
#: ui/preferences-window.ui:215
|
||||
msgid "Normal"
|
||||
msgstr "Predefinido"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
|
||||
msgid "General"
|
||||
msgstr "Geral"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:13
|
||||
msgid "Show shortcuts"
|
||||
msgstr "Mostrar atalhos"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:19
|
||||
msgid "Toggle fullscreen"
|
||||
msgstr "Mudar modo de ecrã"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:20
|
||||
msgid "Double tap | Double click"
|
||||
msgstr "Toque duplo duplo Clique duplo"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:26
|
||||
msgid "Leave fullscreen"
|
||||
msgstr "Sair do modo de ecrã completo"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:32
|
||||
msgid "Reveal OSD (fullscreen only)"
|
||||
msgstr "Revelar OSD (apenas em tela cheia)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:33
|
||||
msgid "Tap"
|
||||
msgstr "Tocar"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:39
|
||||
msgid "Quit"
|
||||
msgstr "Sair"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:47
|
||||
msgid "Media"
|
||||
msgstr "Multimédia"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:50
|
||||
msgid "Open files"
|
||||
msgstr "Abrir ficheiro"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:56 src/dialogs.js:137
|
||||
msgid "Open URI"
|
||||
msgstr "Abrir URI"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:64
|
||||
msgid "Playlist"
|
||||
msgstr "Lista de reprodução"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:67
|
||||
msgid "Next item"
|
||||
msgstr "Próximo item"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:68
|
||||
msgid "Double tap (right side)"
|
||||
msgstr "Toque duplo (lado direito)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:74
|
||||
msgid "Previous item"
|
||||
|
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Abrir Arquivos"
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "Abrir URI"
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
4
po/ro.po
4
po/ro.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
10
po/ru.po
10
po/ru.po
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
|
||||
"PO-Revision-Date: 2022-01-16 14:15\n"
|
||||
"PO-Revision-Date: 2021-09-17 08:56\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru_RU\n"
|
||||
@@ -18,12 +18,12 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Открыть файлы…"
|
||||
msgid "Open Files..."
|
||||
msgstr "Открыть файлы ..."
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Открыть URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "Открыть URI..."
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
|
4
po/sr.po
4
po/sr.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
222
po/sv.po
222
po/sv.po
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
|
||||
"PO-Revision-Date: 2022-01-16 14:15\n"
|
||||
"PO-Revision-Date: 2021-09-14 15:24\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Language: sv_SE\n"
|
||||
@@ -18,442 +18,442 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgstr "Öppna filer…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgstr "Öppna URL…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
msgid "Preferences"
|
||||
msgstr "Inställningar"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:20
|
||||
msgid "Shortcuts"
|
||||
msgstr "Tangentbordsgenvägar"
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:26
|
||||
msgid "About Clapper"
|
||||
msgstr "Om Clapper"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:27
|
||||
msgid "Speed"
|
||||
msgstr "Hastighet"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
|
||||
#: ui/preferences-window.ui:215
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
|
||||
msgid "General"
|
||||
msgstr "Allmänt"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:13
|
||||
msgid "Show shortcuts"
|
||||
msgstr "Visa tangentbordsgenvägar"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:19
|
||||
msgid "Toggle fullscreen"
|
||||
msgstr "Växla helskärmsläge"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:20
|
||||
msgid "Double tap | Double click"
|
||||
msgstr "Dubbeltryck | Dubbelklicka"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:26
|
||||
msgid "Leave fullscreen"
|
||||
msgstr "Lämna helskärmsläge"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:32
|
||||
msgid "Reveal OSD (fullscreen only)"
|
||||
msgstr "Visa OSD (endast helskärmsläge)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:33
|
||||
msgid "Tap"
|
||||
msgstr "Tryck"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:39
|
||||
msgid "Quit"
|
||||
msgstr "Avsluta"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:47
|
||||
msgid "Media"
|
||||
msgstr "Media"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:50
|
||||
msgid "Open files"
|
||||
msgstr "Öppna filer"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:56 src/dialogs.js:137
|
||||
msgid "Open URI"
|
||||
msgstr "Öppna URI"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:64
|
||||
msgid "Playlist"
|
||||
msgstr "Spellista"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:67
|
||||
msgid "Next item"
|
||||
msgstr "Nästa föremål"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:68
|
||||
msgid "Double tap (right side)"
|
||||
msgstr "Dubbeltryck (höger sida)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:74
|
||||
msgid "Previous item"
|
||||
msgstr "Föregående föremål"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:75
|
||||
msgid "Double tap (left side)"
|
||||
msgstr "Dubbeltryck (vänster sida)"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:81
|
||||
msgid "Change repeat mode"
|
||||
msgstr "Ändra upprepningsläge"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:87
|
||||
msgid "Export to file"
|
||||
msgstr "Exportera till fil"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
|
||||
msgid "Playback"
|
||||
msgstr "Uppspelning"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:98
|
||||
msgid "Toggle play"
|
||||
msgstr "Spela/pausa"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:99
|
||||
msgid "Long press | Right click"
|
||||
msgstr "Långtryck | Högerklicka"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:105
|
||||
msgid "Seek forward"
|
||||
msgstr "Spola framåt"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:106
|
||||
msgid "Swipe right | Scroll right"
|
||||
msgstr "Svep höger | Skrolla höger"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:112
|
||||
msgid "Seek backward"
|
||||
msgstr "Spola bakåt"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:113
|
||||
msgid "Swipe left | Scroll left"
|
||||
msgstr "Svep vänster | Skrolla vänster"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:119
|
||||
msgid "Volume up"
|
||||
msgstr "Höj volymen"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:120
|
||||
msgid "Swipe up | Scroll up"
|
||||
msgstr "Svep uppåt | Skrolla uppåt"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:126
|
||||
msgid "Volume down"
|
||||
msgstr "Sänk volymen"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:127
|
||||
msgid "Swipe down | Scroll down"
|
||||
msgstr "Svep nedåt | Skrolla nedåt"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:133
|
||||
msgid "Toggle mute"
|
||||
msgstr "Växla ljudet på/av"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:139
|
||||
msgid "Next chapter"
|
||||
msgstr "Nästa kapitel"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:145
|
||||
msgid "Previous chapter"
|
||||
msgstr "Föregående kapitel"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-plugin-ranking-subpage.ui:11
|
||||
msgid "Decoders"
|
||||
msgstr "Avkodare"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-plugin-ranking-subpage.ui:18
|
||||
msgid "Return to the preferences"
|
||||
msgstr "Återgå till inställningarna"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:16
|
||||
msgid "Behavior"
|
||||
msgstr "Beteende"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:19
|
||||
msgid "Auto fullscreen"
|
||||
msgstr "Automatiskt helskärmsläge"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:20
|
||||
msgid "Enter fullscreen when playlist is replaced except floating mode"
|
||||
msgstr "Växla till fullskärmsläge när en spellista ersätts, förutom i flytande läge"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:26
|
||||
msgid "Ask to resume recent media"
|
||||
msgstr "Be att återuppta senaste media"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:32
|
||||
msgid "Float on all workspaces"
|
||||
msgstr "Flyt på alla arbetsytor"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:33
|
||||
msgid "This option only works on GNOME"
|
||||
msgstr "Det här alternativet fungerar endast i GNOME"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:39
|
||||
msgid "After playback"
|
||||
msgstr "Efter uppspelning"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:44
|
||||
msgid "Do nothing"
|
||||
msgstr "Gör ingenting"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:45
|
||||
msgid "Freeze last frame"
|
||||
msgstr "Frys sista bildruta"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:46
|
||||
msgid "Close the app"
|
||||
msgstr "Stäng appen"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:56
|
||||
msgid "Volume"
|
||||
msgstr "Volym"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:59
|
||||
msgid "Custom initial value"
|
||||
msgstr "Anpassat startvärde"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:60
|
||||
msgid "Set custom volume at startup instead of restoring it"
|
||||
msgstr "Ställ in anpassad volym vid start istället för att återställa den"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:64
|
||||
msgid "Volume percentage"
|
||||
msgstr "Volymprocent"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:75
|
||||
msgid "Seeking"
|
||||
msgstr "Spolning"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:78
|
||||
msgid "Mode"
|
||||
msgstr "Läge"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:84
|
||||
msgid "Accurate"
|
||||
msgstr "Riktig"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:85
|
||||
msgid "Fast"
|
||||
msgstr "Snabbt"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:93
|
||||
msgid "Unit"
|
||||
msgstr "Enhet"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:98
|
||||
msgid "Second"
|
||||
msgstr "Sekund"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:99
|
||||
msgid "Minute"
|
||||
msgstr "Minut"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:100
|
||||
msgid "Percentage"
|
||||
msgstr "Procent"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:108
|
||||
msgid "Value"
|
||||
msgstr "Värde"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:123
|
||||
msgid "Audio"
|
||||
msgstr "Ljud"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:126
|
||||
msgid "Offset in milliseconds"
|
||||
msgstr "Förskjutning i millisekunder"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:133
|
||||
msgid "Only native audio formats"
|
||||
msgstr "Endast inbyggda ljudformat"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:141
|
||||
msgid "Subtitles"
|
||||
msgstr "Undertexter"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:144
|
||||
msgid "Default font"
|
||||
msgstr "Standardteckensnitt"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:154
|
||||
msgid "Network"
|
||||
msgstr "Nätverk"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:158
|
||||
msgid "Client"
|
||||
msgstr "Klient"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:161
|
||||
msgid "Progressive download buffering"
|
||||
msgstr "Progressiv nedladdningsbuffert"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:169
|
||||
msgid "Server"
|
||||
msgstr "Server"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:172
|
||||
msgid "Control player remotely"
|
||||
msgstr "Fjärrstyra spelaren"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:176
|
||||
msgid "Listening port"
|
||||
msgstr "Lyssningsport"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:183
|
||||
msgid "Run web application in background"
|
||||
msgstr "Kör webbprogram i bakgrunden"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:184
|
||||
msgid "Requires GTK compiled with Broadway backend"
|
||||
msgstr "Kräver GTK kompilerad med Broadway backend"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:190
|
||||
msgid "Web application port"
|
||||
msgstr "Port för webbprogram"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:204
|
||||
msgid "Prefer adaptive streaming"
|
||||
msgstr "Föredra adaptiv streaming"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:210
|
||||
msgid "Max quality"
|
||||
msgstr "Max kvalitet"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:228
|
||||
msgid "Tweaks"
|
||||
msgstr "Tweaks"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:232
|
||||
msgid "Appearance"
|
||||
msgstr "Utseende"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:235
|
||||
msgid "Dark theme"
|
||||
msgstr "Mörkt Tema"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:241
|
||||
msgid "Render window shadows"
|
||||
msgstr "Rendera fönsterskuggor"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:242
|
||||
msgid "Disable to increase performance when windowed"
|
||||
msgstr "Inaktivera för att öka prestanda när fönsterläge är på"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:253
|
||||
msgid "Plugin ranking"
|
||||
msgstr "Rangordning av plugin"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:254
|
||||
msgid "Alter default ranks of GStreamer plugins"
|
||||
msgstr "Ändra standardrankningar för GStreamer-plugins"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:259
|
||||
msgid "Use playbin3"
|
||||
msgstr "Använd playbin3"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
|
||||
msgid "Requires player restart"
|
||||
msgstr "Kräver omstart av spelaren"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
|
||||
msgid "Experimental"
|
||||
msgstr "Exprimentalt"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:268
|
||||
msgid "Use PipeWire for audio output"
|
||||
msgstr "Använd PipeWire för Ljudutgång"
|
||||
msgstr ""
|
||||
|
||||
#: src/buttons.js:201
|
||||
#, javascript-format
|
||||
msgid "Decoder: %s"
|
||||
msgstr "Avkodare: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:152
|
||||
msgid "Enter or drop URI here"
|
||||
msgstr "Ange eller släpp URI här"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:157
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:158
|
||||
msgid "Open"
|
||||
msgstr "Öppna"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:226
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:227
|
||||
msgid "Completed"
|
||||
msgstr "Klar"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:235
|
||||
msgid "Resume playback?"
|
||||
msgstr "Återuppta uppspelningen?"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:289
|
||||
#, javascript-format
|
||||
msgid "GTK version: %s"
|
||||
msgstr "GTK version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:290
|
||||
#, javascript-format
|
||||
msgid "Adwaita version: %s"
|
||||
msgstr "Adwaita version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:291
|
||||
#, javascript-format
|
||||
msgid "GStreamer version: %s"
|
||||
msgstr "GStreamer version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:292
|
||||
#, javascript-format
|
||||
msgid "GJS version: %s"
|
||||
msgstr "GJS version: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs.js:300
|
||||
msgid "A GNOME media player powered by GStreamer"
|
||||
msgstr "En media spelare för GNOME som drivs av GStreamer"
|
||||
msgstr ""
|
||||
|
||||
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
|
||||
#: src/dialogs.js:305
|
||||
msgid "translator-credits"
|
||||
msgstr "SA ST (sastofficial)"
|
||||
msgstr ""
|
||||
|
||||
#: src/revealers.js:170
|
||||
#, javascript-format
|
||||
msgid "Ends at: %s"
|
||||
msgstr "Slutar vid: %s"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
|
||||
msgid "Undetermined"
|
||||
msgstr "Obestämd"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:243
|
||||
msgid "Channels"
|
||||
msgstr "Kanaler"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:261
|
||||
msgid "Disabled"
|
||||
msgstr "Avstängd"
|
||||
msgstr ""
|
||||
|
||||
|
4
po/tr.po
4
po/tr.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
4
po/uk.po
4
po/uk.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
4
po/vi.po
4
po/vi.po
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
40
po/zh_CN.po
40
po/zh_CN.po
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: clapper\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
|
||||
"PO-Revision-Date: 2022-01-16 14:15\n"
|
||||
"PO-Revision-Date: 2021-09-24 14:11\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr "打开文件…"
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr "打开 URI…"
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
@@ -39,7 +39,7 @@ msgstr "关于 Claper"
|
||||
|
||||
#: ui/elapsed-time-button.ui:27
|
||||
msgid "Speed"
|
||||
msgstr "速度"
|
||||
msgstr ""
|
||||
|
||||
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
|
||||
#: ui/preferences-window.ui:215
|
||||
@@ -48,7 +48,7 @@ msgstr "一般"
|
||||
|
||||
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
|
||||
msgid "General"
|
||||
msgstr "常规"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:13
|
||||
msgid "Show shortcuts"
|
||||
@@ -112,7 +112,7 @@ msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:81
|
||||
msgid "Change repeat mode"
|
||||
msgstr "更改循环模式"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:87
|
||||
msgid "Export to file"
|
||||
@@ -128,7 +128,7 @@ msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:99
|
||||
msgid "Long press | Right click"
|
||||
msgstr "长按 | 右键点击"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:105
|
||||
msgid "Seek forward"
|
||||
@@ -148,19 +148,19 @@ msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:119
|
||||
msgid "Volume up"
|
||||
msgstr "提高音量"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:120
|
||||
msgid "Swipe up | Scroll up"
|
||||
msgstr "向上滑动 | 向上滚动"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:126
|
||||
msgid "Volume down"
|
||||
msgstr "降低音量"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:127
|
||||
msgid "Swipe down | Scroll down"
|
||||
msgstr "向下滑动 | 向下滚动"
|
||||
msgstr ""
|
||||
|
||||
#: ui/help-overlay.ui:133
|
||||
msgid "Toggle mute"
|
||||
@@ -208,19 +208,19 @@ msgstr "此选项仅适用于 GNOME"
|
||||
|
||||
#: ui/preferences-window.ui:39
|
||||
msgid "After playback"
|
||||
msgstr "播放结束后"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:44
|
||||
msgid "Do nothing"
|
||||
msgstr "不执行任何操作"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:45
|
||||
msgid "Freeze last frame"
|
||||
msgstr "停留在最后一帧"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:46
|
||||
msgid "Close the app"
|
||||
msgstr "关闭应用程序"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:56
|
||||
msgid "Volume"
|
||||
@@ -240,7 +240,7 @@ msgstr "音量百分比"
|
||||
|
||||
#: ui/preferences-window.ui:75
|
||||
msgid "Seeking"
|
||||
msgstr "定位播放"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:78
|
||||
msgid "Mode"
|
||||
@@ -308,7 +308,7 @@ msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:169
|
||||
msgid "Server"
|
||||
msgstr "服务器"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:172
|
||||
msgid "Control player remotely"
|
||||
@@ -328,7 +328,7 @@ msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:190
|
||||
msgid "Web application port"
|
||||
msgstr "Web 应用程序端口"
|
||||
msgstr ""
|
||||
|
||||
#: ui/preferences-window.ui:204
|
||||
msgid "Prefer adaptive streaming"
|
||||
@@ -443,7 +443,7 @@ msgstr ""
|
||||
#: src/revealers.js:170
|
||||
#, javascript-format
|
||||
msgid "Ends at: %s"
|
||||
msgstr "结束于:%s"
|
||||
msgstr ""
|
||||
|
||||
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
|
||||
msgid "Undetermined"
|
||||
@@ -455,5 +455,5 @@ msgstr "声道"
|
||||
|
||||
#: src/widget.js:261
|
||||
msgid "Disabled"
|
||||
msgstr "禁用"
|
||||
msgstr ""
|
||||
|
||||
|
@@ -18,11 +18,11 @@ msgstr ""
|
||||
"X-Crowdin-File-ID: 31\n"
|
||||
|
||||
#: ui/clapper.ui:6
|
||||
msgid "Open Files…"
|
||||
msgid "Open Files..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:10
|
||||
msgid "Open URI…"
|
||||
msgid "Open URI..."
|
||||
msgstr ""
|
||||
|
||||
#: ui/clapper.ui:16 ui/preferences-window.ui:4
|
||||
|
@@ -96,6 +96,13 @@ class ClapperAppBase extends Gtk.Application
|
||||
if(accels)
|
||||
this.set_accels_for_action(`app.${name}`, accels);
|
||||
}
|
||||
|
||||
const gtkSettings = Gtk.Settings.get_default();
|
||||
settings.bind(
|
||||
'dark-theme', gtkSettings,
|
||||
'gtk-application-prefer-dark-theme',
|
||||
Gio.SettingsBindFlags.GET
|
||||
);
|
||||
this.doneFirstActivate = true;
|
||||
}
|
||||
});
|
||||
|
25
src/appRemote.js
Normal file
25
src/appRemote.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const { GObject } = imports.gi;
|
||||
const { AppBase } = imports.src.appBase;
|
||||
const { HeaderBarRemote } = imports.src.headerbarRemote;
|
||||
const { WidgetRemote } = imports.src.widgetRemote;
|
||||
|
||||
var AppRemote = GObject.registerClass({
|
||||
GTypeName: 'ClapperAppRemote',
|
||||
},
|
||||
class ClapperAppRemote extends AppBase
|
||||
{
|
||||
vfunc_startup()
|
||||
{
|
||||
super.vfunc_startup();
|
||||
|
||||
const window = this.active_window;
|
||||
|
||||
const clapperWidget = new WidgetRemote();
|
||||
window.set_child(clapperWidget);
|
||||
|
||||
const headerBar = new HeaderBarRemote();
|
||||
window.set_titlebar(headerBar);
|
||||
|
||||
window.maximize();
|
||||
}
|
||||
});
|
@@ -165,7 +165,7 @@ class ClapperElapsedTimeButton extends PopoverButtonBase
|
||||
|
||||
setInitialState()
|
||||
{
|
||||
this.label = '00∶00∕00∶00';
|
||||
this.label = '00:00/00:00';
|
||||
}
|
||||
|
||||
setFullscreenMode(isFullscreen, isMobileMonitor)
|
||||
|
6
src/controls.js
vendored
6
src/controls.js
vendored
@@ -7,6 +7,8 @@ const Revealers = imports.src.revealers;
|
||||
const { debug } = Debug;
|
||||
const { settings } = Misc;
|
||||
|
||||
const INITIAL_ELAPSED = '00:00/00:00';
|
||||
|
||||
var Controls = GObject.registerClass({
|
||||
GTypeName: 'ClapperControls',
|
||||
},
|
||||
@@ -27,7 +29,7 @@ class ClapperControls extends Gtk.Box
|
||||
this.isMobile = false;
|
||||
|
||||
this.showHours = false;
|
||||
this.durationFormatted = '00∶00';
|
||||
this.durationFormatted = '00:00';
|
||||
this.revealersArr = [];
|
||||
this.chapters = null;
|
||||
|
||||
@@ -146,7 +148,7 @@ class ClapperControls extends Gtk.Box
|
||||
value = value || 0;
|
||||
|
||||
const elapsed = Misc.getFormattedTime(value, this.showHours)
|
||||
+ '∕' + this.durationFormatted;
|
||||
+ '/' + this.durationFormatted;
|
||||
|
||||
this.elapsedButton.label = elapsed;
|
||||
}
|
||||
|
70
src/daemon.js
Normal file
70
src/daemon.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const { Gio, GLib, GObject } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
|
||||
const { debug } = Debug;
|
||||
|
||||
var Daemon = GObject.registerClass({
|
||||
GTypeName: 'ClapperDaemon',
|
||||
},
|
||||
class ClapperDaemon extends Gio.SubprocessLauncher
|
||||
{
|
||||
_init()
|
||||
{
|
||||
const port = ARGV[0] || 8080;
|
||||
|
||||
/* FIXME: show output when debugging is on */
|
||||
const flags = Gio.SubprocessFlags.STDOUT_SILENCE
|
||||
| Gio.SubprocessFlags.STDERR_SILENCE;
|
||||
|
||||
super._init({ flags });
|
||||
|
||||
this.errMsg = 'exited with error or was forced to close';
|
||||
this.loop = GLib.MainLoop.new(null, false);
|
||||
|
||||
this.broadwayd = this.spawnv(['gtk4-broadwayd', '--port=' + port]);
|
||||
this.broadwayd.wait_async(null, this._onBroadwaydClosed.bind(this));
|
||||
|
||||
this.setenv('GDK_BACKEND', 'broadway', true);
|
||||
|
||||
const remoteApp = this.spawnv(['com.github.rafostar.Clapper.Remote']);
|
||||
remoteApp.wait_async(null, this._onRemoteClosed.bind(this));
|
||||
|
||||
this.loop.run();
|
||||
}
|
||||
|
||||
_checkProcResult(proc, result)
|
||||
{
|
||||
let hadError = false;
|
||||
|
||||
try {
|
||||
hadError = proc.wait_finish(result);
|
||||
}
|
||||
catch(err) {
|
||||
debug(err);
|
||||
}
|
||||
|
||||
return hadError;
|
||||
}
|
||||
|
||||
_onBroadwaydClosed(proc, result)
|
||||
{
|
||||
const hadError = this._checkProcResult(proc, result);
|
||||
|
||||
if(hadError)
|
||||
debug(`broadwayd ${this.errMsg}`);
|
||||
|
||||
this.broadwayd = null;
|
||||
this.loop.quit();
|
||||
}
|
||||
|
||||
_onRemoteClosed(proc, result)
|
||||
{
|
||||
const hadError = this._checkProcResult(proc, result);
|
||||
|
||||
if(hadError)
|
||||
debug(`remote app ${this.errMsg}`);
|
||||
|
||||
if(this.broadwayd)
|
||||
this.broadwayd.force_exit();
|
||||
}
|
||||
});
|
165
src/dash.js
Normal file
165
src/dash.js
Normal file
@@ -0,0 +1,165 @@
|
||||
const Debug = imports.src.debug;
|
||||
const FileOps = imports.src.fileOps;
|
||||
const Misc = imports.src.misc;
|
||||
|
||||
const { debug } = Debug;
|
||||
|
||||
function generateDash(dashInfo)
|
||||
{
|
||||
debug('generating dash');
|
||||
|
||||
const bufferSec = Math.min(4, dashInfo.duration);
|
||||
|
||||
const dash = [
|
||||
`<?xml version="1.0" encoding="UTF-8"?>`,
|
||||
`<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
|
||||
` xmlns="urn:mpeg:dash:schema:mpd:2011"`,
|
||||
` xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"`,
|
||||
` type="static"`,
|
||||
` mediaPresentationDuration="PT${dashInfo.duration}S"`,
|
||||
` minBufferTime="PT${bufferSec}S"`,
|
||||
` profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">`,
|
||||
` <Period>`
|
||||
];
|
||||
|
||||
for(let adaptation of dashInfo.adaptations)
|
||||
dash.push(_addAdaptationSet(adaptation));
|
||||
|
||||
dash.push(
|
||||
` </Period>`,
|
||||
`</MPD>`
|
||||
);
|
||||
|
||||
debug('dash generated');
|
||||
|
||||
return dash.join('\n');
|
||||
}
|
||||
|
||||
function _addAdaptationSet(streamsArr)
|
||||
{
|
||||
/* We just need it for adaptation type,
|
||||
* so any stream will do */
|
||||
const { mimeInfo } = streamsArr[0];
|
||||
|
||||
const adaptArr = [
|
||||
`contentType="${mimeInfo.content}"`,
|
||||
`mimeType="${mimeInfo.type}"`,
|
||||
`subsegmentAlignment="true"`,
|
||||
`subsegmentStartsWithSAP="1"`,
|
||||
];
|
||||
|
||||
const widthArr = [];
|
||||
const heightArr = [];
|
||||
const fpsArr = [];
|
||||
|
||||
const representations = [];
|
||||
|
||||
for(let stream of streamsArr) {
|
||||
/* No point parsing if no URL */
|
||||
if(!stream.url)
|
||||
continue;
|
||||
|
||||
if(stream.width && stream.height) {
|
||||
widthArr.push(stream.width);
|
||||
heightArr.push(stream.height);
|
||||
}
|
||||
if(stream.fps)
|
||||
fpsArr.push(stream.fps);
|
||||
|
||||
representations.push(_getStreamRepresentation(stream));
|
||||
}
|
||||
|
||||
if(widthArr.length && heightArr.length) {
|
||||
const maxWidth = Math.max.apply(null, widthArr);
|
||||
const maxHeight = Math.max.apply(null, heightArr);
|
||||
const par = _getPar(maxWidth, maxHeight);
|
||||
|
||||
adaptArr.push(`maxWidth="${maxWidth}"`);
|
||||
adaptArr.push(`maxHeight="${maxHeight}"`);
|
||||
adaptArr.push(`par="${par}"`);
|
||||
}
|
||||
if(fpsArr.length) {
|
||||
const maxFps = Math.max.apply(null, fpsArr);
|
||||
|
||||
adaptArr.push(`maxFrameRate="${maxFps}"`);
|
||||
}
|
||||
|
||||
const adaptationSet = [
|
||||
` <AdaptationSet ${adaptArr.join(' ')}>`,
|
||||
representations.join('\n'),
|
||||
` </AdaptationSet>`
|
||||
];
|
||||
|
||||
return adaptationSet.join('\n');
|
||||
}
|
||||
|
||||
function _getStreamRepresentation(stream)
|
||||
{
|
||||
const repOptsArr = [
|
||||
`id="${stream.itag}"`,
|
||||
`codecs="${stream.mimeInfo.codecs}"`,
|
||||
`bandwidth="${stream.bitrate}"`,
|
||||
];
|
||||
|
||||
if(stream.width && stream.height) {
|
||||
repOptsArr.push(`width="${stream.width}"`);
|
||||
repOptsArr.push(`height="${stream.height}"`);
|
||||
repOptsArr.push(`sar="1:1"`);
|
||||
}
|
||||
if(stream.fps)
|
||||
repOptsArr.push(`frameRate="${stream.fps}"`);
|
||||
|
||||
const repArr = [
|
||||
` <Representation ${repOptsArr.join(' ')}>`,
|
||||
];
|
||||
if(stream.audioChannels) {
|
||||
const audioConfArr = [
|
||||
`schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"`,
|
||||
`value="${stream.audioChannels}"`,
|
||||
];
|
||||
repArr.push(` <AudioChannelConfiguration ${audioConfArr.join(' ')}/>`);
|
||||
}
|
||||
|
||||
repArr.push(
|
||||
` <BaseURL>${stream.url}</BaseURL>`
|
||||
);
|
||||
|
||||
if(stream.indexRange) {
|
||||
const segRange = `${stream.indexRange.start}-${stream.indexRange.end}`;
|
||||
repArr.push(
|
||||
` <SegmentBase indexRange="${segRange}">`
|
||||
);
|
||||
if(stream.initRange) {
|
||||
const initRange = `${stream.initRange.start}-${stream.initRange.end}`;
|
||||
repArr.push(
|
||||
` <Initialization range="${initRange}"/>`
|
||||
);
|
||||
}
|
||||
repArr.push(
|
||||
` </SegmentBase>`
|
||||
);
|
||||
}
|
||||
|
||||
repArr.push(
|
||||
` </Representation>`
|
||||
);
|
||||
|
||||
return repArr.join('\n');
|
||||
}
|
||||
|
||||
function _getPar(width, height)
|
||||
{
|
||||
const gcd = _getGCD(width, height);
|
||||
|
||||
width /= gcd;
|
||||
height /= gcd;
|
||||
|
||||
return `${width}:${height}`;
|
||||
}
|
||||
|
||||
function _getGCD(width, height)
|
||||
{
|
||||
return (height)
|
||||
? _getGCD(height, width % height)
|
||||
: width;
|
||||
}
|
15
src/debug.js
15
src/debug.js
@@ -14,11 +14,13 @@ const clapperDebugger = new Debug.Debugger('Clapper', {
|
||||
}),
|
||||
high_precision: true,
|
||||
});
|
||||
clapperDebugger.enabled = (
|
||||
|
||||
var enabled = (
|
||||
clapperDebugger.enabled
|
||||
|| G_DEBUG_ENV != null
|
||||
&& G_DEBUG_ENV.includes('Clapper')
|
||||
);
|
||||
clapperDebugger.enabled = enabled;
|
||||
|
||||
function _logStructured(debuggerName, msg, level)
|
||||
{
|
||||
@@ -32,15 +34,11 @@ function _logStructured(debuggerName, msg, level)
|
||||
function _debug(debuggerName, msg)
|
||||
{
|
||||
if(msg.message) {
|
||||
_logStructured(
|
||||
debuggerName,
|
||||
msg.message,
|
||||
_logStructured(debuggerName, msg.message,
|
||||
GLib.LogLevelFlags.LEVEL_CRITICAL
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
clapperDebugger.debug(msg);
|
||||
}
|
||||
|
||||
@@ -53,3 +51,8 @@ function warn(msg)
|
||||
{
|
||||
_logStructured('Clapper', msg, GLib.LogLevelFlags.LEVEL_WARNING);
|
||||
}
|
||||
|
||||
function message(msg)
|
||||
{
|
||||
_logStructured('Clapper', msg, GLib.LogLevelFlags.LEVEL_MESSAGE);
|
||||
}
|
||||
|
297
src/gtuber.js
Normal file
297
src/gtuber.js
Normal file
@@ -0,0 +1,297 @@
|
||||
const { Gio, GstClapper } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
const FileOps = imports.src.fileOps;
|
||||
const Gtuber = Misc.tryImport('Gtuber');
|
||||
|
||||
const { debug, warn } = Debug;
|
||||
const { settings } = Misc;
|
||||
|
||||
const best = {
|
||||
video: null,
|
||||
audio: null,
|
||||
video_audio: null,
|
||||
};
|
||||
const codecPairs = [];
|
||||
const qualityType = {
|
||||
0: 30, // normal
|
||||
1: 60, // hfr
|
||||
};
|
||||
|
||||
var isAvailable = (Gtuber != null);
|
||||
var cancellable = null;
|
||||
let client = null;
|
||||
|
||||
function resetBestStreams()
|
||||
{
|
||||
best.video = null;
|
||||
best.audio = null;
|
||||
best.video_audio = null;
|
||||
}
|
||||
|
||||
function isStreamAllowed(stream, opts)
|
||||
{
|
||||
const vcodec = stream.video_codec;
|
||||
const acodec = stream.audio_codec;
|
||||
|
||||
if(
|
||||
vcodec
|
||||
&& (!vcodec.startsWith(opts.vcodec)
|
||||
|| (stream.height < 240 || stream.height > opts.height)
|
||||
|| stream.fps > qualityType[opts.quality])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(
|
||||
acodec
|
||||
&& (!acodec.startsWith(opts.acodec))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (vcodec != null || acodec != null);
|
||||
}
|
||||
|
||||
function updateBestStreams(streams, opts)
|
||||
{
|
||||
for(let stream of streams) {
|
||||
if(!isStreamAllowed(stream, opts))
|
||||
continue;
|
||||
|
||||
const type = (stream.video_codec && stream.audio_codec)
|
||||
? 'video_audio'
|
||||
: (stream.video_codec)
|
||||
? 'video'
|
||||
: 'audio';
|
||||
|
||||
if(!best[type] || best[type].bitrate < stream.bitrate)
|
||||
best[type] = stream;
|
||||
}
|
||||
}
|
||||
|
||||
function _streamFilter(opts, stream)
|
||||
{
|
||||
switch(stream) {
|
||||
case best.video:
|
||||
return (best.audio != null || best.video_audio == null);
|
||||
case best.audio:
|
||||
return (best.video != null || best.video_audio == null);
|
||||
case best.video_audio:
|
||||
return (best.video == null || best.audio == null);
|
||||
default:
|
||||
return (opts.adaptive)
|
||||
? isStreamAllowed(stream, opts)
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
function generateManifest(info, opts)
|
||||
{
|
||||
const gen = new Gtuber.ManifestGenerator({
|
||||
pretty: Debug.enabled,
|
||||
});
|
||||
gen.set_media_info(info);
|
||||
gen.set_filter_func(_streamFilter.bind(this, opts));
|
||||
|
||||
debug('trying to get manifest');
|
||||
|
||||
for(let pair of codecPairs) {
|
||||
opts.vcodec = pair[0];
|
||||
opts.acodec = pair[1];
|
||||
|
||||
/* Find best streams among adaptive ones */
|
||||
if (!opts.adaptive)
|
||||
updateBestStreams(info.get_adaptive_streams(), opts);
|
||||
|
||||
const data = gen.to_data();
|
||||
|
||||
/* Release our ref */
|
||||
if (!opts.adaptive)
|
||||
resetBestStreams();
|
||||
|
||||
if(data) {
|
||||
debug('got manifest');
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
debug('manifest not generated');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBestCombinedUri(info, opts)
|
||||
{
|
||||
const streams = info.get_streams();
|
||||
|
||||
debug('searching for best combined URI');
|
||||
|
||||
for(let pair of codecPairs) {
|
||||
opts.vcodec = pair[0];
|
||||
opts.acodec = pair[1];
|
||||
|
||||
/* Find best non-adaptive stream */
|
||||
updateBestStreams(streams, opts);
|
||||
|
||||
const bestUri = (best.video_audio)
|
||||
? best.video_audio.get_uri()
|
||||
: (best.audio)
|
||||
? best.audio.get_uri()
|
||||
: (best.video)
|
||||
? best.video.get_uri()
|
||||
: null;
|
||||
|
||||
/* Release our ref */
|
||||
resetBestStreams();
|
||||
|
||||
if(bestUri) {
|
||||
debug('got best possible URI');
|
||||
return bestUri;
|
||||
}
|
||||
}
|
||||
|
||||
/* If still nothing find stream by height */
|
||||
for(let stream of streams) {
|
||||
const height = stream.get_height();
|
||||
if(!height || height > opts.height)
|
||||
continue;
|
||||
|
||||
if(!best.video_audio || best.video_audio.height < stream.height)
|
||||
best.video_audio = stream;
|
||||
}
|
||||
|
||||
const anyUri = (best.video_audio)
|
||||
? best.video_audio.get_uri()
|
||||
: null;
|
||||
|
||||
/* Release our ref */
|
||||
resetBestStreams();
|
||||
|
||||
if (anyUri)
|
||||
debug('got any URI');
|
||||
|
||||
return anyUri;
|
||||
}
|
||||
|
||||
async function _parseMediaInfoAsync(info, player)
|
||||
{
|
||||
const resp = {
|
||||
uri: null,
|
||||
title: info.title,
|
||||
};
|
||||
|
||||
const { root } = player.widget;
|
||||
const surface = root.get_surface();
|
||||
const monitor = root.display.get_monitor_at_surface(surface);
|
||||
|
||||
const opts = {
|
||||
width: monitor.geometry.width * monitor.scale_factor,
|
||||
height: monitor.geometry.height * monitor.scale_factor,
|
||||
vcodec: null,
|
||||
acodec: null,
|
||||
quality: settings.get_int('yt-quality-type'),
|
||||
adaptive: settings.get_boolean('yt-adaptive-enabled'),
|
||||
};
|
||||
|
||||
if(info.has_adaptive_streams) {
|
||||
const data = generateManifest(info, opts);
|
||||
if(data) {
|
||||
const manifestFile = await FileOps.saveFilePromise(
|
||||
'tmp', null, 'manifest', data
|
||||
).catch(debug);
|
||||
|
||||
if(!manifestFile)
|
||||
throw new Error('Gtuber: no manifest file was generated');
|
||||
|
||||
resp.uri = manifestFile.get_uri();
|
||||
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
|
||||
resp.uri = getBestCombinedUri(info, opts);
|
||||
|
||||
if(!resp.uri)
|
||||
throw new Error("Gtuber: no compatible stream found");
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
function _createClient(player)
|
||||
{
|
||||
client = new Gtuber.Client();
|
||||
debug('created new gtuber client');
|
||||
|
||||
/* TODO: config based on what HW supports */
|
||||
//codecPairs.push(['vp9', 'opus']);
|
||||
|
||||
codecPairs.push(['avc', 'mp4a']);
|
||||
}
|
||||
|
||||
function mightHandleUri(uri)
|
||||
{
|
||||
const unsupported = [
|
||||
'file', 'fd', 'dvd', 'cdda',
|
||||
'dvb', 'v4l2', 'gs'
|
||||
];
|
||||
return !unsupported.includes(Misc.getUriProtocol(uri));
|
||||
}
|
||||
|
||||
function cancelFetching()
|
||||
{
|
||||
if(cancellable && !cancellable.is_cancelled())
|
||||
cancellable.cancel();
|
||||
}
|
||||
|
||||
function parseUriPromise(uri, player)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
if(!client) {
|
||||
if(!isAvailable) {
|
||||
debug('gtuber is not installed');
|
||||
return resolve({ uri, title: null });
|
||||
}
|
||||
_createClient(player);
|
||||
}
|
||||
|
||||
/* Stop to show reaction and restore internet bandwidth */
|
||||
if(player.state !== GstClapper.ClapperState.STOPPED)
|
||||
player.stop();
|
||||
|
||||
cancellable = new Gio.Cancellable();
|
||||
debug('gtuber is fetching media info...');
|
||||
|
||||
client.fetch_media_info_async(uri, cancellable, (client, task) => {
|
||||
cancellable = null;
|
||||
let info = null;
|
||||
|
||||
try {
|
||||
info = client.fetch_media_info_finish(task);
|
||||
debug('gtuber successfully fetched media info');
|
||||
}
|
||||
catch(err) {
|
||||
const taskCancellable = task.get_cancellable();
|
||||
|
||||
if(taskCancellable.is_cancelled())
|
||||
return reject(err);
|
||||
|
||||
const gtuberNoPlugin = (
|
||||
err.domain === Gtuber.ClientError.quark()
|
||||
&& err.code === Gtuber.ClientError.NO_PLUGIN
|
||||
);
|
||||
if(!gtuberNoPlugin)
|
||||
return reject(err);
|
||||
|
||||
warn(`Gtuber: ${err.message}, trying URI as is...`);
|
||||
|
||||
/* Allow handling URI as is via GStreamer plugins */
|
||||
return resolve({ uri, title: null });
|
||||
}
|
||||
|
||||
_parseMediaInfoAsync(info, player)
|
||||
.then(resp => resolve(resp))
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
});
|
||||
}
|
22
src/headerbarRemote.js
Normal file
22
src/headerbarRemote.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const { GObject } = imports.gi;
|
||||
const { HeaderBarBase } = imports.src.headerbarBase;
|
||||
|
||||
var HeaderBarRemote = GObject.registerClass({
|
||||
GTypeName: 'ClapperHeaderBarRemote',
|
||||
},
|
||||
class ClapperHeaderBarRemote extends HeaderBarBase
|
||||
{
|
||||
_init()
|
||||
{
|
||||
super._init();
|
||||
this.extraButtonsBox.visible = false;
|
||||
}
|
||||
|
||||
_onWindowButtonActivate(action)
|
||||
{
|
||||
if(action === 'toggle-maximized')
|
||||
action = 'toggle_maximized';
|
||||
|
||||
this.root.child.sendWs(action);
|
||||
}
|
||||
});
|
@@ -1,6 +1,7 @@
|
||||
imports.gi.versions.Gdk = '4.0';
|
||||
imports.gi.versions.Gtk = '4.0';
|
||||
imports.gi.versions.Soup = '3.0';
|
||||
imports.gi.versions.Soup = '2.4';
|
||||
imports.gi.versions.Gtuber = '0.0';
|
||||
|
||||
pkg.initGettext();
|
||||
pkg.initFormat();
|
||||
|
6
src/mainDaemon.js
Normal file
6
src/mainDaemon.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { Daemon } = imports.src.daemon;
|
||||
|
||||
function main()
|
||||
{
|
||||
new Daemon();
|
||||
}
|
20
src/mainRemote.js
Normal file
20
src/mainRemote.js
Normal file
@@ -0,0 +1,20 @@
|
||||
imports.gi.versions.Gdk = '4.0';
|
||||
imports.gi.versions.Gtk = '4.0';
|
||||
imports.gi.versions.Soup = '2.4';
|
||||
imports.gi.versions.Gtuber = '0.0';
|
||||
|
||||
pkg.initGettext();
|
||||
|
||||
const Misc = imports.src.misc;
|
||||
Misc.appId += '.Remote';
|
||||
|
||||
const { Gtk, Adw } = imports.gi;
|
||||
const { AppRemote } = imports.src.appRemote;
|
||||
|
||||
function main(argv)
|
||||
{
|
||||
Gtk.init();
|
||||
Adw.init();
|
||||
|
||||
new AppRemote().run(argv);
|
||||
}
|
24
src/misc.js
24
src/misc.js
@@ -1,7 +1,8 @@
|
||||
const { Gio, GLib, Gdk, Gtk } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
|
||||
const { debug } = Debug;
|
||||
const { debug, message } = Debug;
|
||||
const failedImports = [];
|
||||
|
||||
var appName = 'Clapper';
|
||||
var appId = 'com.github.rafostar.Clapper';
|
||||
@@ -28,6 +29,23 @@ const subsKeys = Object.keys(subsTitles);
|
||||
|
||||
let inhibitCookie;
|
||||
|
||||
function tryImport(libName)
|
||||
{
|
||||
let lib = null;
|
||||
|
||||
try {
|
||||
lib = imports.gi[libName];
|
||||
}
|
||||
catch(err) {
|
||||
if(!failedImports.includes(libName)) {
|
||||
failedImports.push(libName);
|
||||
message(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
return lib;
|
||||
}
|
||||
|
||||
function getResourceUri(path)
|
||||
{
|
||||
const res = `file://${pkg.pkgdatadir}/${path}`;
|
||||
@@ -161,8 +179,8 @@ function getFormattedTime(time, showHours)
|
||||
time -= minutes * 60;
|
||||
const seconds = ('0' + Math.floor(time)).slice(-2);
|
||||
|
||||
const parsed = (hours) ? `${hours}∶` : '';
|
||||
return parsed + `${minutes}∶${seconds}`;
|
||||
const parsed = (hours) ? `${hours}:` : '';
|
||||
return parsed + `${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
function parsePlaylistFiles(filesArray)
|
||||
|
@@ -1,8 +1,10 @@
|
||||
const { Adw, Gdk, Gio, GObject, Gst, GstClapper, Gtk } = imports.gi;
|
||||
const { Gdk, Gio, GObject, Gst, GstClapper, Gtk } = imports.gi;
|
||||
const ByteArray = imports.byteArray;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
const Gtuber = imports.src.gtuber;
|
||||
const { PlaylistWidget } = imports.src.playlist;
|
||||
const { WebApp } = imports.src.webApp;
|
||||
|
||||
const { debug, warn } = Debug;
|
||||
const { settings } = Misc;
|
||||
@@ -42,10 +44,12 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
this.visualization_enabled = false;
|
||||
|
||||
this.webserver = null;
|
||||
this.webapp = null;
|
||||
this.playlistWidget = new PlaylistWidget();
|
||||
|
||||
this.seekDone = true;
|
||||
this.needsFastSeekRestore = false;
|
||||
this.customVideoTitle = null;
|
||||
|
||||
this.windowMapped = false;
|
||||
this.quitOnStop = false;
|
||||
@@ -69,7 +73,6 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
set_and_bind_settings()
|
||||
{
|
||||
const settingsToSet = [
|
||||
'dark-theme',
|
||||
'after-playback',
|
||||
'seeking-mode',
|
||||
'audio-offset',
|
||||
@@ -137,19 +140,31 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
|
||||
set_uri(uri)
|
||||
{
|
||||
if(Misc.getUriProtocol(uri) === 'file') {
|
||||
const file = Misc.getFileFromLocalUri(uri);
|
||||
if(!file) {
|
||||
if(!this.playlistWidget.nextTrack())
|
||||
debug('set media reached end of playlist');
|
||||
this.customVideoTitle = null;
|
||||
Gtuber.cancelFetching();
|
||||
|
||||
return;
|
||||
}
|
||||
if(uri.endsWith('.claps')) {
|
||||
this.load_playlist_file(file);
|
||||
if(Gtuber.mightHandleUri(uri)) {
|
||||
Gtuber.parseUriPromise(uri, this)
|
||||
.then(res => {
|
||||
this.customVideoTitle = res.title;
|
||||
super.set_uri(res.uri);
|
||||
})
|
||||
.catch(debug);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const file = Misc.getFileFromLocalUri(uri);
|
||||
if(!file) {
|
||||
if(!this.playlistWidget.nextTrack())
|
||||
debug('set media reached end of playlist');
|
||||
|
||||
return;
|
||||
}
|
||||
if(uri.endsWith('.claps')) {
|
||||
this.load_playlist_file(file);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
super.set_uri(uri);
|
||||
@@ -630,19 +645,6 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'dark-theme':
|
||||
/* TODO: Remove libadwaita alpha2 compat someday */
|
||||
if (Adw.StyleManager != null) {
|
||||
const styleManager = Adw.StyleManager.get_default();
|
||||
styleManager.color_scheme = (settings.get_boolean(key))
|
||||
? Adw.ColorScheme.FORCE_DARK
|
||||
: Adw.ColorScheme.FORCE_LIGHT;
|
||||
}
|
||||
else {
|
||||
const gtkSettings = Gtk.Settings.get_default();
|
||||
gtkSettings.gtk_application_prefer_dark_theme = settings.get_boolean(key);
|
||||
}
|
||||
break;
|
||||
case 'render-shadows':
|
||||
root = this.widget.get_root();
|
||||
if(!root) break;
|
||||
@@ -673,6 +675,7 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
debug(`changed play flags: ${initialFlags} -> ${settingsFlags}`);
|
||||
break;
|
||||
case 'webserver-enabled':
|
||||
case 'webapp-enabled':
|
||||
const webserverEnabled = settings.get_boolean('webserver-enabled');
|
||||
|
||||
if(webserverEnabled) {
|
||||
@@ -687,8 +690,22 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
this.webserver.passMsgData = this.receiveWs.bind(this);
|
||||
}
|
||||
this.webserver.startListening();
|
||||
|
||||
const webappEnabled = settings.get_boolean('webapp-enabled');
|
||||
|
||||
if(!this.webapp && !webappEnabled)
|
||||
break;
|
||||
|
||||
if(webappEnabled) {
|
||||
if(!this.webapp)
|
||||
this.webapp = new WebApp();
|
||||
|
||||
this.webapp.startDaemonApp(settings.get_int('webapp-port'));
|
||||
}
|
||||
}
|
||||
else if(this.webserver) {
|
||||
/* remote app will close when connection is lost
|
||||
* which will cause the daemon to close too */
|
||||
this.webserver.stopListening();
|
||||
}
|
||||
break;
|
||||
|
51
src/playerRemote.js
Normal file
51
src/playerRemote.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const { GObject } = imports.gi;
|
||||
const { WebClient } = imports.src.webClient;
|
||||
|
||||
var ClapperState = {
|
||||
STOPPED: 0,
|
||||
BUFFERING: 1,
|
||||
PAUSED: 2,
|
||||
PLAYING: 3,
|
||||
};
|
||||
|
||||
var PlayerRemote = GObject.registerClass({
|
||||
GTypeName: 'ClapperPlayerRemote',
|
||||
},
|
||||
class ClapperPlayerRemote extends GObject.Object
|
||||
{
|
||||
_init()
|
||||
{
|
||||
super._init();
|
||||
|
||||
this.webclient = new WebClient();
|
||||
}
|
||||
|
||||
set_playlist(playlist)
|
||||
{
|
||||
const uris = [];
|
||||
|
||||
/* We can not send GioFiles via WebSocket */
|
||||
for(let source of playlist)
|
||||
uris.push(this._getSourceUri(source));
|
||||
|
||||
this.webclient.sendMessage({
|
||||
action: 'set_playlist',
|
||||
value: uris
|
||||
});
|
||||
}
|
||||
|
||||
set_subtitles(source)
|
||||
{
|
||||
this.webclient.sendMessage({
|
||||
action: 'set_subtitles',
|
||||
value: this._getSourceUri(source)
|
||||
});
|
||||
}
|
||||
|
||||
_getSourceUri(source)
|
||||
{
|
||||
return (source.get_uri != null)
|
||||
? source.get_uri()
|
||||
: source;
|
||||
}
|
||||
});
|
19
src/prefs.js
19
src/prefs.js
@@ -1,6 +1,7 @@
|
||||
const { Adw, GObject, Gio, Gst, Gtk } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
const Gtuber = imports.src.gtuber;
|
||||
|
||||
const { debug } = Debug;
|
||||
const { settings } = Misc;
|
||||
@@ -440,15 +441,8 @@ class ClapperPrefsPluginExpander extends Adw.ExpanderRow
|
||||
const featuresNames = Object.keys(pluginsData[this.title]);
|
||||
debug(`Adding ${featuresNames.length} features to the list of plugin: ${this.title}`);
|
||||
|
||||
for(let featureObj of pluginsData[this.title]) {
|
||||
const prefsPluginFeature = new PrefsPluginFeature(featureObj);
|
||||
|
||||
/* TODO: Remove old libadwaita compat */
|
||||
if(this.add_row)
|
||||
this.add_row(prefsPluginFeature);
|
||||
else
|
||||
this.add(prefsPluginFeature);
|
||||
}
|
||||
for(let featureObj of pluginsData[this.title])
|
||||
this.add(new PrefsPluginFeature(featureObj));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -544,6 +538,7 @@ class ClapperPrefsPluginRankingSubpage extends Gtk.Box
|
||||
var PrefsWindow = GObject.registerClass({
|
||||
GTypeName: 'ClapperPrefsWindow',
|
||||
Template: Misc.getResourceUri('ui/preferences-window.ui'),
|
||||
InternalChildren: ['gtuber_group'],
|
||||
},
|
||||
class ClapperPrefsWindow extends Adw.PreferencesWindow
|
||||
{
|
||||
@@ -553,11 +548,7 @@ class ClapperPrefsWindow extends Adw.PreferencesWindow
|
||||
transient_for: window,
|
||||
});
|
||||
|
||||
/* FIXME: old libadwaita compat, should be
|
||||
* normally in prefs UI file */
|
||||
this.can_swipe_back = true;
|
||||
this.can_navigate_back = true;
|
||||
|
||||
this._gtuber_group.visible = Gtuber.isAvailable;
|
||||
this.show();
|
||||
}
|
||||
});
|
||||
|
@@ -59,8 +59,8 @@ class ClapperRevealerTop extends CustomRevealer
|
||||
|
||||
const initTime = GLib.DateTime.new_now_local().format('%X');
|
||||
this.timeFormat = (initTime.length > 8)
|
||||
? '%I∶%M %p'
|
||||
: '%H∶%M';
|
||||
? '%I:%M %p'
|
||||
: '%H:%M';
|
||||
|
||||
this.mediaTitle = new Gtk.Label({
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
@@ -321,8 +321,6 @@ class ClapperControlsRevealer extends Gtk.Revealer
|
||||
|
||||
const isStick = (isFloating && settings.get_boolean('floating-stick'));
|
||||
DBus.shellWindowEval('stick', isStick);
|
||||
|
||||
this.root.child.refreshWindowTitle(this.root.title);
|
||||
}
|
||||
|
||||
_onControlsRevealed()
|
||||
|
51
src/webApp.js
Normal file
51
src/webApp.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const { Gio, GObject } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
|
||||
const { debug } = Debug;
|
||||
|
||||
var WebApp = GObject.registerClass({
|
||||
GTypeName: 'ClapperWebApp',
|
||||
},
|
||||
class ClapperWebApp extends Gio.SubprocessLauncher
|
||||
{
|
||||
_init()
|
||||
{
|
||||
const flags = Gio.SubprocessFlags.STDOUT_SILENCE
|
||||
| Gio.SubprocessFlags.STDERR_SILENCE;
|
||||
|
||||
super._init({ flags });
|
||||
|
||||
this.daemonApp = null;
|
||||
}
|
||||
|
||||
startDaemonApp(port)
|
||||
{
|
||||
if(this.daemonApp)
|
||||
return;
|
||||
|
||||
this.daemonApp = this.spawnv([Misc.appId + '.Daemon', String(port)]);
|
||||
this.daemonApp.wait_async(null, this._onDaemonClosed.bind(this));
|
||||
|
||||
debug('daemon app started');
|
||||
}
|
||||
|
||||
_onDaemonClosed(proc, result)
|
||||
{
|
||||
let hadError;
|
||||
|
||||
try {
|
||||
hadError = proc.wait_finish(result);
|
||||
}
|
||||
catch(err) {
|
||||
debug(err);
|
||||
}
|
||||
|
||||
this.daemonApp = null;
|
||||
|
||||
if(hadError)
|
||||
debug('daemon app exited with error or was forced to close');
|
||||
|
||||
debug('daemon app closed');
|
||||
}
|
||||
});
|
90
src/webClient.js
Normal file
90
src/webClient.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const { Gio, GObject, Soup } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
const WebHelpers = imports.src.webHelpers;
|
||||
|
||||
const { debug } = Debug;
|
||||
const { settings } = Misc;
|
||||
|
||||
var WebClient = GObject.registerClass({
|
||||
GTypeName: 'ClapperWebClient',
|
||||
},
|
||||
class ClapperWebClient extends Soup.Session
|
||||
{
|
||||
_init(port)
|
||||
{
|
||||
super._init({
|
||||
timeout: 3,
|
||||
use_thread_context: true,
|
||||
});
|
||||
|
||||
this.wsConn = null;
|
||||
|
||||
this.connectWebsocket();
|
||||
}
|
||||
|
||||
connectWebsocket()
|
||||
{
|
||||
if(this.wsConn)
|
||||
return;
|
||||
|
||||
const port = settings.get_int('webserver-port');
|
||||
const message = Soup.Message.new('GET', `ws://127.0.0.1:${port}/websocket`);
|
||||
this.websocket_connect_async(message, null, null, null, this._onWsConnect.bind(this));
|
||||
|
||||
debug('connecting WebSocket to Clapper app');
|
||||
}
|
||||
|
||||
sendMessage(data)
|
||||
{
|
||||
if(
|
||||
!this.wsConn
|
||||
|| this.wsConn.state !== Soup.WebsocketState.OPEN
|
||||
)
|
||||
return;
|
||||
|
||||
this.wsConn.send_text(JSON.stringify(data));
|
||||
}
|
||||
|
||||
passMsgData(action, value)
|
||||
{
|
||||
}
|
||||
|
||||
_onWsConnect(session, result)
|
||||
{
|
||||
let connection = null;
|
||||
|
||||
try {
|
||||
connection = this.websocket_connect_finish(result);
|
||||
}
|
||||
catch(err) {
|
||||
debug(err);
|
||||
}
|
||||
|
||||
if(!connection)
|
||||
return this.passMsgData('close');
|
||||
|
||||
connection.connect('message', this._onWsMessage.bind(this));
|
||||
connection.connect('closed', this._onWsClosed.bind(this));
|
||||
|
||||
this.wsConn = connection;
|
||||
|
||||
debug('successfully connected WebSocket');
|
||||
}
|
||||
|
||||
_onWsMessage(connection, dataType, bytes)
|
||||
{
|
||||
const [success, parsedMsg] = WebHelpers.parseData(dataType, bytes);
|
||||
|
||||
if(success)
|
||||
this.passMsgData(parsedMsg.action, parsedMsg.value);
|
||||
}
|
||||
|
||||
_onWsClosed(connection)
|
||||
{
|
||||
debug('closed WebSocket connection');
|
||||
this.wsConn = null;
|
||||
|
||||
this.passMsgData('close');
|
||||
}
|
||||
});
|
@@ -107,7 +107,7 @@ class ClapperWebServer extends Soup.Server
|
||||
this.remove_handler('/');
|
||||
}
|
||||
|
||||
_onWsConnection(server, msg, path, connection)
|
||||
_onWsConnection(server, connection)
|
||||
{
|
||||
debug('new WebSocket connection');
|
||||
|
||||
|
@@ -224,7 +224,7 @@ class ClapperWidget extends Gtk.Grid
|
||||
case GstClapper.ClapperVideoInfo:
|
||||
type = 'video';
|
||||
codec = info.get_codec() || _('Undetermined');
|
||||
text = `${codec}, ${info.get_width()}×${info.get_height()}`;
|
||||
text = `${codec}, ${info.get_width()}x${info.get_height()}`;
|
||||
let fps = info.get_framerate();
|
||||
fps = Number((fps[0] / fps[1]).toFixed(2));
|
||||
if(fps)
|
||||
@@ -277,8 +277,7 @@ class ClapperWidget extends Gtk.Grid
|
||||
|
||||
if(currStream && type !== 'subtitle') {
|
||||
const caps = currStream.get_caps();
|
||||
if (caps)
|
||||
debug(`${type} caps: ${caps.to_string()}`);
|
||||
debug(`${type} caps: ${caps.to_string()}`);
|
||||
}
|
||||
if(type === 'video') {
|
||||
const isShowVis = (
|
||||
@@ -308,31 +307,21 @@ class ClapperWidget extends Gtk.Grid
|
||||
|
||||
updateTitle(mediaInfo)
|
||||
{
|
||||
let title = mediaInfo.get_title();
|
||||
let title = this.player.customVideoTitle;
|
||||
|
||||
if(!title)
|
||||
title = mediaInfo.get_title();
|
||||
|
||||
if(!title) {
|
||||
const item = this.player.playlistWidget.getActiveRow();
|
||||
title = item.filename;
|
||||
}
|
||||
|
||||
this.refreshWindowTitle(title);
|
||||
this.root.title = title;
|
||||
this.revealerTop.title = title;
|
||||
this.revealerTop.showTitle = true;
|
||||
}
|
||||
|
||||
refreshWindowTitle(title)
|
||||
{
|
||||
const isFloating = !this.controlsRevealer.reveal_child;
|
||||
const pipSuffix = ' - PiP';
|
||||
const hasPipSuffix = title.endsWith(pipSuffix);
|
||||
|
||||
this.root.title = (isFloating && !hasPipSuffix)
|
||||
? title + pipSuffix
|
||||
: (!isFloating && hasPipSuffix)
|
||||
? title.substring(0, title.length - pipSuffix.length)
|
||||
: title;
|
||||
}
|
||||
|
||||
updateTime()
|
||||
{
|
||||
if(
|
||||
|
72
src/widgetRemote.js
Normal file
72
src/widgetRemote.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const { GObject, Gtk } = imports.gi;
|
||||
const Buttons = imports.src.buttons;
|
||||
const Misc = imports.src.misc;
|
||||
const { PlayerRemote, ClapperState } = imports.src.playerRemote;
|
||||
|
||||
var WidgetRemote = GObject.registerClass({
|
||||
GTypeName: 'ClapperWidgetRemote',
|
||||
},
|
||||
class ClapperWidgetRemote extends Gtk.Grid
|
||||
{
|
||||
_init(opts)
|
||||
{
|
||||
super._init({
|
||||
halign: Gtk.Align.CENTER,
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
|
||||
Misc.loadCustomCss();
|
||||
|
||||
this.player = new PlayerRemote();
|
||||
this.player.webclient.passMsgData = this.receiveWs.bind(this);
|
||||
|
||||
/* FIXME: create better way to add buttons for
|
||||
* remote app without duplicating too much code */
|
||||
this.togglePlayButton = new Buttons.IconToggleButton(
|
||||
'media-playback-start-symbolic',
|
||||
'media-playback-pause-symbolic'
|
||||
);
|
||||
this.togglePlayButton.remove_css_class('flat');
|
||||
this.togglePlayButton.connect(
|
||||
'clicked', () => this.sendWs('toggle_play')
|
||||
);
|
||||
|
||||
this.attach(this.togglePlayButton, 0, 0, 1, 1);
|
||||
}
|
||||
|
||||
sendWs(action, value)
|
||||
{
|
||||
const data = { action };
|
||||
|
||||
/* do not send "null" or "undefined"
|
||||
* for faster network data transfer */
|
||||
if(value != null)
|
||||
data.value = value;
|
||||
|
||||
this.player.webclient.sendMessage(data);
|
||||
}
|
||||
|
||||
receiveWs(action, value)
|
||||
{
|
||||
switch(action) {
|
||||
case 'state_changed':
|
||||
switch(value) {
|
||||
case ClapperState.STOPPED:
|
||||
case ClapperState.PAUSED:
|
||||
this.togglePlayButton.setPrimaryIcon();
|
||||
break;
|
||||
case ClapperState.PLAYING:
|
||||
this.togglePlayButton.setSecondaryIcon();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'close':
|
||||
this.root.run_dispose();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
@@ -3,11 +3,11 @@
|
||||
<menu id="mainMenu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Open Files…</attribute>
|
||||
<attribute name="label" translatable="yes">Open Files...</attribute>
|
||||
<attribute name="action">app.open_local</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Open URI…</attribute>
|
||||
<attribute name="label" translatable="yes">Open URI...</attribute>
|
||||
<attribute name="action">app.open_uri</attribute>
|
||||
</item>
|
||||
</section>
|
||||
|
@@ -37,9 +37,9 @@
|
||||
<property name="valign">center</property>
|
||||
<property name="adjustment">speed_adjustment</property>
|
||||
<marks>
|
||||
<mark value="0.25" position="bottom">0.25×</mark>
|
||||
<mark value="0.25" position="bottom">0.25x</mark>
|
||||
<mark value="1" position="bottom" translatable="yes">Normal</mark>
|
||||
<mark value="2" position="bottom">2×</mark>
|
||||
<mark value="2" position="bottom">2x</mark>
|
||||
</marks>
|
||||
<style>
|
||||
<class name="speedscale"/>
|
||||
|
@@ -5,6 +5,7 @@
|
||||
<property name="resizable">True</property>
|
||||
<property name="search-enabled">True</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="can-swipe-back">True</property>
|
||||
<property name="modal">True</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesPage">
|
||||
@@ -177,6 +178,45 @@
|
||||
<property name="spin-adjustment">web_server_adjustment</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="ClapperPrefsSwitch">
|
||||
<property name="title" translatable="yes">Run web application in background</property>
|
||||
<property name="subtitle" translatable="yes">Requires GTK compiled with Broadway backend</property>
|
||||
<property name="schema-name">webapp-enabled</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="ClapperPrefsSpin">
|
||||
<property name="title" translatable="yes">Web application port</property>
|
||||
<property name="schema-name">webapp-port</property>
|
||||
<property name="spin-adjustment">web_app_adjustment</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="gtuber_group">
|
||||
<property name="title" translatable="no">Gtuber</property>
|
||||
<child>
|
||||
<object class="ClapperPrefsSwitch">
|
||||
<property name="title" translatable="yes">Prefer adaptive streaming</property>
|
||||
<property name="schema-name">yt-adaptive-enabled</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="ClapperPrefsCombo">
|
||||
<property name="title" translatable="yes">Max quality</property>
|
||||
<property name="schema-name">yt-quality-type</property>
|
||||
<property name="model">
|
||||
<object class="GtkStringList">
|
||||
<items>
|
||||
<item translatable="yes">Normal</item>
|
||||
<item translatable="no">HFR</item>
|
||||
</items>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
Reference in New Issue
Block a user