Compare commits

...

23 Commits

Author SHA1 Message Date
Benjamin Otte
c45ec8e0d2 media: Add support for OpenGL to GtkGstMediaFile 2018-03-18 05:41:29 +01:00
Benjamin Otte
fa62485cf0 mediastream: Add gtk_media_stream_realize/unrealize()
This allows widget to attach their streams to GdkWindow(s)

The idea is to allow attaching a stream to windowing system(s) so the
stream can make use of its resources, in particular GL contexts.

I am however unsure what to attach to:
- GtkWindow
- GdkWindow
- GtkWidget
- GskRenderer
Each of these provide advantages and disadvantages.

So I'm very much open to better suggestions.
2018-03-18 05:29:16 +01:00
Benjamin Otte
8dde331bbc build: Make GStreamer backend the default
Instead of compiling without media backend by default, we now use the
GStreamer backend.

This means that CI now requires gstreamer support.
2018-03-18 05:29:16 +01:00
Benjamin Otte
08bf8a60e8 THIS IS EVIL DEMO CODE, DO NOT LOOK AT IT.
Just register a video as paint source so we can play with it whenever
the images demo opens.

Probably needs a better demo.
2018-03-18 05:29:16 +01:00
Benjamin Otte
50373a91a2 css: Add initial support for Houdini-style paint() images
This adds gtk_style_context_add_paint_for_display() which allows to add
a GdkPaintable with a name to the CSS machinery and then allows using it
via "-gtk-paint(name)" inside the CSS as an image.

This essentially allows applications to register snapshot functions for
any part of the CSS machinery that is an image.
I have no idea if this is a good idea, because it further blurs the
lines between theme and application, but it *is* quite powerful.

The inspiration was
https://drafts.css-houdini.org/css-paint-api/
Though we do not support properties or a constructor, but so far require
the constructed image directly.
2018-03-18 05:29:16 +01:00
Benjamin Otte
3797b7e998 gtk-demo: Add a simple video player example
For now, it's impossible to unfullscreen. Somebody should figure out a
way to do this.
2018-03-18 05:29:16 +01:00
Benjamin Otte
01158f4438 gtk: Add a GStreamer implementation to GtkMediaFile 2018-03-18 05:29:14 +01:00
Benjamin Otte
22e729877f build: Add gstreamer dependency 2018-03-18 05:19:59 +01:00
Benjamin Otte
b9b84b3644 demos: Round up the paintable demos with a media stream 2018-03-18 05:19:59 +01:00
Benjamin Otte
433c71435a demos: Add an animated paintable demo
This builds on the paintable demo.
2018-03-18 05:19:59 +01:00
Benjamin Otte
4fc1ea9d12 gtk-demo: Add paintable demo 2018-03-18 05:19:59 +01:00
Benjamin Otte
074c829736 demos: Add a video benchmark to widgetbowl demo 2018-03-18 05:19:59 +01:00
Benjamin Otte
2a8d7f8d6a gtk-demo: Add a video example (with controls) to the images demo 2018-03-18 05:19:59 +01:00
Benjamin Otte
7e22264918 gtk: Add GtkVideo
GtkVideo is a simple video player widget. It probably needs some more
configurability, but it does its job.
2018-03-18 05:19:59 +01:00
Benjamin Otte
98bfd8eca0 gtk: Add GtkMediaControls
This is a basic first implementation of controls for a GtkMediaStream,
modeled after Totem's UI.
2018-03-18 05:19:59 +01:00
Benjamin Otte
ffe4b66699 gtk: Add ffmpeg implementation of GtkMediaFile
This adds a module using ffmpeg to implement the GtkMediaFile interface.
2018-03-18 05:19:59 +01:00
Benjamin Otte
a09667d4b9 build: Add ffmpeg requirement
This adds an ffmpeg build option for a potential media stream
implementation.
2018-03-18 05:19:59 +01:00
Matthias Clasen
bd25c4110c mediafile: Turn into an extension point
This way, we can support external libraries providing implementations of
GtkMediaFile.

We also add a media backend called 'nomedia' that can be enabled to not
compile any support for GtkMediaFile. This is useful when people want to
statically compile GTK into an application that does not use media.
For now, this option is the default.

We also support a new environment variable GTK_MEDIA that allows
selecting the implementation to use.
GTK_MEDIA=help can be used to get info about the available
implementations.
2018-03-18 05:19:59 +01:00
Benjamin Otte
427bce6c54 gtk: Add GtkMediaFile
GtkMediaFile is an implementation of GtkMediaStream to play back files.

Except it isn't an implementation yet, but only an interface.
2018-03-18 05:19:58 +01:00
Benjamin Otte
939e466eb4 gtk: Add GtkMediaStream
GtkMediaStream is the new base class for playback of audio and video
streams.

It implements GdkPaintable for rendering.
2018-03-18 05:19:58 +01:00
Benjamin Otte
10e1520162 texture: Expose subclasses as subclasses
This is necessary so that bidnings work properly and don't make
gdk_gl_texture_release() a function on GdkTexture.
It also allows code to identify what type of texture they are dealing
with.

Finally, we can now decide to add getters later without screwing
anything up, if we want to allow people to access GL textures directly.
2018-03-18 05:19:58 +01:00
Benjamin Otte
4bef77abd5 gdk: Add GDK_MEMORY_DEFAULT
This is the default memory format.

I added it because it is way better than including a private header and
using GDK_MEMORY_CAIRO_FORMAT_ARGB32.
2018-03-18 05:19:58 +01:00
Benjamin Otte
e30ba1dced gdk: Remove gdk_texture_new_from_data()
Use gdk_memory_texture_new() instead.
2018-03-18 05:19:58 +01:00
63 changed files with 7123 additions and 106 deletions

View File

@@ -41,6 +41,9 @@
/* Define to 1 if you have the <dlfcn.h> header file. */
#mesondefine HAVE_DLFCN_H
/* Have the ffmpeg library */
#mesondefine HAVE_FFMPEG
/* Define to 1 if you have the <ftw.h> header file. */
#mesondefine HAVE_FTW_H
@@ -53,6 +56,9 @@
/* Define if gio-unix is available */
#mesondefine HAVE_GIO_UNIX
/* Define if GStreamer support is available */
#mesondefine HAVE_GSTREAMER
/* Define to 1 if you have the `httpGetAuthString' function. */
#mesondefine HAVE_HTTPGETAUTHSTRING

View File

@@ -121,6 +121,7 @@
<gresource prefix="/images">
<file>alphatest.png</file>
<file>floppybuddy.gif</file>
<file>gtk-logo.webm</file>
</gresource>
<gresource prefix="/pixbufs">
<file>apple-red.png</file>
@@ -181,6 +182,9 @@
<file>overlay.c</file>
<file>overlay2.c</file>
<file>pagesetup.c</file>
<file>paintable.c</file>
<file>paintable_animated.c</file>
<file>paintable_mediastream.c</file>
<file>panes.c</file>
<file>pickers.c</file>
<file>pixbufs.c</file>
@@ -204,6 +208,7 @@
<file>transparent.c</file>
<file>tree_store.c</file>
<file>textmask.c</file>
<file>video_player.c</file>
</gresource>
<gresource prefix="/textview">
<file>floppybuddy.gif</file>

Binary file not shown.

View File

@@ -317,12 +317,15 @@ toggle_sensitivity_callback (GtkWidget *togglebutton,
GtkWidget *
do_images (GtkWidget *do_widget)
{
GtkWidget *video;
GtkWidget *frame;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *base_vbox;
GtkWidget *image;
GtkWidget *label;
GtkWidget *button;
GIcon *gicon;
GIcon *gicon;
if (!window)
{
@@ -336,9 +339,15 @@ do_images (GtkWidget *do_widget)
g_signal_connect (window, "destroy",
G_CALLBACK (cleanup_callback), NULL);
base_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
g_object_set (base_vbox, "margin", 16, NULL);
gtk_container_add (GTK_CONTAINER (window), base_vbox);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16);
gtk_container_add (GTK_CONTAINER (base_vbox), hbox);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
g_object_set (vbox, "margin", 16, NULL);
gtk_container_add (GTK_CONTAINER (window), vbox);
gtk_container_add (GTK_CONTAINER (hbox), vbox);
label = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (label),
@@ -395,6 +404,8 @@ do_images (GtkWidget *do_widget)
/* Progressive */
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
gtk_container_add (GTK_CONTAINER (hbox), vbox);
label = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (label),
@@ -415,13 +426,35 @@ do_images (GtkWidget *do_widget)
start_progressive_loading (image);
/* Video */
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
gtk_container_add (GTK_CONTAINER (hbox), vbox);
label = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (label),
"<u>Displaying video</u>");
gtk_box_pack_start (GTK_BOX (vbox), label);
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_widget_set_halign (frame, GTK_ALIGN_CENTER);
gtk_widget_set_valign (frame, GTK_ALIGN_CENTER);
gtk_box_pack_start (GTK_BOX (vbox), frame);
video = gtk_video_new_for_resource ("/images/gtk-logo.webm");
gtk_media_stream_set_loop (gtk_video_get_media_stream (GTK_VIDEO (video)), TRUE);
gtk_container_add (GTK_CONTAINER (frame), video);
gtk_style_context_add_paint_for_display (gdk_display_get_default (),
"video",
GDK_PAINTABLE (gtk_video_get_media_stream (GTK_VIDEO (video))));
/* Sensitivity control */
button = gtk_toggle_button_new_with_mnemonic ("_Insensitive");
gtk_box_pack_start (GTK_BOX (vbox), button);
gtk_box_pack_start (GTK_BOX (base_vbox), button);
g_signal_connect (button, "toggled",
G_CALLBACK (toggle_sensitivity_callback),
vbox);
base_vbox);
}
if (!gtk_widget_get_visible (window))

View File

@@ -45,6 +45,9 @@ demos = files([
'modelbutton.c',
'overlay.c',
'overlay2.c',
'paintable.c',
'paintable_animated.c',
'paintable_mediastream.c',
'panes.c',
'pickers.c',
'pixbufs.c',
@@ -68,6 +71,7 @@ demos = files([
'theming_style_classes.c',
'transparent.c',
'tree_store.c',
'video_player.c',
])
gtkdemo_deps = [ libgtk_dep, ]

179
demos/gtk-demo/paintable.c Normal file
View File

@@ -0,0 +1,179 @@
/* Paintable/A simple paintable
*
* GdkPaintable is an interface used by GTK for drawings of any sort
* that do not require layouting or positioning.
*
* This demo code gives a simple example on how a paintable can
* be created.
*
* Paintables can be used in many places inside GTK widgets, but the
* most common usage is inside GtkImage and that's what we're going
* to do here.
*/
#include <gtk/gtk.h>
#include "paintable.h"
static GtkWidget *window = NULL;
/* First, add the boilerplate for the object itself.
* This part would normally go in the header.
*/
#define GTK_TYPE_NUCLEAR_ICON (gtk_nuclear_icon_get_type ())
G_DECLARE_FINAL_TYPE (GtkNuclearIcon, gtk_nuclear_icon, GTK, NUCLEAR_ICON, GObject)
/* Declare the struct. */
struct _GtkNuclearIcon
{
GObject parent_instance;
/* We store this rotation value here.
* We are not doing with it here, but it will come in
* very useful in the followup demos.
*/
double rotation;
};
struct _GtkNuclearIconClass
{
GObjectClass parent_class;
};
/* This is the function that draws the actual icon.
* We make it a custom function and define it in the paintable.h header
* so that it can be called from all the other demos, too.
*/
void
gtk_nuclear_snapshot (GtkSnapshot *snapshot,
double width,
double height,
double rotation)
{
#define RADIUS 0.3
cairo_t *cr;
double size;
gtk_snapshot_append_color (snapshot,
&(GdkRGBA) { 0.9, 0.75, 0.15, 1.0 },
&GRAPHENE_RECT_INIT (0, 0, width, height),
"Yellow background");
size = MIN (width, height);
cr = gtk_snapshot_append_cairo (snapshot,
&GRAPHENE_RECT_INIT ((width - size) / 2.0,
(height - size) / 2.0,
size, size),
"Radioactive Icon");
cairo_translate (cr, width / 2.0, height / 2.0);
cairo_scale (cr, size, size);
cairo_rotate (cr, rotation);
cairo_arc (cr, 0, 0, 0.1, - G_PI, G_PI);
cairo_fill (cr);
cairo_set_line_width (cr, RADIUS);
cairo_set_dash (cr, (double[1]) { RADIUS * G_PI / 3 }, 1, 0.0);
cairo_arc (cr, 0, 0, RADIUS, - G_PI, G_PI);
cairo_stroke (cr);
cairo_destroy (cr);
}
/* Here, we implement the functionality required by the GdkPaintable interface */
static void
gtk_nuclear_icon_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkNuclearIcon *nuclear = GTK_NUCLEAR_ICON (paintable);
/* The snapshot function is the only function we need to implement.
* It does the actual drawing of the paintable.
*/
gtk_nuclear_snapshot (snapshot,
width, height,
nuclear->rotation);
}
static GdkPaintableFlags
gtk_nuclear_icon_get_flags (GdkPaintable *paintable)
{
/* The flags are very useful to let GTK know that this image
* is never going to change.
* This allows many optimizations and should therefore always
* be set.
*/
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_nuclear_icon_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = gtk_nuclear_icon_snapshot;
iface->get_flags = gtk_nuclear_icon_get_flags;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkNuclearIcon, gtk_nuclear_icon, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_nuclear_icon_paintable_init))
/* Here's the boilerplate for the GObject declaration.
* We don't need to do anything special here, because we keep no
* data of our own.
*/
static void
gtk_nuclear_icon_class_init (GtkNuclearIconClass *klass)
{
}
static void
gtk_nuclear_icon_init (GtkNuclearIcon *nuclear)
{
}
/* And finally, we add a simple constructor.
* It is declared in the header so that the other examples
* can use it.
*/
GdkPaintable *
gtk_nuclear_icon_new (double rotation)
{
GtkNuclearIcon *nuclear;
nuclear = g_object_new (GTK_TYPE_NUCLEAR_ICON, NULL);
nuclear->rotation = rotation;
return GDK_PAINTABLE (nuclear);
}
GtkWidget *
do_paintable (GtkWidget *do_widget)
{
GdkPaintable *nuclear;
GtkWidget *image;
if (!window)
{
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Nuclear Icon");
gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
nuclear = gtk_nuclear_icon_new (0.0);
image = gtk_image_new_from_paintable (nuclear);
gtk_container_add (GTK_CONTAINER (window), image);
g_object_unref (nuclear);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_widget_destroy (window);
return window;
}

View File

@@ -0,0 +1,15 @@
#ifndef __PAINTABLE_H__
#define __PAINTABLE_H__
#include <gtk/gtk.h>
void gtk_nuclear_snapshot (GtkSnapshot *snapshot,
double width,
double height,
double rotation);
GdkPaintable * gtk_nuclear_icon_new (double rotation);
GdkPaintable * gtk_nuclear_animation_new (void);
GtkMediaStream *gtk_nuclear_media_stream_new (void);
#endif /* __PAINTABLE_H__ */

View File

@@ -0,0 +1,209 @@
/* Paintable/An animated paintable
*
* GdkPaintable also allows paintables to change.
*
* This demo code gives an example of how this could work. It builds
* on the previous simple example.
*
* Paintables can also change their size, this works similarly, but
* we will not demonstrate this here as our icon does not have any size.
*/
#include <gtk/gtk.h>
#include "paintable.h"
static GtkWidget *window = NULL;
/* First, add the boilerplate for the object itself.
* This part would normally go in the header.
*/
#define GTK_TYPE_NUCLEAR_ANIMATION (gtk_nuclear_animation_get_type ())
G_DECLARE_FINAL_TYPE (GtkNuclearAnimation, gtk_nuclear_animation, GTK, NUCLEAR_ANIMATION, GObject)
/* Do a full rotation in 5 seconds.
* We will register the timeout for doing a single step to
* be executed every 10ms, which means after 1000 steps
* 10s will have elapsed.
*/
#define MAX_PROGRESS 500
/* Declare the struct. */
struct _GtkNuclearAnimation
{
GObject parent_instance;
/* This variable stores the progress of our animation.
* We just count upwards until we hit MAX_PROGRESS and
* then start from scratch.
*/
int progress;
/* This variable holds the ID of the timer that updates
* our progress variable.
* We need to keep track of it so that we can remove it
* again.
*/
guint source_id;
};
struct _GtkNuclearAnimationClass
{
GObjectClass parent_class;
};
/* Again, we implement the functionality required by the GdkPaintable interface */
static void
gtk_nuclear_animation_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (paintable);
/* We call the function from the previous example here. */
gtk_nuclear_snapshot (snapshot,
width, height,
2 * G_PI * nuclear->progress / MAX_PROGRESS);
}
static GdkPaintable *
gtk_nuclear_animation_get_current_image (GdkPaintable *paintable)
{
GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (paintable);
/* For non-static paintables, this function needs to be implemented.
* It must return a static paintable with the same contents
* as this one currently has.
*
* Luckily we added the rotation property to the nuclear icon
* object previously, so we can just return an instance of that one.
*/
return gtk_nuclear_icon_new (2 * G_PI * nuclear->progress / MAX_PROGRESS);
}
static GdkPaintableFlags
gtk_nuclear_animation_get_flags (GdkPaintable *paintable)
{
/* This time, we cannot set the static contents flag because our animation
* changes the contents.
* However, our size still doesn't change, so report that flag.
*/
return GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_nuclear_animation_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = gtk_nuclear_animation_snapshot;
iface->get_current_image = gtk_nuclear_animation_get_current_image;
iface->get_flags = gtk_nuclear_animation_get_flags;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkNuclearAnimation, gtk_nuclear_animation, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_nuclear_animation_paintable_init))
/* This time, we need to implement the finalize function,
*/
static void
gtk_nuclear_animation_finalize (GObject *object)
{
GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (object);
/* Remove the timeout we registered when constructing
* the object.
*/
g_source_remove (nuclear->source_id);
/* Don't forget to chain up to the parent class' implementation
* of the finalize function.
*/
G_OBJECT_CLASS (gtk_nuclear_animation_parent_class)->finalize (object);
}
/* In the class declaration, we need to add our finalize function.
*/
static void
gtk_nuclear_animation_class_init (GtkNuclearAnimationClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = gtk_nuclear_animation_finalize;
}
static gboolean
gtk_nuclear_animation_step (gpointer data)
{
GtkNuclearAnimation *nuclear = data;
/* Add 1 to the progress and reset it when we've reached
* the maximum value.
* The animation will rotate by 360 degrees at MAX_PROGRESS
* so it will be identical to the original unrotated one.
*/
nuclear->progress = (nuclear->progress + 1) % MAX_PROGRESS;
/* Now we need to tell all listeners that we've changed out contents
* so that they can redraw this paintable.
*/
gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
/* We want this timeout function to be called repeatedly,
* so we return this value here.
* If this was a single-shot timeout, we could also
* return G_SOURCE_REMOVE here to get rid of it.
*/
return G_SOURCE_CONTINUE;
}
static void
gtk_nuclear_animation_init (GtkNuclearAnimation *nuclear)
{
/* Add a timer here that constantly updates our animations.
* We want to update it often enough to guarantee a smooth animation.
*
* Ideally, we'd attach to the frame clock, but because we do
* not have it available here, we just use a regular timeout
* that hopefully triggers often enough to be smooth.
*/
nuclear->source_id = g_timeout_add (10,
gtk_nuclear_animation_step,
nuclear);
}
/* And finally, we add the simple constructor we declared in the header. */
GdkPaintable *
gtk_nuclear_animation_new (void)
{
return g_object_new (GTK_TYPE_NUCLEAR_ANIMATION, NULL);
}
GtkWidget *
do_paintable_animated (GtkWidget *do_widget)
{
GdkPaintable *nuclear;
GtkWidget *image;
if (!window)
{
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Nuclear Animation");
gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
nuclear = gtk_nuclear_animation_new ();
image = gtk_image_new_from_paintable (nuclear);
gtk_container_add (GTK_CONTAINER (window), image);
g_object_unref (nuclear);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_widget_destroy (window);
return window;
}

View File

@@ -0,0 +1,312 @@
/* Paintable/A media stream
*
* GdkPaintable is also used by the GtkMediaStream class.
*
* This demo code turns the nuclear media_stream into the object
* GTK uses for videos. This allows treating the icon like a
* regular video, so we can for example attach controls to it.
*
* After all, what good is an media_stream if one cannot pause
* it.
*/
#include <gtk/gtk.h>
#include "paintable.h"
static GtkWidget *window = NULL;
/* First, add the boilerplate for the object itself.
* This part would normally go in the header.
*/
#define GTK_TYPE_NUCLEAR_MEDIA_STREAM (gtk_nuclear_media_stream_get_type ())
G_DECLARE_FINAL_TYPE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK, NUCLEAR_MEDIA_STREAM, GtkMediaStream)
/* Do a full rotation in 5 seconds.
*
* We do not save steps here but real timestamps.
* GtkMediaStream uses microseconds, so we will do so, too.
*/
#define DURATION (5 * G_USEC_PER_SEC)
/* Declare the struct. */
struct _GtkNuclearMediaStream
{
/* We now inherit from the media stream object. */
GtkMediaStream parent_instance;
/* This variable stores the progress of our video.
*/
gint64 progress;
/* This variable stores the timestamp of the last
* time we updated the progress variable when the
* video is currently playing.
* This is so that we can always accurately compute the
* progress we've had, even if the timeout does not
* exactly work.
*/
gint64 last_time;
/* This variable again holds the ID of the timer that
* updates our progress variable. Nothing changes about
* how this works compared to the previous example.
*/
guint source_id;
};
struct _GtkNuclearMediaStreamClass
{
GObjectClass parent_class;
};
/* GtkMediaStream is a GdkPaintable. So when we want to display video,
* we have to implement the interface, just like in the animation example.
*/
static void
gtk_nuclear_media_stream_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (paintable);
/* We call the function from the previous example here. */
gtk_nuclear_snapshot (snapshot,
width, height,
2 * G_PI * nuclear->progress / DURATION);
}
static GdkPaintable *
gtk_nuclear_media_stream_get_current_image (GdkPaintable *paintable)
{
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (paintable);
/* Same thing as with the animation */
return gtk_nuclear_icon_new (2 * G_PI * nuclear->progress / DURATION);
}
static GdkPaintableFlags
gtk_nuclear_media_stream_get_flags (GdkPaintable *paintable)
{
/* And same thing as with the animation over here, too. */
return GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_nuclear_media_stream_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = gtk_nuclear_media_stream_snapshot;
iface->get_current_image = gtk_nuclear_media_stream_get_current_image;
iface->get_flags = gtk_nuclear_media_stream_get_flags;
}
/* This time, we inherit from GTK_TYPE_MEDIA_STREAM */
G_DEFINE_TYPE_WITH_CODE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK_TYPE_MEDIA_STREAM,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_nuclear_media_stream_paintable_init))
static gboolean
gtk_nuclear_media_stream_step (gpointer data)
{
GtkNuclearMediaStream *nuclear = data;
gint64 current_time;
/* Compute the time that has elapsed since the last time we were called
* and add it to our current progress.
*/
current_time = g_source_get_time (g_main_current_source ());
nuclear->progress += current_time - nuclear->last_time;
/* Check if we've ended */
if (nuclear->progress > DURATION)
{
if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (nuclear)))
{
/* We're looping. So make the progress loop using modulo */
nuclear->progress %= DURATION;
}
else
{
/* Just make sure we don't overflow */
nuclear->progress = DURATION;
}
}
/* Update the last time to the current timestamp. */
nuclear->last_time = current_time;
/* Update the timestamp of the media stream */
gtk_media_stream_update (GTK_MEDIA_STREAM (nuclear), nuclear->progress);
/* We also need to invalidate our contents again.
* After all, we are a video and not just an audio stream.
*/
gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
/* Now check if we have finished playing and if so,
* tell the media stream. The media stream will then
* call our pause function to pause the stream.
*/
if (nuclear->progress >= DURATION)
gtk_media_stream_ended (GTK_MEDIA_STREAM (nuclear));
/* The timeout function is removed by the pause function,
* so we can just always return this value.
*/
return G_SOURCE_CONTINUE;
}
static gboolean
gtk_nuclear_media_stream_play (GtkMediaStream *stream)
{
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
/* If we're already at the end of the stream, we don't want
* to start playing and exit early.
*/
if (nuclear->progress >= DURATION)
return FALSE;
/* This time, we add the source only when we start playing.
*/
nuclear->source_id = g_timeout_add (10,
gtk_nuclear_media_stream_step,
nuclear);
/* We also want to initialize our time, so that we can
* do accurate updates.
*/
nuclear->last_time = g_get_monotonic_time ();
/* We successfully started playing, so we return TRUE here. */
return TRUE;
}
static void
gtk_nuclear_media_stream_pause (GtkMediaStream *stream)
{
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
/* This function will be called when a playing stream
* gets paused.
* So we remove the updating source here and set it
* back to 0 so that the finalize function doesn't try
* to remove it again.
*/
g_source_remove (nuclear->source_id);
nuclear->source_id = 0;
nuclear->last_time = 0;
}
static void
gtk_nuclear_media_stream_seek (GtkMediaStream *stream,
gint64 timestamp)
{
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
/* This is optional functionality for media streams,
* but not being able to seek is kinda boring.
* And it's trivial to implement, so let's go for it.
*/
nuclear->progress = timestamp;
/* Media streams are asynchronous, so seeking can take a while.
* We however don't need that functionality, so we can just
* report success.
*/
gtk_media_stream_seek_success (stream);
/* We also have to update our timestamp and tell the
* paintable interface abbout the seek
*/
gtk_media_stream_update (stream, nuclear->progress);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
}
/* Again, we need to implement the finalize function.
*/
static void
gtk_nuclear_media_stream_finalize (GObject *object)
{
GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (object);
/* This time, we need to check if the source exists before
* removing it as it only exists while we are playing.
*/
if (nuclear->source_id > 0)
g_source_remove (nuclear->source_id);
/* Don't forget to chain up to the parent class' implementation
* of the finalize function.
*/
G_OBJECT_CLASS (gtk_nuclear_media_stream_parent_class)->finalize (object);
}
/* In the class declaration, we need to implement the media stream */
static void
gtk_nuclear_media_stream_class_init (GtkNuclearMediaStreamClass *klass)
{
GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
stream_class->play = gtk_nuclear_media_stream_play;
stream_class->pause = gtk_nuclear_media_stream_pause;
stream_class->seek = gtk_nuclear_media_stream_seek;
gobject_class->finalize = gtk_nuclear_media_stream_finalize;
}
static void
gtk_nuclear_media_stream_init (GtkNuclearMediaStream *nuclear)
{
/* This time, we don't have to add a timer here, because media
* streams start paused.
*
* However, media streams need to tell GTK once they are intialized,
* so we do that here.
*/
gtk_media_stream_prepared (GTK_MEDIA_STREAM (nuclear),
FALSE,
TRUE,
TRUE,
DURATION);
}
/* And finally, we add the simple constructor we declared in the header. */
GtkMediaStream *
gtk_nuclear_media_stream_new (void)
{
return g_object_new (GTK_TYPE_NUCLEAR_MEDIA_STREAM, NULL);
}
GtkWidget *
do_paintable_mediastream (GtkWidget *do_widget)
{
GtkMediaStream *nuclear;
GtkWidget *video;
if (!window)
{
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Nuclear MediaStream");
gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
nuclear = gtk_nuclear_media_stream_new ();
gtk_media_stream_set_loop (GTK_MEDIA_STREAM (nuclear), TRUE);
video = gtk_video_new_for_media_stream (nuclear);
gtk_container_add (GTK_CONTAINER (window), video);
g_object_unref (nuclear);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_widget_destroy (window);
return window;
}

View File

@@ -0,0 +1,96 @@
/* Video Player
*
* This is a simple video player using just GTK widgets.
*/
#include <gtk/gtk.h>
static GtkWidget *window = NULL;
static void
open_dialog_response_cb (GtkWidget *dialog,
int response,
GtkWidget *video)
{
gtk_widget_hide (dialog);
if (response == GTK_RESPONSE_ACCEPT)
{
GFile *file;
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
gtk_video_set_file (GTK_VIDEO (video), file);
g_object_unref (file);
}
gtk_widget_destroy (dialog);
}
static void
open_clicked_cb (GtkWidget *button,
GtkWidget *video)
{
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Select a video",
GTK_WINDOW (gtk_widget_get_toplevel (button)),
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response_cb), video);
gtk_widget_show (dialog);
}
static void
fullscreen_clicked_cb (GtkWidget *button,
gpointer unused)
{
GtkWidget *window = gtk_widget_get_toplevel (button);
gtk_window_fullscreen (GTK_WINDOW (window));
}
GtkWidget *
do_video_player (GtkWidget *do_widget)
{
GtkWidget *title;
GtkWidget *video;
GtkWidget *open_button;
GtkWidget *fullscreen_button;
if (!window)
{
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Video Player");
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_widget_destroyed), &window);
video = gtk_video_new ();
gtk_container_add (GTK_CONTAINER (window), video);
title = gtk_header_bar_new ();
gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (title), TRUE);
gtk_header_bar_set_title (GTK_HEADER_BAR (title), "Video Player");
gtk_window_set_titlebar (GTK_WINDOW (window), title);
open_button = gtk_button_new_with_mnemonic ("_Open");
g_signal_connect (open_button, "clicked", G_CALLBACK (open_clicked_cb), video);
gtk_header_bar_pack_start (GTK_HEADER_BAR (title), open_button);
fullscreen_button = gtk_button_new_from_icon_name ("view-fullscreen-symbolic");
g_signal_connect (fullscreen_button, "clicked", G_CALLBACK (fullscreen_clicked_cb), NULL);
gtk_header_bar_pack_end (GTK_HEADER_BAR (title), fullscreen_button);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_widget_destroy (window);
return window;
}

View File

@@ -81,6 +81,18 @@ create_label (void)
return w;
}
static GtkWidget *
create_video (void)
{
GtkMediaStream *stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
GtkWidget *w = gtk_image_new_from_paintable (GDK_PAINTABLE (stream));
gtk_media_stream_set_loop (stream, TRUE);
gtk_media_stream_play (stream);
g_object_unref (stream);
return w;
}
static GtkWidget *
create_gears (void)
{
@@ -102,6 +114,7 @@ static const struct {
{ "Label" , create_label },
{ "Spinner" , create_spinner },
{ "Spinbutton", create_spinbutton },
{ "Video", create_video },
{ "Gears", create_gears },
};

View File

@@ -733,13 +733,14 @@ gdk_paintable_invalidate_size
<TITLE>Textures</TITLE>
<FILE>textures</FILE>
GdkTexture
gdk_texture_new_for_data
gdk_texture_new_for_pixbuf
gdk_texture_new_from_resource
gdk_texture_new_from_file
gdk_texture_get_width
gdk_texture_get_height
gdk_texture_download
GdkMemoryFormat
GDK_MEMORY_FORMAT_DEFAULT
gdk_memory_texture_new
gdk_gl_texture_new
gdk_gl_texture_release
@@ -750,6 +751,16 @@ gdk_texture_get_type
GDK_TYPE_TEXTURE
GDK_IS_TEXTURE
GDK_TEXTURE
GdkGLTextureClass
gdk_gl_texture_get_type
GDK_TYPE_GL_TEXTURE
GDK_IS_GL_TEXTURE
GDK_GL_TEXTURE
GdkMemoryTextureClass
gdk_memory_texture_get_type
GDK_TYPE_MEMORY_TEXTURE
GDK_IS_MEMORY_TEXTURE
GDK_MEMORY_TEXTURE
</SECTION>
<SECTION>

View File

@@ -133,28 +133,26 @@ gdk_gl_texture_get_id (GdkGLTexture *self)
/**
* gdk_gl_texture_release:
* @texture: a #GdkTexture wrapping a GL texture
* @self: a #GdkTexture wrapping a GL texture
*
* Releases the GL resources held by a #GdkTexture that
* was created with gdk_texture_new_for_gl().
* Releases the GL resources held by a #GdkGLTexture that
* was created with gdk_gl_texture_new().
*
* The texture contents are still available via the
* gdk_texture_download() function, after this function
* has been called.
*/
void
gdk_gl_texture_release (GdkTexture *texture)
gdk_gl_texture_release (GdkGLTexture *self)
{
GdkGLTexture *self;
GdkWindow *window;
GdkTexture *texture;
cairo_t *cr;
g_return_if_fail (GDK_IS_GL_TEXTURE (texture));
self = GDK_GL_TEXTURE (texture);
g_return_if_fail (GDK_IS_GL_TEXTURE (self));
g_return_if_fail (self->saved == NULL);
texture = GDK_TEXTURE (self);
self->saved = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
texture->width, texture->height);
@@ -178,7 +176,7 @@ gdk_gl_texture_release (GdkTexture *texture)
}
/**
* gdk_gl_texture_new: (constructor)
* gdk_gl_texture_new:
* @context: a #GdkGLContext
* @id: the ID of a texture that was created with @context
* @width: the nominal width of the texture

View File

@@ -23,11 +23,23 @@
#error "Only <gdk/gdk.h> can be included directly."
#endif
#include <gdk/gdktypes.h>
#include <gdk/gdkglcontext.h>
#include <gdk/gdktexture.h>
G_BEGIN_DECLS
#define GDK_TYPE_GL_TEXTURE (gdk_gl_texture_get_type ())
#define GDK_GL_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_GL_TEXTURE, GdkGLTexture))
#define GDK_IS_GL_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_GL_TEXTURE))
typedef struct _GdkGLTexture GdkGLTexture;
typedef struct _GdkGLTextureClass GdkGLTextureClass;
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkGLTexture, g_object_unref)
GDK_AVAILABLE_IN_ALL
GType gdk_gl_texture_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GdkTexture * gdk_gl_texture_new (GdkGLContext *context,
@@ -38,7 +50,7 @@ GdkTexture * gdk_gl_texture_new (GdkGLContext
gpointer data);
GDK_AVAILABLE_IN_ALL
void gdk_gl_texture_release (GdkTexture *texture);
void gdk_gl_texture_release (GdkGLTexture *texture);
G_END_DECLS

View File

@@ -7,10 +7,6 @@
G_BEGIN_DECLS
#define GDK_TYPE_GL_TEXTURE (gdk_gl_texture_get_type ())
G_DECLARE_FINAL_TYPE (GdkGLTexture, gdk_gl_texture, GDK, GL_TEXTURE, GdkTexture)
GdkGLContext * gdk_gl_texture_get_context (GdkGLTexture *self);
guint gdk_gl_texture_get_id (GdkGLTexture *self);

View File

@@ -68,6 +68,38 @@ typedef enum {
GDK_MEMORY_N_FORMATS
} GdkMemoryFormat;
/**
* GDK_MEMORY_DEFAULT:
*
* This is the default memory format used by GTK and is the format
* provided by gdk_texture_download(). It is equal to
* %CAIRO_FORMAT_ARGB32.
*
* Be aware that unlike the #GdkMemoryFormat values, this format is
* different for different endianness.
*/
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GDK_MEMORY_DEFAULT GDK_MEMORY_B8G8R8A8_PREMULTIPLIED
#elif G_BYTE_ORDER == G_BIG_ENDIAN
#define GDK_MEMORY_DEFAULT GDK_MEMORY_A8R8G8B8_PREMULTIPLIED
#else
#error "Unknown byte order for GDK_MEMORY_DEFAULT"
#endif
#define GDK_TYPE_MEMORY_TEXTURE (gdk_memory_texture_get_type ())
#define GDK_MEMORY_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_MEMORY_TEXTURE, GdkMemoryTexture))
#define GDK_IS_MEMORY_TEXTURE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_MEMORY_TEXTURE))
typedef struct _GdkMemoryTexture GdkMemoryTexture;
typedef struct _GdkMemoryTextureClass GdkMemoryTextureClass;
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkMemoryTexture, g_object_unref)
GDK_AVAILABLE_IN_ALL
GType gdk_memory_texture_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GdkTexture * gdk_memory_texture_new (int width,
int height,

View File

@@ -29,17 +29,7 @@ G_BEGIN_DECLS
#define GDK_MEMORY_GDK_PIXBUF_OPAQUE GDK_MEMORY_R8G8B8
#define GDK_MEMORY_GDK_PIXBUF_ALPHA GDK_MEMORY_R8G8B8A8
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_B8G8R8A8_PREMULTIPLIED
#elif G_BYTE_ORDER == G_BIG_ENDIAN
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_A8R8G8B8_PREMULTIPLIED
#else
#error "Unknown byte order."
#endif
#define GDK_TYPE_MEMORY_TEXTURE (gdk_memory_texture_get_type ())
G_DECLARE_FINAL_TYPE (GdkMemoryTexture, gdk_memory_texture, GDK, MEMORY_TEXTURE, GdkTexture)
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_DEFAULT
GdkMemoryFormat gdk_memory_texture_get_format (GdkMemoryTexture *self);
const guchar * gdk_memory_texture_get_data (GdkMemoryTexture *self);

View File

@@ -266,48 +266,6 @@ gdk_texture_init (GdkTexture *self)
{
}
/**
* gdk_texture_new_for_data:
* @data: (array): the pixel data
* @width: the number of pixels in each row
* @height: the number of rows
* @stride: the distance from the beginning of one row to the next, in bytes
*
* Creates a new texture object holding the given data.
* The data is assumed to be in CAIRO_FORMAT_ARGB32 format.
*
* Returns: a new #GdkTexture
*/
GdkTexture *
gdk_texture_new_for_data (const guchar *data,
int width,
int height,
int stride)
{
GdkTexture *texture;
cairo_surface_t *original, *copy;
cairo_t *cr;
g_return_val_if_fail (width > 0, NULL);
g_return_val_if_fail (height > 0, NULL);
original = cairo_image_surface_create_for_data ((guchar *) data, CAIRO_FORMAT_ARGB32, width, height, stride);
copy = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create (copy);
cairo_set_source_surface (cr, original, 0, 0);
cairo_paint (cr);
cairo_destroy (cr);
texture = gdk_texture_new_for_surface (copy);
cairo_surface_destroy (copy);
cairo_surface_finish (original);
cairo_surface_destroy (original);
return texture;
}
/**
* gdk_texture_new_for_surface:
* @surface: a cairo image surface

View File

@@ -42,11 +42,6 @@ typedef struct _GdkTextureClass GdkTextureClass;
GDK_AVAILABLE_IN_ALL
GType gdk_texture_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GdkTexture * gdk_texture_new_for_data (const guchar *data,
int width,
int height,
int stride);
GDK_AVAILABLE_IN_ALL
GdkTexture * gdk_texture_new_for_pixbuf (GdkPixbuf *pixbuf);
GDK_AVAILABLE_IN_ALL

View File

@@ -24,6 +24,7 @@
#include "gskdebugprivate.h"
#include "gskrendererprivate.h"
#include "gskroundedrectprivate.h"
#include "gdk/gdktextureprivate.h"
static gboolean
@@ -729,6 +730,7 @@ gsk_texture_node_deserialize (GVariant *variant,
guint32 width, height;
GVariant *pixel_variant;
gsize n_pixels;
GBytes *bytes;
if (!check_variant_type (variant, GSK_TEXTURE_NODE_VARIANT_TYPE, error))
return NULL;
@@ -738,9 +740,23 @@ gsk_texture_node_deserialize (GVariant *variant,
&width, &height, &pixel_variant);
/* XXX: Make this work without copying the data */
texture = gdk_texture_new_for_data (g_variant_get_fixed_array (pixel_variant, &n_pixels, sizeof (guint32)),
width, height, width * 4);
g_variant_unref (pixel_variant);
bytes = g_bytes_new_with_free_func (g_variant_get_fixed_array (pixel_variant, &n_pixels, sizeof (guint32)),
width * height * sizeof (guint32),
(GDestroyNotify) g_variant_unref,
pixel_variant);
if (n_pixels != width * height)
{
g_set_error (error, GSK_SERIALIZATION_ERROR, GSK_SERIALIZATION_INVALID_DATA,
"Expected %u pixels but got %"G_GSIZE_FORMAT" for %ux%u image",
width * height, n_pixels, width, height);
g_bytes_unref (bytes);
return NULL;
}
texture = gdk_memory_texture_new (width, height,
GDK_MEMORY_DEFAULT,
bytes,
width * 4);
node = gsk_texture_node_new (texture, &GRAPHENE_RECT_INIT(bounds[0], bounds[1], bounds[2], bounds[3]));

View File

@@ -633,6 +633,7 @@ gsk_vulkan_image_download (GskVulkanImage *self,
{
GskVulkanBuffer *buffer;
GdkTexture *texture;
GBytes *bytes;
guchar *mem;
gsk_vulkan_uploader_add_image_barrier (uploader,
@@ -671,7 +672,11 @@ gsk_vulkan_image_download (GskVulkanImage *self,
GSK_VK_CHECK (vkQueueWaitIdle, gdk_vulkan_context_get_queue (self->vulkan));
mem = gsk_vulkan_buffer_map (buffer);
texture = gdk_texture_new_for_data (mem, self->width, self->height, self->width * 4);
bytes = g_bytes_new (mem, self->width * self->height * 4);
texture = gdk_memory_texture_new (self->width, self->height,
GDK_MEMORY_DEFAULT,
bytes,
self->width * 4);
gsk_vulkan_buffer_unmap (buffer);
gsk_vulkan_buffer_free (buffer);

View File

@@ -137,6 +137,9 @@
#include <gtk/gtkliststore.h>
#include <gtk/gtklockbutton.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkmediacontrols.h>
#include <gtk/gtkmediafile.h>
#include <gtk/gtkmediastream.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtkmenubar.h>
#include <gtk/gtkmenubutton.h>
@@ -225,6 +228,7 @@
#include <gtk/gtktypebuiltins.h>
#include <gtk/gtktypes.h>
#include <gtk/gtkversion.h>
#include <gtk/gtkvideo.h>
#include <gtk/gtkviewport.h>
#include <gtk/gtkvolumebutton.h>
#include <gtk/gtkwidget.h>

View File

@@ -26,13 +26,14 @@
/* for the types only */
#include "gtk/gtkcssimagecrossfadeprivate.h"
#include "gtk/gtkcssimagefallbackprivate.h"
#include "gtk/gtkcssimageiconthemeprivate.h"
#include "gtk/gtkcssimagelinearprivate.h"
#include "gtk/gtkcssimagepaintprivate.h"
#include "gtk/gtkcssimageradialprivate.h"
#include "gtk/gtkcssimageurlprivate.h"
#include "gtk/gtkcssimagescaledprivate.h"
#include "gtk/gtkcssimagerecolorprivate.h"
#include "gtk/gtkcssimagefallbackprivate.h"
#include "gtk/gtkcssimagescaledprivate.h"
#include "gtk/gtkcssimageurlprivate.h"
#include "gtk/gtkcssimagewin32private.h"
G_DEFINE_ABSTRACT_TYPE (GtkCssImage, _gtk_css_image, G_TYPE_OBJECT)
@@ -501,6 +502,7 @@ gtk_css_image_get_parser_type (GtkCssParser *parser)
} image_types[] = {
{ "url", _gtk_css_image_url_get_type },
{ "-gtk-icontheme", _gtk_css_image_icon_theme_get_type },
{ "-gtk-paint", gtk_css_image_paint_get_type },
{ "-gtk-scaled", _gtk_css_image_scaled_get_type },
{ "-gtk-recolor", _gtk_css_image_recolor_get_type },
{ "-gtk-win32-theme-part", _gtk_css_image_win32_get_type },

147
gtk/gtkcssimagepaint.c Normal file
View File

@@ -0,0 +1,147 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include <string.h>
#include "gtkcssimagepaintprivate.h"
#include "gtkcssimageinvalidprivate.h"
#include "gtkcssimagepaintableprivate.h"
#include "gtkstyleproviderprivate.h"
G_DEFINE_TYPE (GtkCssImagePaint, gtk_css_image_paint, GTK_TYPE_CSS_IMAGE)
static void
gtk_css_image_paint_snapshot (GtkCssImage *image,
GtkSnapshot *snapshot,
double width,
double height)
{
}
static GtkCssImage *
gtk_css_image_paint_compute (GtkCssImage *image,
guint property_id,
GtkStyleProvider *provider,
GtkCssStyle *style,
GtkCssStyle *parent_style)
{
GtkCssImagePaint *paint = GTK_CSS_IMAGE_PAINT (image);
GdkPaintable *paintable, *static_paintable;
GtkCssImage *result;
paintable = gtk_style_provider_get_paint (provider, paint->name);
if (paintable == NULL)
return gtk_css_image_invalid_new ();
static_paintable = gdk_paintable_get_current_image (paintable);
result = gtk_css_image_paintable_new (paintable, static_paintable);
g_object_unref (static_paintable);
return result;
}
static gboolean
gtk_css_image_paint_equal (GtkCssImage *image1,
GtkCssImage *image2)
{
GtkCssImagePaint *paint1 = GTK_CSS_IMAGE_PAINT (image1);
GtkCssImagePaint *paint2 = GTK_CSS_IMAGE_PAINT (image2);
return g_str_equal (paint1->name, paint2->name);
}
static gboolean
gtk_css_image_paint_parse (GtkCssImage *image,
GtkCssParser *parser)
{
GtkCssImagePaint *paint = GTK_CSS_IMAGE_PAINT (image);
if (!_gtk_css_parser_try (parser, "-gtk-paint", TRUE))
{
_gtk_css_parser_error (parser, "'-gtk-paint'");
return FALSE;
}
if (!_gtk_css_parser_try (parser, "(", TRUE))
{
_gtk_css_parser_error (parser, "Expected '(' after '-gtk-paint'");
return FALSE;
}
paint->name = _gtk_css_parser_try_ident (parser, TRUE);
if (paint->name == NULL)
{
_gtk_css_parser_error (parser, "Expected the name of the paint");
return FALSE;
}
if (!_gtk_css_parser_try (parser, ")", TRUE))
{
_gtk_css_parser_error (parser,
"Expected ')' at end of '-gtk-paint'");
return FALSE;
}
return TRUE;
}
static void
gtk_css_image_paint_print (GtkCssImage *image,
GString *string)
{
GtkCssImagePaint *paint = GTK_CSS_IMAGE_PAINT (image);
g_string_append (string, "paint (");
g_string_append (string, paint->name);
g_string_append (string, ")");
}
static void
gtk_css_image_paint_dispose (GObject *object)
{
GtkCssImagePaint *paint = GTK_CSS_IMAGE_PAINT (object);
g_clear_pointer (&paint->name, g_free);
G_OBJECT_CLASS (gtk_css_image_paint_parent_class)->dispose (object);
}
static void
gtk_css_image_paint_class_init (GtkCssImagePaintClass *klass)
{
GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
image_class->compute = gtk_css_image_paint_compute;
image_class->snapshot = gtk_css_image_paint_snapshot;
image_class->parse = gtk_css_image_paint_parse;
image_class->print = gtk_css_image_paint_print;
image_class->equal = gtk_css_image_paint_equal;
object_class->dispose = gtk_css_image_paint_dispose;
}
static void
gtk_css_image_paint_init (GtkCssImagePaint *image_paint)
{
}

View File

@@ -122,7 +122,7 @@ gtk_css_image_paintable_is_dynamic (GtkCssImage *image)
{
GtkCssImagePaintable *paintable = GTK_CSS_IMAGE_PAINTABLE (image);
return (gdk_paintable_get_flags (paintable->paintable) & GDK_PAINTABLE_IMMUTABLE) == GDK_PAINTABLE_IMMUTABLE;
return (gdk_paintable_get_flags (paintable->paintable) & GDK_PAINTABLE_IMMUTABLE) != GDK_PAINTABLE_IMMUTABLE;
}
static GtkCssImage *

View File

@@ -0,0 +1,53 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_CSS_IMAGE_PAINT_PRIVATE_H__
#define __GTK_CSS_IMAGE_PAINT_PRIVATE_H__
#include "gtk/gtkcssimageprivate.h"
G_BEGIN_DECLS
#define GTK_TYPE_CSS_IMAGE_PAINT (gtk_css_image_paint_get_type ())
#define GTK_CSS_IMAGE_PAINT(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_CSS_IMAGE_PAINT, GtkCssImagePaint))
#define GTK_CSS_IMAGE_PAINT_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_CSS_IMAGE_PAINT, GtkCssImagePaintClass))
#define GTK_IS_CSS_IMAGE_PAINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_CSS_IMAGE_PAINT))
#define GTK_IS_CSS_IMAGE_PAINT_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_CSS_IMAGE_PAINT))
#define GTK_CSS_IMAGE_PAINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CSS_IMAGE_PAINT, GtkCssImagePaintClass))
typedef struct _GtkCssImagePaint GtkCssImagePaint;
typedef struct _GtkCssImagePaintClass GtkCssImagePaintClass;
struct _GtkCssImagePaint
{
GtkCssImage parent;
char *name; /* the name of the paint */
};
struct _GtkCssImagePaintClass
{
GtkCssImageClass parent_class;
};
GType gtk_css_image_paint_get_type (void) G_GNUC_CONST;
G_END_DECLS
#endif /* __GTK_CSS_IMAGE_PAINT_PRIVATE_H__ */

View File

@@ -386,7 +386,7 @@ delete_one_texture (gpointer data)
Texture *texture = data;
if (texture->holder)
gdk_gl_texture_release (texture->holder);
gdk_gl_texture_release (GDK_GL_TEXTURE (texture->holder));
if (texture->id != 0)
{

View File

@@ -119,6 +119,7 @@
#include "gtkdebugupdatesprivate.h"
#include "gtkdndprivate.h"
#include "gtkmain.h"
#include "gtkmediafileprivate.h"
#include "gtkmenu.h"
#include "gtkmodulesprivate.h"
#include "gtkprivate.h"
@@ -632,6 +633,7 @@ default_display_notify_cb (GdkDisplayManager *dm)
debug_flags[0].display = gdk_display_get_default ();
gtk_print_backends_init ();
gtk_im_modules_init ();
gtk_media_file_extension_init ();
_gtk_accessibility_init ();
}

501
gtk/gtkmediacontrols.c Normal file
View File

@@ -0,0 +1,501 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkmediacontrols.h"
#include "gtkadjustment.h"
#include "gtkbutton.h"
#include "gtkintl.h"
#include "gtklabel.h"
/**
* SECTION:gtkmediacontrols
* @title: GtkMediaControls
* @short_description: A widget showing controls for a media stream
*
* GtkMediaControls is a widget to show controls for a #GtkMediaStream
* and giving users a way to use it.
*/
struct _GtkMediaControls
{
GtkWidget parent_instance;
GtkMediaStream *stream;
GtkAdjustment *time_adjustment;
GtkAdjustment *volume_adjustment;
GtkWidget *box;
GtkWidget *play_button;
GtkWidget *time_box;
GtkWidget *time_label;
GtkWidget *seek_scale;
GtkWidget *duration_label;
};
enum
{
PROP_0,
PROP_MEDIA_STREAM,
N_PROPS
};
G_DEFINE_TYPE (GtkMediaControls, gtk_media_controls, GTK_TYPE_WIDGET)
static GParamSpec *properties[N_PROPS] = { NULL, };
/* FIXME: Remove
* See https://bugzilla.gnome.org/show_bug.cgi?id=679850 */
static char *
totem_time_to_string (gint64 usecs,
gboolean remaining,
gboolean force_hour)
{
int sec, min, hour, _time;
_time = (int) (usecs / G_USEC_PER_SEC);
/* When calculating the remaining time,
* we want to make sure that:
* current time + time remaining = total run time */
if (remaining)
_time++;
sec = _time % 60;
_time = _time - sec;
min = (_time % (60*60)) / 60;
_time = _time - (min * 60);
hour = _time / (60*60);
if (hour > 0 || force_hour) {
if (!remaining) {
/* hour:minutes:seconds */
/* Translators: This is a time format, like "-9:05:02" for 9
* hours, 5 minutes, and 2 seconds. You may change ":" to
* the separator that your locale uses or use "%Id" instead
* of "%d" if your locale uses localized digits.
*/
return g_strdup_printf (C_("long time format", "%d:%02d:%02d"), hour, min, sec);
} else {
/* -hour:minutes:seconds */
/* Translators: This is a time format, like "-9:05:02" for 9
* hours, 5 minutes, and 2 seconds playback remaining. You may
* change ":" to the separator that your locale uses or use
* "%Id" instead of "%d" if your locale uses localized digits.
*/
return g_strdup_printf (C_("long time format", "-%d:%02d:%02d"), hour, min, sec);
}
}
if (remaining) {
/* -minutes:seconds */
/* Translators: This is a time format, like "-5:02" for 5
* minutes and 2 seconds playback remaining. You may change
* ":" to the separator that your locale uses or use "%Id"
* instead of "%d" if your locale uses localized digits.
*/
return g_strdup_printf (C_("short time format", "-%d:%02d"), min, sec);
}
/* minutes:seconds */
/* Translators: This is a time format, like "5:02" for 5
* minutes and 2 seconds. You may change ":" to the
* separator that your locale uses or use "%Id" instead of
* "%d" if your locale uses localized digits.
*/
return g_strdup_printf (C_("short time format", "%d:%02d"), min, sec);
}
static void
time_adjustment_changed (GtkAdjustment *adjustment,
GtkMediaControls *controls)
{
if (controls->stream == NULL)
return;
/* We just updated the adjustment and it's correct now */
if (gtk_adjustment_get_value (adjustment) == (double) gtk_media_stream_get_timestamp (controls->stream) / G_USEC_PER_SEC)
return;
gtk_media_stream_seek (controls->stream,
gtk_adjustment_get_value (adjustment) * G_USEC_PER_SEC + 0.5);
}
static void
volume_adjustment_changed (GtkAdjustment *adjustment,
GtkMediaControls *controls)
{
if (controls->stream == NULL)
return;
/* We just updated the adjustment and it's correct now */
if (gtk_adjustment_get_value (adjustment) == gtk_media_stream_get_volume (controls->stream))
return;
gtk_media_stream_set_muted (controls->stream, gtk_adjustment_get_value (adjustment) == 0.0);
gtk_media_stream_set_volume (controls->stream, gtk_adjustment_get_value (adjustment));
}
static void
play_button_clicked (GtkWidget *button,
GtkMediaControls *controls)
{
if (controls->stream == NULL)
return;
gtk_media_stream_set_playing (controls->stream,
!gtk_media_stream_get_playing (controls->stream));
}
static void
gtk_media_controls_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (widget);
gtk_widget_measure (controls->box,
orientation,
for_size,
minimum, natural,
minimum_baseline, natural_baseline);
}
static void
gtk_media_controls_size_allocate (GtkWidget *widget,
const GtkAllocation *allocation,
int baseline,
GtkAllocation *out_clip)
{
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (widget);
gtk_widget_size_allocate (controls->box, allocation, baseline, out_clip);
}
static void
gtk_media_controls_dispose (GObject *object)
{
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
gtk_media_controls_set_media_stream (controls, NULL);
g_clear_pointer (&controls->box, gtk_widget_unparent);
G_OBJECT_CLASS (gtk_media_controls_parent_class)->dispose (object);
}
static void
gtk_media_controls_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
switch (property_id)
{
case PROP_MEDIA_STREAM:
g_value_set_object (value, controls->stream);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_media_controls_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object);
switch (property_id)
{
case PROP_MEDIA_STREAM:
gtk_media_controls_set_media_stream (controls, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_media_controls_class_init (GtkMediaControlsClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
widget_class->measure = gtk_media_controls_measure;
widget_class->size_allocate = gtk_media_controls_size_allocate;
gobject_class->dispose = gtk_media_controls_dispose;
gobject_class->get_property = gtk_media_controls_get_property;
gobject_class->set_property = gtk_media_controls_set_property;
/**
* GtkMediaControls:media-stream:
*
* The media-stream managed by this object or %NULL if none.
*/
properties[PROP_MEDIA_STREAM] =
g_param_spec_object ("media-stream",
P_("Media Stream"),
P_("The media stream managed"),
GTK_TYPE_MEDIA_STREAM,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkmediacontrols.ui");
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_adjustment);
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, volume_adjustment);
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, box);
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, play_button);
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_box);
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_label);
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, seek_scale);
gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, duration_label);
gtk_widget_class_bind_template_callback (widget_class, play_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, time_adjustment_changed);
gtk_widget_class_bind_template_callback (widget_class, volume_adjustment_changed);
gtk_widget_class_set_css_name (widget_class, I_("controls"));
}
static void
gtk_media_controls_init (GtkMediaControls *controls)
{
gtk_widget_init_template (GTK_WIDGET (controls));
gtk_widget_set_has_window (GTK_WIDGET (controls), FALSE);
}
/**
* gtk_media_controls_new:
* @stream: (allow-none) (transfer none): a #GtkMediaStream to
* manage or %NULL for none.
*
* Creates a new #GtkMediaControls managing the @stream passed to it.
*
* Returns: a new #GtkMediaControls
**/
GtkWidget *
gtk_media_controls_new (GtkMediaStream *stream)
{
return g_object_new (GTK_TYPE_MEDIA_CONTROLS,
"media-stream", stream,
NULL);
}
/**
* gtk_media_controls_get_media_stream:
* @controls: a #GtkMediaControls
*
* Gets the media stream managed by @controls or %NULL if none.
*
* Returns: (nullable): The media stream managed by @controls
**/
GtkMediaStream *
gtk_media_controls_get_media_stream (GtkMediaControls *controls)
{
g_return_val_if_fail (GTK_IS_MEDIA_CONTROLS (controls), NULL);
return controls->stream;
}
static void
update_timestamp (GtkMediaControls *controls)
{
gint64 timestamp, duration;
char *time_string;
if (controls->stream)
{
timestamp = gtk_media_stream_get_timestamp (controls->stream);
duration = gtk_media_stream_get_duration (controls->stream);
}
else
{
timestamp = 0;
duration = 0;
}
time_string = totem_time_to_string (timestamp, FALSE, FALSE);
gtk_label_set_text (GTK_LABEL (controls->time_label), time_string);
g_free (time_string);
if (duration > 0)
{
time_string = totem_time_to_string (duration > timestamp ? duration - timestamp : 0, TRUE, FALSE);
gtk_label_set_text (GTK_LABEL (controls->duration_label), time_string);
g_free (time_string);
gtk_adjustment_set_value (controls->time_adjustment, (double) timestamp / G_USEC_PER_SEC);
}
}
static void
update_duration (GtkMediaControls *controls)
{
gint64 timestamp, duration;
char *time_string;
if (controls->stream)
{
timestamp = gtk_media_stream_get_timestamp (controls->stream);
duration = gtk_media_stream_get_duration (controls->stream);
}
else
{
timestamp = 0;
duration = 0;
}
time_string = totem_time_to_string (duration > timestamp ? duration - timestamp : 0, TRUE, FALSE);
gtk_label_set_text (GTK_LABEL (controls->duration_label), time_string);
gtk_widget_set_visible (controls->duration_label, duration > 0);
g_free (time_string);
gtk_adjustment_set_upper (controls->time_adjustment,
gtk_adjustment_get_page_size (controls->time_adjustment)
+ (double) duration / G_USEC_PER_SEC);
gtk_adjustment_set_value (controls->time_adjustment, (double) timestamp / G_USEC_PER_SEC);
}
static void
update_playing (GtkMediaControls *controls)
{
gboolean playing;
const char *icon_name;
if (controls->stream)
playing = gtk_media_stream_get_playing (controls->stream);
else
playing = FALSE;
if (playing)
icon_name = "media-playback-pause-symbolic";
else
icon_name = "media-playback-start-symbolic";
gtk_button_set_icon_name (GTK_BUTTON (controls->play_button), icon_name);
}
static void
update_seekable (GtkMediaControls *controls)
{
gboolean seekable;
if (controls->stream)
seekable = gtk_media_stream_is_seekable (controls->stream);
else
seekable = FALSE;
gtk_widget_set_sensitive (controls->seek_scale, seekable);
}
static void
update_volume (GtkMediaControls *controls)
{
double volume;
if (controls->stream == NULL)
volume = 1.0;
else if (gtk_media_stream_get_muted (controls->stream))
volume = 0.0;
else
volume = gtk_media_stream_get_volume (controls->stream);
gtk_adjustment_set_value (controls->volume_adjustment, volume);
}
static void
update_all (GtkMediaControls *controls)
{
update_timestamp (controls);
update_duration (controls);
update_playing (controls);
update_seekable (controls);
update_volume (controls);
}
static void
gtk_media_controls_notify_cb (GtkMediaStream *stream,
GParamSpec *pspec,
GtkMediaControls *controls)
{
if (g_str_equal (pspec->name, "timestamp"))
update_timestamp (controls);
else if (g_str_equal (pspec->name, "duration"))
update_duration (controls);
else if (g_str_equal (pspec->name, "playing"))
update_playing (controls);
else if (g_str_equal (pspec->name, "seekable"))
update_seekable (controls);
else if (g_str_equal (pspec->name, "muted"))
update_volume (controls);
else if (g_str_equal (pspec->name, "volume"))
update_volume (controls);
}
void
gtk_media_controls_set_media_stream (GtkMediaControls *controls,
GtkMediaStream *stream)
{
g_return_if_fail (GTK_IS_MEDIA_CONTROLS (controls));
g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream));
if (controls->stream == stream)
return;
if (controls->stream)
{
g_signal_handlers_disconnect_by_func (controls->stream,
gtk_media_controls_notify_cb,
controls);
g_object_unref (controls->stream);
controls->stream = NULL;
}
if (stream)
{
controls->stream = g_object_ref (stream);
g_signal_connect (controls->stream,
"notify",
G_CALLBACK (gtk_media_controls_notify_cb),
controls);
}
update_all (controls);
gtk_widget_set_sensitive (controls->box, stream != NULL);
g_object_notify_by_pspec (G_OBJECT (controls), properties[PROP_MEDIA_STREAM]);
}

45
gtk/gtkmediacontrols.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_MEDIA_CONTROLS_H__
#define __GTK_MEDIA_CONTROLS_H__
#include <gtk/gtkmediastream.h>
#include <gtk/gtkwidget.h>
G_BEGIN_DECLS
#define GTK_TYPE_MEDIA_CONTROLS (gtk_media_controls_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkMediaControls, gtk_media_controls, GTK, MEDIA_CONTROLS, GtkWidget)
GDK_AVAILABLE_IN_ALL
GtkWidget *gtk_media_controls_new (GtkMediaStream *stream);
GDK_AVAILABLE_IN_ALL
GtkMediaStream *gtk_media_controls_get_media_stream (GtkMediaControls *controls);
GDK_AVAILABLE_IN_ALL
void gtk_media_controls_set_media_stream (GtkMediaControls *controls,
GtkMediaStream *stream);
G_END_DECLS
#endif /* __GTK_MEDIA_CONTROLS_H__ */

629
gtk/gtkmediafile.c Normal file
View File

@@ -0,0 +1,629 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkmediafileprivate.h"
#include "gtkdebug.h"
#include "gtkintl.h"
#include "gtkmodulesprivate.h"
#include "gtknomediafileprivate.h"
/**
* SECTION:gtkmediafile
* @Short_description: Open media files for use in GTK
* @Title: GtkMediaFile
* @See_also: #GtkMediaStream
*
* #GtkMediaFile is the implementation for media file usage with #GtkMediaStream.
*
* This provides a simple way to play back video files with GTK.
*/
typedef struct _GtkMediaFilePrivate GtkMediaFilePrivate;
struct _GtkMediaFilePrivate
{
GFile *file;
GInputStream *input_stream;
};
enum {
PROP_0,
PROP_FILE,
PROP_INPUT_STREAM,
N_PROPS,
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkMediaFile, gtk_media_file, GTK_TYPE_MEDIA_STREAM,
G_ADD_PRIVATE (GtkMediaFile))
#define GTK_MEDIA_FILE_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
g_critical ("Media file of type '%s' does not implement GtkMediaFile::" # method, G_OBJECT_TYPE_NAME (obj))
static void
gtk_media_file_default_open (GtkMediaFile *self)
{
GTK_MEDIA_FILE_WARN_NOT_IMPLEMENTED_METHOD (self, open);
}
static void
gtk_media_file_default_close (GtkMediaFile *self)
{
gtk_media_stream_unprepared (GTK_MEDIA_STREAM (self));
}
static void
gtk_media_file_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkMediaFile *self = GTK_MEDIA_FILE (object);
switch (prop_id)
{
case PROP_FILE:
gtk_media_file_set_file (self, g_value_get_object (value));
break;
case PROP_INPUT_STREAM:
gtk_media_file_set_input_stream (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_media_file_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkMediaFile *self = GTK_MEDIA_FILE (object);
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
switch (prop_id)
{
case PROP_FILE:
g_value_set_object (value, priv->file);
break;
case PROP_INPUT_STREAM:
g_value_set_object (value, priv->input_stream);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_media_file_dispose (GObject *object)
{
GtkMediaFile *self = GTK_MEDIA_FILE (object);
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
g_clear_object (&priv->file);
g_clear_object (&priv->input_stream);
G_OBJECT_CLASS (gtk_media_file_parent_class)->dispose (object);
}
static void
gtk_media_file_class_init (GtkMediaFileClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
class->open = gtk_media_file_default_open;
class->close = gtk_media_file_default_close;
gobject_class->set_property = gtk_media_file_set_property;
gobject_class->get_property = gtk_media_file_get_property;
gobject_class->dispose = gtk_media_file_dispose;
/**
* GtkMediaFile:file:
*
* The file being played back or %NULL if not playing a file.
*/
properties[PROP_FILE] =
g_param_spec_object ("file",
P_("File"),
P_("File being played back"),
G_TYPE_FILE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkMediaFile:input-stream:
*
* The stream being played back or %NULL if not playing a stream, like when playing a file.
*/
properties[PROP_INPUT_STREAM] =
g_param_spec_object ("input-stream",
P_("Input stream"),
P_("Input stream being played back"),
G_TYPE_INPUT_STREAM,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
gtk_media_file_init (GtkMediaFile *self)
{
}
static GType
gtk_media_file_get_impl_type (void)
{
static GType impl_type = G_TYPE_NONE;
const char *extension_name;
GIOExtension *e;
GIOExtensionPoint *ep;
if (G_LIKELY (impl_type != G_TYPE_NONE))
return impl_type;
GTK_NOTE (MODULES, g_print ("Looking up MediaFile extension\n"));
ep = g_io_extension_point_lookup (GTK_MEDIA_FILE_EXTENSION_POINT_NAME);
e = NULL;
extension_name = g_getenv ("GTK_MEDIA");
if (extension_name)
{
if (g_str_equal (extension_name, "help"))
{
GList *l;
g_print ("Supported arguments for GTK_MEDIA environment variable:\n");
for (l = g_io_extension_point_get_extensions (ep); l; l = l->next)
{
e = l->data;
g_print ("%10s - %d\n", g_io_extension_get_name (e), g_io_extension_get_priority (e));
}
e = NULL;
}
else
{
e = g_io_extension_point_get_extension_by_name (ep, extension_name);
if (e == NULL)
{
g_warning ("Media extension \"%s\" from GTK_MEDIA environment variable not found.", extension_name);
}
}
}
if (e == NULL)
{
GList *l = g_io_extension_point_get_extensions (ep);
if (l == NULL)
{
g_error ("GTK was run without any GtkMediaFile extension being present. This must not happen.");
}
e = l->data;
}
impl_type = g_io_extension_get_type (e);
GTK_NOTE (MODULES, g_print ("Using %s from \"%s\" extension\n", g_type_name (impl_type), g_io_extension_get_name (e)));
return impl_type;
}
/**
* gtk_media_file_new:
*
* Creates a new empty media file.
*
* Returns: a new #GtkMediaFile
**/
GtkMediaStream *
gtk_media_file_new (void)
{
return g_object_new (gtk_media_file_get_impl_type (), NULL);
}
/**
* gtk_media_file_new_for_filename:
* @filename: filename to open
*
* This is a utility function that converts the given @filename
* to a #GFile and calls gtk_media_file_new_for_file().
*
* Returns: a new #GtkMediaFile playing @filename
**/
GtkMediaStream *
gtk_media_file_new_for_filename (const char *filename)
{
GtkMediaStream *result;
GFile *file;
if (filename)
file = g_file_new_for_path (filename);
else
file = NULL;
result = gtk_media_file_new_for_file (file);
if (file)
g_object_unref (file);
return result;
}
/**
* gtk_media_file_new_for_resource:
* @resource_path: resource path to open
*
* This is a utility function that converts the given @resource
* to a #GFile and calls gtk_media_file_new_for_file().
*
* Returns: a new #GtkMediaFile playing @resource_path
**/
GtkMediaStream *
gtk_media_file_new_for_resource (const char *resource_path)
{
GtkMediaStream *result;
GFile *file;
if (resource_path)
{
char *uri, *escaped;
escaped = g_uri_escape_string (resource_path,
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
uri = g_strconcat ("resource://", escaped, NULL);
g_free (escaped);
file = g_file_new_for_uri (uri);
g_free (uri);
}
else
{
file = NULL;
}
result = gtk_media_file_new_for_file (file);
if (file)
g_object_unref (file);
return result;
}
/**
* gtk_media_file_new_for_file:
* @file: (allow-none): The file to play
*
* Creates a new media file to play @file.
*
* Returns: a new #GtkMediaFile playing @file
**/
GtkMediaStream *
gtk_media_file_new_for_file (GFile *file)
{
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
return g_object_new (gtk_media_file_get_impl_type (),
"file", file,
NULL);
}
/**
* gtk_media_file_new_for_input_stream:
* @stream: (allow-none): The stream to play
*
* Creates a new media file to play @stream. If you want the
* resulting media to be seekable, the stream should implement
* the #GSeekable interface.
*
* Returns: a new #GtkMediaFile
**/
GtkMediaStream *
gtk_media_file_new_for_input_stream (GInputStream *stream)
{
g_return_val_if_fail (stream == NULL || G_IS_INPUT_STREAM (stream), NULL);
return g_object_new (gtk_media_file_get_impl_type (),
"input-stream", stream,
NULL);
}
static gboolean
gtk_media_file_is_open (GtkMediaFile *self)
{
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
return priv->file || priv->input_stream;
}
/**
* gtk_media_file_clear:
* @self: a #GtkMediaFile
*
* Resets the media file to be empty.
**/
void
gtk_media_file_clear (GtkMediaFile *self)
{
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
if (!gtk_media_file_is_open (self))
return;
GTK_MEDIA_FILE_GET_CLASS (self)->close (self);
if (priv->input_stream)
{
g_clear_object (&priv->input_stream);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPUT_STREAM]);
}
if (priv->file)
{
g_clear_object (&priv->file);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]);
}
}
/**
* gtk_media_file_set_filename:
* @self: a #GtkMediaFile
* @filename: (allow-none): name of file to play
*
* This is a utility function that converts the given @filename
* to a #GFile and calls gtk_media_file_set_file().
**/
void
gtk_media_file_set_filename (GtkMediaFile *self,
const char *filename)
{
GFile *file;
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
if (filename)
file = g_file_new_for_path (filename);
else
file = NULL;
gtk_media_file_set_file (self, file);
if (file)
g_object_unref (file);
}
/**
* gtk_media_file_set_resource:
* @self: a #GtkMediaFile
* @resource_path: (allow-none): path to resource to play
*
* This is a utility function that converts the given @resource_path
* to a #GFile and calls gtk_media_file_set_file().
**/
void
gtk_media_file_set_resource (GtkMediaFile *self,
const char *resource_path)
{
GFile *file;
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
if (resource_path)
{
char *uri, *escaped;
escaped = g_uri_escape_string (resource_path,
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
uri = g_strconcat ("resource://", escaped, NULL);
g_free (escaped);
file = g_file_new_for_uri (uri);
g_free (uri);
}
else
{
file = NULL;
}
gtk_media_file_set_file (self, file);
if (file)
g_object_unref (file);
}
/**
* gtk_media_file_set_file:
* @self: a #GtkMediaFile
* @file: (allow-none): the file to play
*
* If any file is still playing, stop playing it.
*
* Then start playing the given @file.
**/
void
gtk_media_file_set_file (GtkMediaFile *self,
GFile *file)
{
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
g_return_if_fail (file == NULL || G_IS_FILE (file));
if (file)
g_object_ref (file);
g_object_freeze_notify (G_OBJECT (self));
gtk_media_file_clear (self);
if (file)
{
priv->file = file;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]);
GTK_MEDIA_FILE_GET_CLASS (self)->open (self);
}
g_object_thaw_notify (G_OBJECT (self));
}
/**
* gtk_media_file_get_file:
* @self: a #GtkMediaFile
*
* Returns the file that @self is currently playing from.
*
* When @self is not playing or not playing from a file,
* %NULL is returned.
*
* Returns: (nullable): The currently playing file or %NULL if not
* playing from a file.
**/
GFile *
gtk_media_file_get_file (GtkMediaFile *self)
{
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
g_return_val_if_fail (GTK_IS_MEDIA_FILE (self), NULL);
return priv->file;
}
/**
* gtk_media_file_set_input_stream:
* @self: a #GtkMediaFile
* @stream: (allow-none): the stream to play from
*
* If anything is still playing, stop playing it. Then start
* playing the given @stream.
*
* Full control about the @stream is assumed for the duration of
* playback. The stream will not bt be closed.
**/
void
gtk_media_file_set_input_stream (GtkMediaFile *self,
GInputStream *stream)
{
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
g_return_if_fail (GTK_IS_MEDIA_FILE (self));
g_return_if_fail (stream == NULL || G_IS_INPUT_STREAM (stream));
if (stream)
g_object_ref (stream);
g_object_freeze_notify (G_OBJECT (self));
gtk_media_file_clear (self);
if (stream)
{
priv->input_stream = stream;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INPUT_STREAM]);
GTK_MEDIA_FILE_GET_CLASS (self)->open (self);
}
g_object_thaw_notify (G_OBJECT (self));
}
/**
* gtk_media_file_get_input_stream:
* @self: a #GtkMediaFile
*
* Returns the stream that @self is currently playing from.
*
* When @self is not playing or not playing from a stream,
* %NULL is returned.
*
* Returns: (nullable): The currently playing stream or %NULL if not
* playing from a stream.
**/
GInputStream *
gtk_media_file_get_input_stream (GtkMediaFile *self)
{
GtkMediaFilePrivate *priv = gtk_media_file_get_instance_private (self);
g_return_val_if_fail (GTK_IS_MEDIA_FILE (self), NULL);
return priv->input_stream;
}
void
gtk_media_file_extension_init (void)
{
GIOExtensionPoint *ep;
GIOModuleScope *scope;
char **paths;
int i;
GTK_NOTE (MODULES,
g_print ("Registering extension point %s\n", GTK_MEDIA_FILE_EXTENSION_POINT_NAME));
ep = g_io_extension_point_register (GTK_MEDIA_FILE_EXTENSION_POINT_NAME);
g_io_extension_point_set_required_type (ep, GTK_TYPE_MEDIA_FILE);
g_type_ensure (GTK_TYPE_NO_MEDIA_FILE);
scope = g_io_module_scope_new (G_IO_MODULE_SCOPE_BLOCK_DUPLICATES);
paths = _gtk_get_module_path ("media");
for (i = 0; paths[i]; i++)
{
GTK_NOTE (MODULES,
g_print ("Scanning io modules in %s\n", paths[i]));
g_io_modules_scan_all_in_directory_with_scope (paths[i], scope);
}
g_strfreev (paths);
g_io_module_scope_free (scope);
if (GTK_DEBUG_CHECK (MODULES))
{
GList *list, *l;
list = g_io_extension_point_get_extensions (ep);
for (l = list; l; l = l->next)
{
GIOExtension *ext = l->data;
g_print ("extension: %s: type %s\n",
g_io_extension_get_name (ext),
g_type_name (g_io_extension_get_type (ext)));
}
}
}

86
gtk/gtkmediafile.h Normal file
View File

@@ -0,0 +1,86 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_MEDIA_FILE_H__
#define __GTK_MEDIA_FILE_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkmediastream.h>
G_BEGIN_DECLS
#define GTK_MEDIA_FILE_EXTENSION_POINT_NAME "gtk-media-file"
#define GTK_TYPE_MEDIA_FILE (gtk_media_file_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_DERIVABLE_TYPE (GtkMediaFile, gtk_media_file, GTK, MEDIA_FILE, GtkMediaStream)
struct _GtkMediaFileClass
{
GtkMediaStreamClass parent_class;
void (* open) (GtkMediaFile *self);
void (* close) (GtkMediaFile *self);
/* Padding for future expansion */
void (*_gtk_reserved1) (void);
void (*_gtk_reserved2) (void);
void (*_gtk_reserved3) (void);
void (*_gtk_reserved4) (void);
};
GDK_AVAILABLE_IN_ALL
GtkMediaStream * gtk_media_file_new (void);
GDK_AVAILABLE_IN_ALL
GtkMediaStream * gtk_media_file_new_for_filename (const char *filename);
GDK_AVAILABLE_IN_ALL
GtkMediaStream * gtk_media_file_new_for_resource (const char *resource_path);
GDK_AVAILABLE_IN_ALL
GtkMediaStream * gtk_media_file_new_for_file (GFile *file);
GDK_AVAILABLE_IN_ALL
GtkMediaStream * gtk_media_file_new_for_input_stream (GInputStream *stream);
GDK_AVAILABLE_IN_ALL
void gtk_media_file_clear (GtkMediaFile *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_file_set_filename (GtkMediaFile *self,
const char *filename);
GDK_AVAILABLE_IN_ALL
void gtk_media_file_set_resource (GtkMediaFile *self,
const char *resource_path);
GDK_AVAILABLE_IN_ALL
void gtk_media_file_set_file (GtkMediaFile *self,
GFile *file);
GDK_AVAILABLE_IN_ALL
GFile * gtk_media_file_get_file (GtkMediaFile *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_file_set_input_stream (GtkMediaFile *self,
GInputStream *stream);
GDK_AVAILABLE_IN_ALL
GInputStream * gtk_media_file_get_input_stream (GtkMediaFile *self);
G_END_DECLS
#endif /* __GTK_MEDIA_FILE_H__ */

29
gtk/gtkmediafileprivate.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_MEDIA_FILE_PRIVATE_H__
#define __GTK_MEDIA_FILE_PRIVATE_H__
#include "gtkmediafile.h"
void gtk_media_file_extension_init (void);
#endif /* __GTK_MEDIA_FILE_PRIVATE_H__ */

1260
gtk/gtkmediastream.c Normal file

File diff suppressed because it is too large Load Diff

155
gtk/gtkmediastream.h Normal file
View File

@@ -0,0 +1,155 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_MEDIA_STREAM_H__
#define __GTK_MEDIA_STREAM_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define GTK_TYPE_MEDIA_STREAM (gtk_media_stream_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_DERIVABLE_TYPE (GtkMediaStream, gtk_media_stream, GTK, MEDIA_STREAM, GObject)
struct _GtkMediaStreamClass
{
GObjectClass parent_class;
gboolean (* play) (GtkMediaStream *self);
void (* pause) (GtkMediaStream *self);
void (* seek) (GtkMediaStream *self,
gint64 timestamp);
void (* update_audio) (GtkMediaStream *self,
gboolean muted,
double volume);
void (* realize) (GtkMediaStream *self,
GdkWindow *window);
void (* unrealize) (GtkMediaStream *self,
GdkWindow *window);
/* Padding for future expansion */
void (*_gtk_reserved1) (void);
void (*_gtk_reserved2) (void);
void (*_gtk_reserved3) (void);
void (*_gtk_reserved4) (void);
void (*_gtk_reserved5) (void);
void (*_gtk_reserved6) (void);
void (*_gtk_reserved7) (void);
void (*_gtk_reserved8) (void);
};
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_is_prepared (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
const GError * gtk_media_stream_get_error (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_has_audio (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_has_video (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_play (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_pause (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_get_playing (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_set_playing (GtkMediaStream *self,
gboolean playing);
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_get_ended (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
gint64 gtk_media_stream_get_timestamp (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
gint64 gtk_media_stream_get_duration (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_is_seekable (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_is_seeking (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_seek (GtkMediaStream *self,
gint64 timestamp);
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_get_loop (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_set_loop (GtkMediaStream *self,
gboolean loop);
GDK_AVAILABLE_IN_ALL
gboolean gtk_media_stream_get_muted (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_set_muted (GtkMediaStream *self,
gboolean muted);
GDK_AVAILABLE_IN_ALL
double gtk_media_stream_get_volume (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_set_volume (GtkMediaStream *self,
double volume);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_realize (GtkMediaStream *self,
GdkWindow *window);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_unrealize (GtkMediaStream *self,
GdkWindow *window);
/* for implementations only */
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_prepared (GtkMediaStream *self,
gboolean has_audio,
gboolean has_video,
gboolean seekable,
gint64 duration);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_unprepared (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_update (GtkMediaStream *self,
gint64 timestamp);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_ended (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_seek_success (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_seek_failed (GtkMediaStream *self);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_gerror (GtkMediaStream *self,
GError *error);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_error (GtkMediaStream *self,
GQuark domain,
gint code,
const gchar *format,
...) G_GNUC_PRINTF (4, 5);
GDK_AVAILABLE_IN_ALL
void gtk_media_stream_error_valist (GtkMediaStream *self,
GQuark domain,
gint code,
const gchar *format,
va_list args) G_GNUC_PRINTF (4, 0);
G_END_DECLS
#endif /* __GTK_MEDIA_STREAM_H__ */

63
gtk/gtknomediafile.c Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtknomediafileprivate.h"
#include "gtkintl.h"
struct _GtkNoMediaFile
{
GtkMediaFile parent_instance;
};
struct _GtkNoMediaFileClass
{
GtkMediaFileClass parent_class;
};
G_DEFINE_TYPE_WITH_CODE (GtkNoMediaFile, gtk_no_media_file, GTK_TYPE_MEDIA_FILE,
g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
g_define_type_id,
"none",
G_MININT);)
static void
gtk_no_media_file_open (GtkMediaFile *file)
{
gtk_media_stream_error (GTK_MEDIA_STREAM (file),
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("GTK has been compiled without media support."));
}
static void
gtk_no_media_file_class_init (GtkNoMediaFileClass *klass)
{
GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (klass);
file_class->open = gtk_no_media_file_open;
}
static void
gtk_no_media_file_init (GtkNoMediaFile *video)
{
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_NO_MEDIA_FILE_H__
#define __GTK_NO_MEDIA_FILE_H__
#include <gtk/gtkmediafile.h>
G_BEGIN_DECLS
#define GTK_TYPE_NO_MEDIA_FILE (gtk_no_media_file_get_type ())
G_DECLARE_FINAL_TYPE (GtkNoMediaFile, gtk_no_media_file, GTK, NO_MEDIA_FILE, GtkMediaFile)
G_END_DECLS
#endif /* __GTK_NO_MEDIA_FILE_H__ */

View File

@@ -155,6 +155,42 @@ gtk_style_cascade_get_color (GtkStyleProvider *provider,
return NULL;
}
static GdkPaintable *
gtk_style_cascade_get_paint (GtkStyleProvider *provider,
const char *name)
{
GtkStyleCascade *cascade = GTK_STYLE_CASCADE (provider);
GtkStyleCascadeIter iter;
GdkPaintable *paintable;
GtkStyleProvider *item;
for (item = gtk_style_cascade_iter_init (cascade, &iter);
item;
item = gtk_style_cascade_iter_next (cascade, &iter))
{
if (GTK_IS_STYLE_PROVIDER (item))
{
paintable = gtk_style_provider_get_paint (GTK_STYLE_PROVIDER (item), name);
if (paintable)
{
gtk_style_cascade_iter_clear (&iter);
return paintable;
}
}
else
{
/* If somebody hits this code path, shout at them */
}
}
gtk_style_cascade_iter_clear (&iter);
if (cascade->paints == NULL)
return NULL;
return g_hash_table_lookup (cascade->paints, name);
}
static int
gtk_style_cascade_get_scale (GtkStyleProvider *provider)
{
@@ -227,6 +263,7 @@ static void
gtk_style_cascade_provider_iface_init (GtkStyleProviderInterface *iface)
{
iface->get_color = gtk_style_cascade_get_color;
iface->get_paint = gtk_style_cascade_get_paint;
iface->get_settings = gtk_style_cascade_get_settings;
iface->get_scale = gtk_style_cascade_get_scale;
iface->get_keyframes = gtk_style_cascade_get_keyframes;
@@ -244,6 +281,7 @@ gtk_style_cascade_dispose (GObject *object)
_gtk_style_cascade_set_parent (cascade, NULL);
g_array_unref (cascade->providers);
g_clear_pointer (&cascade->paints, g_hash_table_unref);
G_OBJECT_CLASS (_gtk_style_cascade_parent_class)->dispose (object);
}
@@ -386,3 +424,39 @@ _gtk_style_cascade_get_scale (GtkStyleCascade *cascade)
return cascade->scale;
}
gboolean
gtk_style_cascade_add_paint (GtkStyleCascade *cascade,
const char *name,
GdkPaintable *paintable)
{
if (cascade->paints == NULL)
{
cascade->paints = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
}
else
{
if (g_hash_table_contains (cascade->paints, name))
return FALSE;
}
g_hash_table_insert (cascade->paints, g_strdup (name), g_object_ref (paintable));
gtk_style_provider_changed (GTK_STYLE_PROVIDER (cascade));
return TRUE;
}
gboolean
gtk_style_cascade_remove_paint (GtkStyleCascade *cascade,
const char *name)
{
gboolean result;
if (cascade->paints == NULL)
return FALSE;
result = g_hash_table_remove (cascade->paints, name);
if (result)
gtk_style_provider_changed (GTK_STYLE_PROVIDER (cascade));
return result;
}

View File

@@ -38,6 +38,7 @@ struct _GtkStyleCascade
GObject object;
GtkStyleCascade *parent;
GHashTable *paints;
GArray *providers;
int scale;
};
@@ -62,6 +63,11 @@ void _gtk_style_cascade_add_provider (GtkStyleCascade
guint priority);
void _gtk_style_cascade_remove_provider (GtkStyleCascade *cascade,
GtkStyleProvider *provider);
gboolean gtk_style_cascade_add_paint (GtkStyleCascade *cascade,
const char *name,
GdkPaintable *paintable);
gboolean gtk_style_cascade_remove_paint (GtkStyleCascade *cascade,
const char *name);
G_END_DECLS

View File

@@ -638,6 +638,34 @@ gtk_style_context_remove_provider_for_display (GdkDisplay *display,
_gtk_style_cascade_remove_provider (cascade, provider);
}
gboolean
gtk_style_context_add_paint_for_display (GdkDisplay *display,
const char *name,
GdkPaintable *paintable)
{
GtkStyleCascade *cascade;
g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
g_return_val_if_fail (GDK_IS_PAINTABLE (paintable), FALSE);
cascade = _gtk_settings_get_style_cascade (gtk_settings_get_for_display (display), 1);
return gtk_style_cascade_add_paint (cascade, name, paintable);
}
gboolean
gtk_style_context_remove_paint_for_display (GdkDisplay *display,
const char *name)
{
GtkStyleCascade *cascade;
g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
cascade = _gtk_settings_get_style_cascade (gtk_settings_get_for_display (display), 1);
return gtk_style_cascade_remove_paint (cascade, name);
}
/**
* gtk_style_context_get_section:
* @context: a #GtkStyleContext

View File

@@ -945,6 +945,13 @@ void gtk_style_context_add_provider_for_display (GdkDisplay *display,
GDK_AVAILABLE_IN_ALL
void gtk_style_context_remove_provider_for_display (GdkDisplay *display,
GtkStyleProvider *provider);
GDK_AVAILABLE_IN_ALL
gboolean gtk_style_context_add_paint_for_display (GdkDisplay *display,
const char *name,
GdkPaintable *paintable);
GDK_AVAILABLE_IN_ALL
gboolean gtk_style_context_remove_paint_for_display(GdkDisplay *display,
const char *name);
GDK_AVAILABLE_IN_ALL
void gtk_style_context_add_provider (GtkStyleContext *context,

View File

@@ -153,6 +153,22 @@ gtk_style_provider_get_scale (GtkStyleProvider *provider)
return iface->get_scale (provider);
}
GdkPaintable *
gtk_style_provider_get_paint (GtkStyleProvider *provider,
const char *name)
{
GtkStyleProviderInterface *iface;
gtk_internal_return_val_if_fail (GTK_IS_STYLE_PROVIDER (provider), NULL);
iface = GTK_STYLE_PROVIDER_GET_INTERFACE (provider);
if (!iface->get_paint)
return NULL;
return iface->get_paint (provider, name);
}
void
gtk_style_provider_emit_error (GtkStyleProvider *provider,
GtkCssSection *section,

View File

@@ -35,7 +35,9 @@ struct _GtkStyleProviderInterface
{
GTypeInterface g_iface;
GtkCssValue * (* get_color) (GtkStyleProvider *provider,
GtkCssValue * (* get_color) (GtkStyleProvider *provider,
const char *name);
GdkPaintable * (* get_paint) (GtkStyleProvider *provider,
const char *name);
GtkSettings * (* get_settings) (GtkStyleProvider *provider);
GtkCssKeyframes * (* get_keyframes) (GtkStyleProvider *provider,
@@ -52,8 +54,10 @@ struct _GtkStyleProviderInterface
void (* changed) (GtkStyleProvider *provider);
};
GtkSettings * gtk_style_provider_get_settings (GtkStyleProvider *provider);
GtkCssValue * gtk_style_provider_get_color (GtkStyleProvider *provider,
GtkSettings * gtk_style_provider_get_settings (GtkStyleProvider *provider);
GtkCssValue * gtk_style_provider_get_color (GtkStyleProvider *provider,
const char *name);
GdkPaintable * gtk_style_provider_get_paint (GtkStyleProvider *provider,
const char *name);
GtkCssKeyframes * gtk_style_provider_get_keyframes (GtkStyleProvider *provider,
const char *name);

651
gtk/gtkvideo.c Normal file
View File

@@ -0,0 +1,651 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkvideo.h"
#include "gtkeventcontrollermotion.h"
#include "gtkimage.h"
#include "gtkintl.h"
#include "gtkmediacontrols.h"
#include "gtkmediafile.h"
#include "gtkrevealer.h"
/**
* SECTION:gtkvideo
* @title: GtkVideo
* @short_description: A widget for displaying video
*
* GtkVideo is a widget to show a #GtkMediaStream.
*/
struct _GtkVideo
{
GtkWidget parent_instance;
GFile *file;
GtkMediaStream *media_stream;
GtkEventController *motion_controller;
GtkWidget *box;
GtkWidget *video_image;
GtkWidget *overlay_icon;
GtkWidget *controls_revealer;
GtkWidget *controls;
guint controls_hide_source;
};
enum
{
PROP_0,
PROP_FILE,
PROP_MEDIA_STREAM,
N_PROPS
};
G_DEFINE_TYPE (GtkVideo, gtk_video, GTK_TYPE_WIDGET)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_video_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkVideo *self = GTK_VIDEO (widget);
gtk_widget_measure (self->box,
orientation,
for_size,
minimum, natural,
minimum_baseline, natural_baseline);
}
static void
gtk_video_size_allocate (GtkWidget *widget,
const GtkAllocation *allocation,
int baseline,
GtkAllocation *out_clip)
{
GtkVideo *self = GTK_VIDEO (widget);
gtk_widget_size_allocate (self->box, allocation, baseline, out_clip);
}
static void
gtk_video_realize (GtkWidget *widget)
{
GtkVideo *self = GTK_VIDEO (widget);
GTK_WIDGET_CLASS (gtk_video_parent_class)->realize (widget);
if (self->media_stream)
gtk_media_stream_realize (self->media_stream, gtk_widget_get_window (GTK_WIDGET (self)));
if (self->file)
gtk_media_file_set_file (GTK_MEDIA_FILE (self->media_stream), self->file);
}
static void
gtk_video_unrealize (GtkWidget *widget)
{
GtkVideo *self = GTK_VIDEO (widget);
if (self->media_stream)
gtk_media_stream_unrealize (self->media_stream, gtk_widget_get_window (GTK_WIDGET (self)));
GTK_WIDGET_CLASS (gtk_video_parent_class)->unrealize (widget);
}
static void
gtk_video_unmap (GtkWidget *widget)
{
GtkVideo *self = GTK_VIDEO (widget);
if (self->controls_hide_source)
{
g_source_remove (self->controls_hide_source);
self->controls_hide_source = 0;
gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE);
}
/* XXX: pause video here? */
GTK_WIDGET_CLASS (gtk_video_parent_class)->unmap (widget);
}
static void
gtk_video_dispose (GObject *object)
{
GtkVideo *self = GTK_VIDEO (object);
gtk_video_set_media_stream (self, NULL);
g_clear_object (&self->motion_controller);
g_clear_pointer (&self->box, gtk_widget_unparent);
G_OBJECT_CLASS (gtk_video_parent_class)->dispose (object);
}
static void
gtk_video_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkVideo *self = GTK_VIDEO (object);
switch (property_id)
{
case PROP_FILE:
g_value_set_object (value, self->file);
break;
case PROP_MEDIA_STREAM:
g_value_set_object (value, self->media_stream);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_video_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkVideo *self = GTK_VIDEO (object);
switch (property_id)
{
case PROP_FILE:
gtk_video_set_file (self, g_value_get_object (value));
break;
case PROP_MEDIA_STREAM:
gtk_video_set_media_stream (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_video_class_init (GtkVideoClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
widget_class->measure = gtk_video_measure;
widget_class->size_allocate = gtk_video_size_allocate;
widget_class->realize = gtk_video_realize;
widget_class->unrealize = gtk_video_unrealize;
widget_class->unmap = gtk_video_unmap;
gobject_class->dispose = gtk_video_dispose;
gobject_class->get_property = gtk_video_get_property;
gobject_class->set_property = gtk_video_set_property;
/**
* GtkVideo:file:
*
* The file played by this video if the video is playing a file.
*/
properties[PROP_FILE] =
g_param_spec_object ("file",
P_("File"),
P_("The video file played back"),
G_TYPE_FILE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkVideo:media-stream:
*
* The media-stream played
*/
properties[PROP_MEDIA_STREAM] =
g_param_spec_object ("media-stream",
P_("Media Stream"),
P_("The media stream played"),
GTK_TYPE_MEDIA_STREAM,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkvideo.ui");
gtk_widget_class_bind_template_child (widget_class, GtkVideo, box);
gtk_widget_class_bind_template_child (widget_class, GtkVideo, video_image);
gtk_widget_class_bind_template_child (widget_class, GtkVideo, overlay_icon);
gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls);
gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls_revealer);
gtk_widget_class_set_css_name (widget_class, I_("video"));
}
static gboolean
gtk_video_hide_controls (gpointer data)
{
GtkVideo *self = data;
gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE);
self->controls_hide_source = 0;
return G_SOURCE_REMOVE;
}
static void
gtk_video_motion (GtkEventControllerMotion *motion,
double x,
double y,
GtkVideo *self)
{
gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), TRUE);
if (self->controls_hide_source)
g_source_remove (self->controls_hide_source);
self->controls_hide_source = g_timeout_add (5 * 1000,
gtk_video_hide_controls,
self);
}
static void
gtk_video_init (GtkVideo *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
self->motion_controller = gtk_event_controller_motion_new (GTK_WIDGET (self));
g_signal_connect (self->motion_controller, "motion", G_CALLBACK (gtk_video_motion), self);
}
/**
* gtk_video_new:
*
* Creates a new empty #GtkVideo.
*
* Returns: a new #GtkVideo
**/
GtkWidget *
gtk_video_new (void)
{
return g_object_new (GTK_TYPE_VIDEO, NULL);
}
/**
* gtk_video_new_for_media_stream:
* @stream: (allow-none): a #GtkMediaStream
*
* Creates a #GtkVideo to play back the given @stream.
*
* Returns: a new #GtkVideo
**/
GtkWidget *
gtk_video_new_for_media_stream (GtkMediaStream *stream)
{
g_return_val_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream), NULL);
return g_object_new (GTK_TYPE_VIDEO,
"media-stream", stream,
NULL);
}
/**
* gtk_video_new_for_file:
* @file: (allow-none): a #GFile
*
* Creates a #GtkVideo to play back the given @file.
*
* Returns: a new #GtkVideo
**/
GtkWidget *
gtk_video_new_for_file (GFile *file)
{
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
return g_object_new (GTK_TYPE_VIDEO,
"file", file,
NULL);
}
/**
* gtk_video_new_for_filename:
* @filename: (allow-none): filename to play back
*
* Creates a #GtkVideo to play back the given @filename.
*
* This is a utility function that calls gtk_video_new_for_file(),
*
* Returns: a new #GtkVideo
**/
GtkWidget *
gtk_video_new_for_filename (const char *filename)
{
GtkWidget *result;
GFile *file;
if (filename)
file = g_file_new_for_path (filename);
else
file = NULL;
result = gtk_video_new_for_file (file);
if (file)
g_object_unref (file);
return result;
}
/**
* gtk_video_new_for_resource:
* @filename: (allow-none): resource path to play back
*
* Creates a #GtkVideo to play back the resource at the
* given @resource_path.
*
* This is a utility function that calls gtk_video_new_for_file(),
*
* Returns: a new #GtkVideo
**/
GtkWidget *
gtk_video_new_for_resource (const char *resource_path)
{
GtkWidget *result;
GFile *file;
if (resource_path)
{
char *uri, *escaped;
escaped = g_uri_escape_string (resource_path,
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
uri = g_strconcat ("resource://", escaped, NULL);
g_free (escaped);
file = g_file_new_for_uri (uri);
g_free (uri);
}
else
{
file = NULL;
}
result = gtk_video_new_for_file (file);
if (file)
g_object_unref (file);
return result;
}
/**
* gtk_video_get_media_stream:
* @self: a #GtkVideo
*
* Gets the media stream managed by @self or %NULL if none.
*
* Returns: (nullable): The media stream managed by @self
**/
GtkMediaStream *
gtk_video_get_media_stream (GtkVideo *self)
{
g_return_val_if_fail (GTK_IS_VIDEO (self), NULL);
return self->media_stream;
}
static void
gtk_video_update_overlay_icon (GtkVideo *self)
{
const char *icon_name;
const GError *error = NULL;
if (self->media_stream == NULL)
icon_name = "media-eject-symbolic";
else if ((error = gtk_media_stream_get_error (self->media_stream)))
icon_name = "dialog-error-symbolic";
else if (gtk_media_stream_get_ended (self->media_stream))
icon_name = "media-playlist-repeat-symbolic";
else
icon_name = "media-playback-start-symbolic";
gtk_image_set_from_icon_name (GTK_IMAGE (self->overlay_icon), icon_name);
if (error)
gtk_widget_set_tooltip_text (self->overlay_icon, error->message);
else
gtk_widget_set_tooltip_text (self->overlay_icon, NULL);
}
static void
gtk_video_update_ended (GtkVideo *self)
{
gtk_video_update_overlay_icon (self);
}
static void
gtk_video_update_error (GtkVideo *self)
{
gtk_video_update_overlay_icon (self);
}
static void
gtk_video_update_playing (GtkVideo *self)
{
gboolean playing;
if (self->media_stream != NULL)
playing = gtk_media_stream_get_playing (self->media_stream);
else
playing = FALSE;
gtk_widget_set_visible (self->overlay_icon, !playing);
}
static void
gtk_video_update_all (GtkVideo *self)
{
gtk_video_update_ended (self);
gtk_video_update_error (self);
gtk_video_update_playing (self);
}
static void
gtk_video_notify_cb (GtkMediaStream *stream,
GParamSpec *pspec,
GtkVideo *self)
{
if (g_str_equal (pspec->name, "ended"))
gtk_video_update_ended (self);
if (g_str_equal (pspec->name, "error"))
gtk_video_update_error (self);
if (g_str_equal (pspec->name, "playing"))
gtk_video_update_playing (self);
}
/**
* gtk_video_set_media_stream:
* @self: a #GtkVideo
* @stream: (allow-none): The media stream to play or %NULL to unset
*
* Sets the media stream to be played back. @self will take full control
* of managing the media stream. If you want to manage a media stream
* yourself, consider using a #GtkImage for display.
*
* If you want to display a file, consider using gtk_video_set_file()
* instead.
**/
void
gtk_video_set_media_stream (GtkVideo *self,
GtkMediaStream *stream)
{
g_return_if_fail (GTK_IS_VIDEO (self));
g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream));
if (self->media_stream == stream)
return;
if (self->media_stream)
{
g_signal_handlers_disconnect_by_func (self->media_stream,
gtk_video_notify_cb,
self);
if (gtk_widget_get_realized (GTK_WIDGET (self)))
gtk_media_stream_unrealize (self->media_stream, gtk_widget_get_window (GTK_WIDGET (self)));
g_object_unref (self->media_stream);
self->media_stream = NULL;
}
if (stream)
{
self->media_stream = g_object_ref (stream);
if (gtk_widget_get_realized (GTK_WIDGET (self)))
gtk_media_stream_realize (stream, gtk_widget_get_window (GTK_WIDGET (self)));
g_signal_connect (self->media_stream,
"notify",
G_CALLBACK (gtk_video_notify_cb),
self);
}
gtk_media_controls_set_media_stream (GTK_MEDIA_CONTROLS (self->controls), stream);
gtk_image_set_from_paintable (GTK_IMAGE (self->video_image), GDK_PAINTABLE (stream));
gtk_video_update_all (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MEDIA_STREAM]);
}
/**
* gtk_video_set_file:
* @self: a #GtkVideo
* @file: (allow-none): the file to play
*
* Makes @self play the given @file.
**/
void
gtk_video_set_file (GtkVideo *self,
GFile *file)
{
g_return_if_fail (GTK_IS_VIDEO (self));
g_return_if_fail (file == NULL || G_IS_FILE (file));
if (!g_set_object (&self->file, file))
return;
g_object_freeze_notify (G_OBJECT (self));
if (file)
{
GtkMediaStream *stream;
stream = gtk_media_file_new ();
gtk_video_set_media_stream (self, stream);
if (gtk_widget_get_realized (GTK_WIDGET (self)))
gtk_media_file_set_file (GTK_MEDIA_FILE (stream), file);
g_object_unref (stream);
}
else
{
gtk_video_set_media_stream (self, NULL);
}
g_object_thaw_notify (G_OBJECT (self));
}
/**
* gtk_video_set_resource:
* @self: a #GtkVideo
* @filename: (allow-none): the filename to play
*
* Makes @self play the given @filename.
*
* This is a utility function that calls gtk_video_set_file(),
**/
void
gtk_video_set_filename (GtkVideo *self,
const char *filename)
{
GFile *file;
g_return_if_fail (GTK_IS_VIDEO (self));
if (filename)
file = g_file_new_for_path (filename);
else
file = NULL;
gtk_video_set_file (self, file);
if (file)
g_object_unref (file);
}
/**
* gtk_video_set_resource:
* @self: a #GtkVideo
* @resource_path: (allow-none): the resource to set
*
* Makes @self play the resource at the given @resource_path.
*
* This is a utility function that calls gtk_video_set_file(),
**/
void
gtk_video_set_resource (GtkVideo *self,
const char *resource_path)
{
GFile *file;
g_return_if_fail (GTK_IS_VIDEO (self));
if (resource_path)
{
char *uri, *escaped;
escaped = g_uri_escape_string (resource_path,
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
uri = g_strconcat ("resource://", escaped, NULL);
g_free (escaped);
file = g_file_new_for_uri (uri);
g_free (uri);
}
else
{
file = NULL;
}
gtk_video_set_file (self, file);
if (file)
g_object_unref (file);
}

64
gtk/gtkvideo.h Normal file
View File

@@ -0,0 +1,64 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_VIDEO_H__
#define __GTK_VIDEO_H__
#include <gtk/gtkmediastream.h>
#include <gtk/gtkwidget.h>
G_BEGIN_DECLS
#define GTK_TYPE_VIDEO (gtk_video_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkVideo, gtk_video, GTK, VIDEO, GtkWidget)
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_video_new (void);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_video_new_for_media_stream (GtkMediaStream *stream);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_video_new_for_file (GFile *file);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_video_new_for_filename (const char *filename);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_video_new_for_resource (const char *resource_path);
GDK_AVAILABLE_IN_ALL
GtkMediaStream *gtk_video_get_media_stream (GtkVideo *self);
GDK_AVAILABLE_IN_ALL
void gtk_video_set_media_stream (GtkVideo *self,
GtkMediaStream *stream);
GDK_AVAILABLE_IN_ALL
GFile * gtk_video_get_file (GtkVideo *self);
GDK_AVAILABLE_IN_ALL
void gtk_video_set_file (GtkVideo *self,
GFile *file);
GDK_AVAILABLE_IN_ALL
void gtk_video_set_filename (GtkVideo *self,
const char *filename);
GDK_AVAILABLE_IN_ALL
void gtk_video_set_resource (GtkVideo *self,
const char *resource_path);
G_END_DECLS
#endif /* __GTK_VIDEO_H__ */

View File

@@ -73,6 +73,7 @@
#include "inspector/init.h"
#include "inspector/window.h"
#include "gdk/gdktextureprivate.h"
#include "gdk/gdk-private.h"
#include <cairo-gobject.h>
@@ -4600,10 +4601,7 @@ icon_from_list (GList *list,
cairo_destroy (cr);
cairo_surface_destroy (source);
texture = gdk_texture_new_for_data (cairo_image_surface_get_data (target),
cairo_image_surface_get_width (target),
cairo_image_surface_get_height (target),
cairo_image_surface_get_stride (target));
texture = gdk_texture_new_for_surface (target);
cairo_surface_destroy (target);
return texture;

View File

@@ -48,6 +48,7 @@ gtk_private_sources = files([
'gtkcssimageicontheme.c',
'gtkcssimageinvalid.c',
'gtkcssimagelinear.c',
'gtkcssimagepaint.c',
'gtkcssimagepaintable.c',
'gtkcssimageradial.c',
'gtkcssimagerecolor.c',
@@ -251,6 +252,9 @@ gtk_public_sources = files([
'gtkliststore.c',
'gtklockbutton.c',
'gtkmain.c',
'gtkmediacontrols.c',
'gtkmediafile.c',
'gtkmediastream.c',
'gtkmenu.c',
'gtkmenubar.c',
'gtkmenubutton.c',
@@ -263,6 +267,7 @@ gtk_public_sources = files([
'gtkmodules.c',
'gtkmountoperation.c',
'gtknativedialog.c',
'gtknomediafile.c',
'gtknotebook.c',
'gtkorientable.c',
'gtkoverlay.c',
@@ -354,6 +359,7 @@ gtk_public_sources = files([
'gtktreeview.c',
'gtktreeviewcolumn.c',
'gtkutils.c',
'gtkvideo.c',
'gtkviewport.c',
'gtkvolumebutton.c',
'gtkwidget.c',
@@ -480,6 +486,9 @@ gtk_public_headers = files([
'gtkliststore.h',
'gtklockbutton.h',
'gtkmain.h',
'gtkmediacontrols.h',
'gtkmediafile.h',
'gtkmediastream.h',
'gtkmenu.h',
'gtkmenubar.h',
'gtkmenubutton.h',
@@ -566,6 +575,7 @@ gtk_public_headers = files([
'gtktreeview.h',
'gtktreeviewcolumn.h',
'gtktypes.h',
'gtkvideo.h',
'gtkviewport.h',
'gtkvolumebutton.h',
'gtkwidget.h',

View File

@@ -3998,6 +3998,19 @@ paned {
}
/**************
* GtkVideo *
**************/
video {
& image.osd {
min-width: 64px;
min-height: 64px;
border-radius: 32px;
}
background: black;
}
/**************
* GtkInfoBar *
**************/

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk40">
<!-- interface-requires gtk+ 3.6 -->
<object class="GtkAdjustment" id="time_adjustment">
<property name="upper">10</property>
<property name="step-increment">1</property>
<property name="page-increment">10</property>
<signal name="value-changed" handler="time_adjustment_changed" object="GtkMediaControls" swapped="no"/>
</object>
<object class="GtkAdjustment" id="volume_adjustment">
<property name="upper">1</property>
<property name="step-increment">0.1</property>
<property name="page-increment">1</property>
<property name="value">1</property>
<signal name="value-changed" handler="volume_adjustment_changed" object="GtkMediaControls" swapped="no"/>
</object>
<template class="GtkMediaControls" parent="GtkWidget">
<child>
<object class="GtkBox" id="box">
<property name="hexpand">0</property>
<property name="sensitive">0</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="play_button">
<property name="can-focus">1</property>
<property name="receives-default">1</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="icon-name">media-playback-start-symbolic</property>
<signal name="clicked" handler="play_button_clicked" object="GtkMediaControls" swapped="no"/>
</object>
</child>
<child>
<object class="GtkBox" id="time_box">
<child>
<object class="GtkLabel" id="time_label">
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkScale" id="seek_scale">
<property name="adjustment">time_adjustment</property>
<property name="can_focus">True</property>
<property name="draw_value">False</property>
<property name="restrict-to-fill-level">False</property>
<property name="hexpand">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="duration_label">
<property name="can_focus">False</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkVolumeButton" id="volume_button">
<property name="adjustment">volume_adjustment</property>
</object>
</child>
</object>
</child>
</template>
</interface>

47
gtk/ui/gtkvideo.ui Normal file
View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk40">
<!-- interface-requires gtk+ 3.6 -->
<template class="GtkVideo" parent="GtkWidget">
<child>
<object class="GtkOverlay" id="box">
<child>
<object class="GtkImage" id="video_image">
<property name="can-shrink">1</property>
</object>
</child>
<child type="overlay">
<object class="GtkImage" id="overlay_icon">
<style>
<class name="osd"/>
<class name="circular"/>
</style>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="icon-name">media-playback-start-symbolic</property>
<property name="icon-size">large</property>
</object>
<packing>
<property name="measure">1</property>
</packing>
</child>
<child type="overlay">
<object class="GtkRevealer" id="controls_revealer">
<property name="reveal-child">0</property>
<property name="valign">end</property>
<child>
<object class="GtkMediaControls" id="controls">
<style>
<class name="osd"/>
<class name="bottom"/>
</style>
</object>
</child>
</object>
<packing>
<property name="measure">1</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@@ -589,6 +589,42 @@ if cloudproviders_enabled
endif
endif
media_backends = []
# We require manually disabling support for media backends.
# People should only do that when they know exactly what they're doing
# (either not using media or providing their own implementation.)
if get_option('nomedia')
media_backends += ['none']
endif
ffmpeg_enabled = get_option('ffmpeg')
if ffmpeg_enabled
libavfilter_dep = dependency('libavfilter', version: '>= 6.47.100', required: true)
libavformat_dep = dependency('libavformat', version: '>= 57.41.100', required: true)
libavcodec_dep = dependency('libavcodec', version: '>= 57.48.101', required: true)
libavutil_dep = dependency('libavutil', version: '>= 55.28.100', required: true)
libswscale_dep = dependency('libswscale', version: '>= 4.6.100', required: true)
ffmpeg_deps = [libavfilter_dep, libavformat_dep, libavcodec_dep, libavutil_dep, libswscale_dep]
cdata.set('HAVE_FFMPEG', 1)
media_backends += ['ffmpeg']
endif
gstreamer_enabled = get_option('gstreamer')
if gstreamer_enabled
gstplayer_dep = dependency('gstreamer-player-1.0', version: '>= 1.12.3', required: true)
gstgl_dep = dependency('gstreamer-gl-1.0', version: '>= 1.12.3', required: true)
gstreamer_deps = [gstplayer_dep, gstgl_dep]
cdata.set('HAVE_GSTREAMER', 1)
media_backends += ['gstreamer']
endif
if media_backends.length() == 0
error('No media backends enabled. If you are sure about that, enable the "nomedia" option.')
endif
subdir('gdk')
subdir('gsk')
subdir('gtk')
@@ -705,6 +741,7 @@ summary = [
' Enabled backends: @0@'.format(pkg_targets.strip()),
' Vulkan support: @0@'.format(have_vulkan),
' Print backends: @0@'.format(' '.join(print_backends)),
' Media backends: @0@'.format(' '.join(media_backends)),
' Tests: @0@'.format(get_option('build-tests')),
' Documentation: @0@'.format(get_option('documentation')),
' Demos: @0@'.format(get_option('demos')),

View File

@@ -10,6 +10,14 @@ option('win32-backend', type: 'boolean', value: true,
option('quartz-backend', type: 'boolean', value: true,
description : 'Enable the macOS gdk backend (only when building on macOS)')
# Media backends
option('nomedia', type: 'boolean', value: false,
description : 'Set to confirm that no media backend should be enabled')
option('ffmpeg', type: 'boolean', value: false,
description : 'Enable ffmpeg media backend')
option('gstreamer', type: 'boolean', value: true,
description : 'Enable GStreamer media backend')
# Optional dependencies
option('vulkan', type: 'combo', choices : ['yes', 'no', 'auto'], value : 'auto',
description : 'Enable support for the Vulkan graphics API')

View File

@@ -0,0 +1,756 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkffmediafileprivate.h"
#include "gtkintl.h"
#include "gdk/gdkmemorytextureprivate.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libswscale/swscale.h>
typedef struct _GtkVideoFrameFFMpeg GtkVideoFrameFFMpeg;
struct _GtkVideoFrameFFMpeg
{
GdkTexture *texture;
gint64 timestamp;
};
struct _GtkFfMediaFile
{
GtkMediaFile parent_instance;
GFile *file;
GInputStream *input_stream;
AVFormatContext *format_ctx;
AVCodecContext *codec_ctx;
int stream_id;
struct SwsContext *sws_ctx;
enum AVPixelFormat sws_pix_fmt;
GdkMemoryFormat memory_format;
GtkVideoFrameFFMpeg current_frame;
GtkVideoFrameFFMpeg next_frame;
gint64 start_time; /* monotonic time when we displayed the last frame */
guint next_frame_cb; /* Source ID of next frame callback */
};
struct _GtkFfMediaFileClass
{
GtkMediaFileClass parent_class;
};
static void
gtk_video_frame_ffmpeg_init (GtkVideoFrameFFMpeg *frame,
GdkTexture *texture,
gint64 timestamp)
{
frame->texture = texture;
frame->timestamp = timestamp;
}
static void
gtk_video_frame_ffmpeg_clear (GtkVideoFrameFFMpeg *frame)
{
g_clear_object (&frame->texture);
frame->timestamp = 0;
}
static gboolean
gtk_video_frame_ffmpeg_is_empty (GtkVideoFrameFFMpeg *frame)
{
return frame->texture == NULL;
}
static void
gtk_video_frame_ffmpeg_move (GtkVideoFrameFFMpeg *dest,
GtkVideoFrameFFMpeg *src)
{
*dest = *src;
src->texture = NULL;
src->timestamp = 0;
}
static void
gtk_ff_media_file_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
if (!gtk_video_frame_ffmpeg_is_empty (&video->current_frame))
{
gdk_paintable_snapshot (GDK_PAINTABLE (video->current_frame.texture), snapshot, width, height);
}
}
static GdkPaintable *
gtk_ff_media_file_paintable_get_current_image (GdkPaintable *paintable)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
return GDK_PAINTABLE (g_object_ref (video->current_frame.texture));
}
static int
gtk_ff_media_file_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
if (video->codec_ctx)
return video->codec_ctx->width;
return 0;
}
static int
gtk_ff_media_file_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
if (video->codec_ctx)
return video->codec_ctx->height;
return 0;
}
static double gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
if (video->codec_ctx)
return (double) video->codec_ctx->width / video->codec_ctx->height;
return 0.0;
};
static void
gtk_ff_media_file_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = gtk_ff_media_file_paintable_snapshot;
iface->get_current_image = gtk_ff_media_file_paintable_get_current_image;
iface->get_intrinsic_width = gtk_ff_media_file_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_ff_media_file_paintable_get_intrinsic_height;
iface->get_intrinsic_aspect_ratio = gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio;
}
G_DEFINE_TYPE_EXTENDED (GtkFfMediaFile, gtk_ff_media_file, GTK_TYPE_MEDIA_FILE, 0,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_ff_media_file_paintable_init))
void
g_io_module_load (GIOModule *module)
{
g_type_module_use (G_TYPE_MODULE (module));
av_register_all ();
g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
GTK_TYPE_FF_MEDIA_FILE,
"ffmpeg",
0);
}
void
g_io_module_unload (GIOModule *module)
{
g_assert_not_reached ();
}
char **
g_io_module_query (void)
{
char *eps[] = {
GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
NULL
};
return g_strdupv (eps);
}
static void
gtk_ff_media_file_set_ffmpeg_error (GtkFfMediaFile *video,
int av_errnum)
{
char s[AV_ERROR_MAX_STRING_SIZE];
if (gtk_media_stream_get_error (GTK_MEDIA_STREAM (video)))
return;
if (av_strerror (av_errnum, s, sizeof (s) != 0))
snprintf (s, sizeof (s), _("Unspecified error decoding video"));
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
G_IO_ERROR,
G_IO_ERROR_FAILED,
"%s",
s);
}
static int
gtk_ff_media_file_read_packet_cb (void *data,
uint8_t *buf,
int buf_size)
{
GtkFfMediaFile *video = data;
GError *error = NULL;
gssize n_read;
n_read = g_input_stream_read (video->input_stream,
buf,
buf_size,
NULL,
&error);
if (n_read < 0)
{
gtk_media_stream_gerror (GTK_MEDIA_STREAM (video), error);
}
else if (n_read == 0)
{
n_read = AVERROR_EOF;
}
return n_read;
}
static GdkMemoryFormat
memory_format_from_pix_fmt (enum AVPixelFormat pix_fmt)
{
switch ((int) pix_fmt)
{
case AV_PIX_FMT_RGBA:
return GDK_MEMORY_R8G8B8A8;
case AV_PIX_FMT_RGB24:
return GDK_MEMORY_R8G8B8;
default:
g_assert_not_reached ();
return GDK_MEMORY_R8G8B8A8;
}
}
static gboolean
gtk_ff_media_file_decode_frame (GtkFfMediaFile *video,
GtkVideoFrameFFMpeg *result)
{
GdkTexture *texture;
AVPacket packet;
AVFrame *frame;
int errnum;
GBytes *bytes;
guchar *data;
frame = av_frame_alloc ();
for (errnum = av_read_frame (video->format_ctx, &packet);
errnum >= 0;
errnum = av_read_frame (video->format_ctx, &packet))
{
if (packet.stream_index == video->stream_id)
{
errnum = avcodec_send_packet (video->codec_ctx, &packet);
if (errnum < 0)
G_BREAKPOINT();
if (errnum >= 0)
{
errnum = avcodec_receive_frame (video->codec_ctx, frame);
if (errnum < 0)
G_BREAKPOINT();
if (errnum >= 0)
{
av_packet_unref (&packet);
break;
}
}
}
av_packet_unref (&packet);
}
if (errnum < 0)
{
if (errnum != AVERROR_EOF)
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
av_frame_free (&frame);
return FALSE;
}
data = g_try_malloc0 (video->codec_ctx->width * video->codec_ctx->height * 4);
if (data == NULL)
{
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("Not enough memory"));
av_frame_free (&frame);
return FALSE;
}
if (video->sws_ctx == NULL ||
video->sws_pix_fmt != frame->format)
{
const AVPixFmtDescriptor *desc;
enum AVPixelFormat gdk_pix_fmt;
g_clear_pointer (&video->sws_ctx, sws_freeContext);
video->sws_pix_fmt = frame->format;
desc = av_pix_fmt_desc_get (video->sws_pix_fmt);
/* Use gdk-pixbuf formats because ffmpeg can't premultiply */
if (desc != NULL && (desc->flags & AV_PIX_FMT_FLAG_ALPHA))
gdk_pix_fmt = AV_PIX_FMT_RGBA;
else
gdk_pix_fmt = AV_PIX_FMT_RGB24;
video->sws_ctx = sws_getContext (video->codec_ctx->width,
video->codec_ctx->height,
frame->format,
video->codec_ctx->width,
video->codec_ctx->height,
gdk_pix_fmt,
0,
NULL,
NULL,
NULL);
video->memory_format = memory_format_from_pix_fmt (gdk_pix_fmt);
}
sws_scale(video->sws_ctx,
(const uint8_t * const *) frame->data, frame->linesize,
0, video->codec_ctx->height,
(uint8_t *[1]) { data }, (int[1]) { video->codec_ctx->width * 4 });
bytes = g_bytes_new_take (data, video->codec_ctx->width * video->codec_ctx->height * 4);
texture = gdk_memory_texture_new (video->codec_ctx->width,
video->codec_ctx->height,
video->memory_format,
bytes,
video->codec_ctx->width * 4);
g_bytes_unref (bytes);
gtk_video_frame_ffmpeg_init (result,
texture,
av_rescale_q (av_frame_get_best_effort_timestamp (frame),
video->format_ctx->streams[video->stream_id]->time_base,
(AVRational) { 1, G_USEC_PER_SEC }));
av_frame_free (&frame);
return TRUE;
}
static int64_t
gtk_ff_media_file_seek_cb (void *data,
int64_t offset,
int whence)
{
GtkFfMediaFile *video = data;
GSeekType seek_type;
gboolean result;
switch (whence)
{
case SEEK_SET:
seek_type = G_SEEK_SET;
break;
case SEEK_CUR:
seek_type = G_SEEK_CUR;
break;
case SEEK_END:
seek_type = G_SEEK_END;
break;
case AVSEEK_SIZE:
/* FIXME: Handle size querying */
return -1;
default:
g_assert_not_reached ();
return -1;
}
result = g_seekable_seek (G_SEEKABLE (video->input_stream),
offset,
seek_type,
NULL,
NULL);
if (!result)
return -1;
return g_seekable_tell (G_SEEKABLE (video->input_stream));
}
static gboolean
gtk_ff_media_file_create_input_stream (GtkFfMediaFile *video)
{
GError *error = NULL;
GFile *file;
file = gtk_media_file_get_file (GTK_MEDIA_FILE (video));
if (file)
{
video->input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
if (video->input_stream == NULL)
{
gtk_media_stream_gerror (GTK_MEDIA_STREAM (video), error);
g_error_free (error);
return FALSE;
}
}
else
{
video->input_stream = g_object_ref (gtk_media_file_get_input_stream (GTK_MEDIA_FILE (video)));
}
return TRUE;
}
static AVIOContext *
gtk_ff_media_file_create_io_context (GtkFfMediaFile *video)
{
AVIOContext *result;
int buffer_size = 4096; /* it's what everybody else uses... */
unsigned char *buffer;
if (!gtk_ff_media_file_create_input_stream (video))
return NULL;
buffer = av_malloc (buffer_size);
if (buffer == NULL)
return NULL;
result = avio_alloc_context (buffer,
buffer_size,
AVIO_FLAG_READ,
video,
gtk_ff_media_file_read_packet_cb,
NULL,
G_IS_SEEKABLE (video->input_stream)
? gtk_ff_media_file_seek_cb
: NULL);
result->buf_ptr = result->buf_end;
result->write_flag = 0;
return result;
}
static gboolean gtk_ff_media_file_play (GtkMediaStream *stream);
static void
gtk_ff_media_file_open (GtkMediaFile *file)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (file);
AVStream *stream;
AVCodec *codec;
int errnum;
video->format_ctx = avformat_alloc_context ();
video->format_ctx->pb = gtk_ff_media_file_create_io_context (video);
if (video->format_ctx->pb == NULL)
{
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("Not enough memory"));
return;
}
errnum = avformat_open_input (&video->format_ctx, NULL, NULL, NULL);
if (errnum != 0)
{
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
return;
}
errnum = avformat_find_stream_info (video->format_ctx, NULL);
if (errnum < 0)
{
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
return;
}
video->stream_id = av_find_best_stream (video->format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video->stream_id < 0)
{
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
_("Not a video file"));
return;
}
stream = video->format_ctx->streams[video->stream_id];
/* alpha transparency requires the libvpx codecs, not the ffmpeg builtin ones */
if (stream->codecpar->codec_id == AV_CODEC_ID_VP8)
codec = avcodec_find_decoder_by_name ("libvpx");
else if (stream->codecpar->codec_id == AV_CODEC_ID_VP9)
codec = avcodec_find_decoder_by_name ("libvpx-vp9");
else
codec = NULL;
if (codec == NULL)
codec = avcodec_find_decoder (stream->codecpar->codec_id);
if (codec == NULL)
{
gtk_media_stream_error (GTK_MEDIA_STREAM (video),
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
_("Unsupported video codec"));
return;
}
video->codec_ctx = avcodec_alloc_context3 (codec);
errnum = avcodec_parameters_to_context (video->codec_ctx, stream->codecpar);
if (errnum < 0)
{
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
return;
}
errnum = avcodec_open2 (video->codec_ctx, codec, &stream->metadata);
if (errnum < 0)
{
gtk_ff_media_file_set_ffmpeg_error (video, errnum);
return;
}
gtk_media_stream_prepared (GTK_MEDIA_STREAM (video),
FALSE,
video->codec_ctx != NULL,
TRUE,
video->format_ctx->duration != AV_NOPTS_VALUE
? av_rescale (video->format_ctx->duration, G_USEC_PER_SEC, AV_TIME_BASE)
: 0);
gdk_paintable_invalidate_size (GDK_PAINTABLE (video));
if (gtk_ff_media_file_decode_frame (video, &video->current_frame))
gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
if (gtk_media_stream_get_playing (GTK_MEDIA_STREAM (video)))
gtk_ff_media_file_play (GTK_MEDIA_STREAM (video));
}
static void
gtk_ff_media_file_close (GtkMediaFile *file)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (file);
g_clear_object (&video->input_stream);
g_clear_pointer (&video->sws_ctx, sws_freeContext);
g_clear_pointer (&video->codec_ctx, avcodec_close);
avformat_close_input (&video->format_ctx);
video->stream_id = -1;
gtk_video_frame_ffmpeg_clear (&video->next_frame);
gtk_video_frame_ffmpeg_clear (&video->current_frame);
gdk_paintable_invalidate_size (GDK_PAINTABLE (video));
gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
}
static gboolean
gtk_ff_media_file_next_frame_cb (gpointer data);
static void
gtk_ff_media_file_queue_frame (GtkFfMediaFile *video)
{
gint64 time, frame_time;
guint delay;
time = g_get_monotonic_time ();
frame_time = video->start_time + video->next_frame.timestamp;
delay = time > frame_time ? 0 : (frame_time - time) / 1000;
video->next_frame_cb = g_timeout_add (delay, gtk_ff_media_file_next_frame_cb, video);
}
static gboolean
gtk_ff_media_file_restart (GtkFfMediaFile *video)
{
if (av_seek_frame (video->format_ctx,
video->stream_id,
av_rescale_q (0,
(AVRational) { 1, G_USEC_PER_SEC },
video->format_ctx->streams[video->stream_id]->time_base),
AVSEEK_FLAG_BACKWARD) < 0)
return FALSE;
if (!gtk_ff_media_file_decode_frame (video, &video->next_frame))
return FALSE;
return TRUE;
}
static gboolean
gtk_ff_media_file_next_frame_cb (gpointer data)
{
GtkFfMediaFile *video = data;
video->next_frame_cb = 0;
if (gtk_video_frame_ffmpeg_is_empty (&video->next_frame))
{
if (!gtk_media_stream_get_loop (GTK_MEDIA_STREAM (video)) ||
!gtk_ff_media_file_restart (video))
{
gtk_media_stream_ended (GTK_MEDIA_STREAM (video));
return G_SOURCE_REMOVE;
}
video->start_time += video->current_frame.timestamp - video->next_frame.timestamp;
}
gtk_video_frame_ffmpeg_clear (&video->current_frame);
gtk_video_frame_ffmpeg_move (&video->current_frame,
&video->next_frame);
gtk_media_stream_update (GTK_MEDIA_STREAM (video),
video->current_frame.timestamp);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
/* ignore failure here, we'll handle the empty frame case above
* the next time we're called. */
gtk_ff_media_file_decode_frame (video, &video->next_frame);
gtk_ff_media_file_queue_frame (video);
return G_SOURCE_REMOVE;
}
static gboolean
gtk_ff_media_file_play (GtkMediaStream *stream)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
if (!gtk_media_stream_is_prepared (stream))
return TRUE;
if (gtk_video_frame_ffmpeg_is_empty (&video->next_frame) &&
!gtk_ff_media_file_decode_frame (video, &video->next_frame))
{
if (gtk_ff_media_file_restart (video))
{
video->start_time = g_get_monotonic_time () - video->next_frame.timestamp;
}
else
{
return FALSE;
}
}
else
{
video->start_time = g_get_monotonic_time () - video->current_frame.timestamp;
}
gtk_ff_media_file_queue_frame (video);
return TRUE;
}
static void
gtk_ff_media_file_pause (GtkMediaStream *stream)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
if (video->next_frame_cb)
{
g_source_remove (video->next_frame_cb);
video->next_frame_cb = 0;
}
video->start_time = 0;
}
static void
gtk_ff_media_file_seek (GtkMediaStream *stream,
gint64 timestamp)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
int errnum;
errnum = av_seek_frame (video->format_ctx,
video->stream_id,
av_rescale_q (timestamp,
(AVRational) { 1, G_USEC_PER_SEC },
video->format_ctx->streams[video->stream_id]->time_base),
AVSEEK_FLAG_BACKWARD);
if (errnum < 0)
{
gtk_media_stream_seek_failed (stream);
return;
}
gtk_media_stream_seek_success (stream);
gtk_video_frame_ffmpeg_clear (&video->next_frame);
gtk_video_frame_ffmpeg_clear (&video->current_frame);
if (gtk_ff_media_file_decode_frame (video, &video->current_frame))
gtk_media_stream_update (stream, video->current_frame.timestamp);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
if (gtk_media_stream_get_playing (stream))
{
gtk_ff_media_file_pause (stream);
if (!gtk_ff_media_file_play (stream))
gtk_media_stream_ended (stream);
}
}
static void
gtk_ff_media_file_dispose (GObject *object)
{
GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (object);
gtk_ff_media_file_pause (GTK_MEDIA_STREAM (video));
gtk_ff_media_file_close (GTK_MEDIA_FILE (video));
G_OBJECT_CLASS (gtk_ff_media_file_parent_class)->dispose (object);
}
static void
gtk_ff_media_file_class_init (GtkFfMediaFileClass *klass)
{
GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (klass);
GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
file_class->open = gtk_ff_media_file_open;
file_class->close = gtk_ff_media_file_close;
stream_class->play = gtk_ff_media_file_play;
stream_class->pause = gtk_ff_media_file_pause;
stream_class->seek = gtk_ff_media_file_seek;
gobject_class->dispose = gtk_ff_media_file_dispose;
}
static void
gtk_ff_media_file_init (GtkFfMediaFile *video)
{
video->stream_id = -1;
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_FF_MEDIA_FILE_H__
#define __GTK_FF_MEDIA_FILE_H__
#include <gtk/gtkmediafile.h>
G_BEGIN_DECLS
#define GTK_TYPE_FF_MEDIA_FILE (gtk_ff_media_file_get_type ())
G_DECLARE_FINAL_TYPE (GtkFfMediaFile, gtk_ff_media_file, GTK, FF_MEDIA_FILE, GtkMediaFile)
G_END_DECLS
#endif /* __GTK_FF_MEDIA_FILE_H__ */

View File

@@ -0,0 +1,342 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkgstmediafileprivate.h"
#include "gtkgstpaintableprivate.h"
#include <gst/player/gstplayer.h>
#include <gst/player/gstplayer-g-main-context-signal-dispatcher.h>
struct _GtkGstMediaFile
{
GtkMediaFile parent_instance;
GstPlayer *player;
GdkPaintable *paintable;
};
struct _GtkGstMediaFileClass
{
GtkMediaFileClass parent_class;
};
#define TO_GST_TIME(ts) ((ts) * (GST_SECOND / G_USEC_PER_SEC))
#define FROM_GST_TIME(ts) ((ts) / (GST_SECOND / G_USEC_PER_SEC))
static void
gtk_gst_media_file_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
gdk_paintable_snapshot (self->paintable, snapshot, width, height);
}
static GdkPaintable *
gtk_gst_media_file_paintable_get_current_image (GdkPaintable *paintable)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
return gdk_paintable_get_current_image (self->paintable);
}
static int
gtk_gst_media_file_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
return gdk_paintable_get_intrinsic_width (self->paintable);
}
static int
gtk_gst_media_file_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
return gdk_paintable_get_intrinsic_height (self->paintable);
}
static double gtk_gst_media_file_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (paintable);
return gdk_paintable_get_intrinsic_aspect_ratio (self->paintable);
};
static void
gtk_gst_media_file_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = gtk_gst_media_file_paintable_snapshot;
iface->get_current_image = gtk_gst_media_file_paintable_get_current_image;
iface->get_intrinsic_width = gtk_gst_media_file_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_gst_media_file_paintable_get_intrinsic_height;
iface->get_intrinsic_aspect_ratio = gtk_gst_media_file_paintable_get_intrinsic_aspect_ratio;
}
G_DEFINE_TYPE_EXTENDED (GtkGstMediaFile, gtk_gst_media_file, GTK_TYPE_MEDIA_FILE, 0,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_gst_media_file_paintable_init))
void
g_io_module_load (GIOModule *module)
{
g_type_module_use (G_TYPE_MODULE (module));
g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
GTK_TYPE_GST_MEDIA_FILE,
"gstreamer",
10);
}
void
g_io_module_unload (GIOModule *module)
{
g_assert_not_reached ();
}
char **
g_io_module_query (void)
{
char *eps[] = {
GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
NULL
};
return g_strdupv (eps);
}
static void
gtk_gst_media_file_position_updated_cb (GstPlayer *player,
GstClockTime time,
GtkGstMediaFile *self)
{
gtk_media_stream_update (GTK_MEDIA_STREAM (self), FROM_GST_TIME (time));
}
static void
gtk_gst_media_file_duration_changed_cb (GstPlayer *player,
GstClockTime duration,
GtkGstMediaFile *self)
{
if (gtk_media_stream_is_prepared (GTK_MEDIA_STREAM (self)))
return;
gtk_media_stream_prepared (GTK_MEDIA_STREAM (self),
TRUE,
TRUE,
TRUE,
FROM_GST_TIME (duration));
}
static void
gtk_gst_media_file_seek_done_cb (GstPlayer *player,
GstClockTime time,
GtkGstMediaFile *self)
{
/* if we're not seeking, we're doing the loop seek-back after EOS */
if (gtk_media_stream_is_seeking (GTK_MEDIA_STREAM (self)))
gtk_media_stream_seek_success (GTK_MEDIA_STREAM (self));
gtk_media_stream_update (GTK_MEDIA_STREAM (self), FROM_GST_TIME (time));
}
static void
gtk_gst_media_file_end_of_stream_cb (GstPlayer *player,
GtkGstMediaFile *self)
{
if (gtk_media_stream_get_ended (GTK_MEDIA_STREAM (self)))
return;
if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (self)))
{
gst_player_seek (self->player, 0);
return;
}
gtk_media_stream_ended (GTK_MEDIA_STREAM (self));
}
static void
gtk_gst_media_file_destroy_player (GtkGstMediaFile *self)
{
if (self->player == NULL)
return;
g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_duration_changed_cb, self);
g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_position_updated_cb, self);
g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_end_of_stream_cb, self);
g_signal_handlers_disconnect_by_func (self->player, gtk_gst_media_file_seek_done_cb, self);
g_object_unref (self->player);
self->player = NULL;
}
static void
gtk_gst_media_file_create_player (GtkGstMediaFile *file)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (file);
if (self->player != NULL)
return;
self->player = gst_player_new (GST_PLAYER_VIDEO_RENDERER (g_object_ref (self->paintable)),
gst_player_g_main_context_signal_dispatcher_new (NULL));
g_signal_connect (self->player, "duration-changed", G_CALLBACK (gtk_gst_media_file_duration_changed_cb), self);
g_signal_connect (self->player, "position-updated", G_CALLBACK (gtk_gst_media_file_position_updated_cb), self);
g_signal_connect (self->player, "end-of-stream", G_CALLBACK (gtk_gst_media_file_end_of_stream_cb), self);
g_signal_connect (self->player, "seek-done", G_CALLBACK (gtk_gst_media_file_seek_done_cb), self);
}
static void
gtk_gst_media_file_open (GtkMediaFile *media_file)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (media_file);
GFile *file;
gtk_gst_media_file_create_player (self);
file = gtk_media_file_get_file (media_file);
if (file)
{
/* XXX: This is technically incorrect because GFile uris aren't real uris */
char *uri = g_file_get_uri (file);
gst_player_set_uri (self->player, uri);
g_free (uri);
}
else
{
/* It's an input stream */
g_assert_not_reached ();
}
gst_player_pause (self->player);
}
static void
gtk_gst_media_file_close (GtkMediaFile *file)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (file);
gtk_gst_media_file_destroy_player (self);
}
static gboolean
gtk_gst_media_file_play (GtkMediaStream *stream)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
gst_player_play (self->player);
return TRUE;
}
static void
gtk_gst_media_file_pause (GtkMediaStream *stream)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
gst_player_pause (self->player);
}
static void
gtk_gst_media_file_seek (GtkMediaStream *stream,
gint64 timestamp)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
gst_player_seek (self->player, TO_GST_TIME (timestamp));
}
static void
gtk_gst_media_file_update_audio (GtkMediaStream *stream,
gboolean muted,
double volume)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
gst_player_set_mute (self->player, muted);
gst_player_set_volume (self->player, volume);
}
static void
gtk_gst_media_stream_realize (GtkMediaStream *stream,
GdkWindow *window)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
gtk_gst_paintable_realize (GTK_GST_PAINTABLE (self->paintable), window);
}
static void
gtk_gst_media_stream_unrealize (GtkMediaStream *stream,
GdkWindow *window)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (stream);
gtk_gst_paintable_unrealize (GTK_GST_PAINTABLE (self->paintable), window);
}
static void
gtk_gst_media_file_dispose (GObject *object)
{
GtkGstMediaFile *self = GTK_GST_MEDIA_FILE (object);
gtk_gst_media_file_destroy_player (self);
if (self->paintable)
{
g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_size, self);
g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_contents, self);
g_clear_object (&self->paintable);
}
G_OBJECT_CLASS (gtk_gst_media_file_parent_class)->dispose (object);
}
static void
gtk_gst_media_file_class_init (GtkGstMediaFileClass *klass)
{
GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (klass);
GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
file_class->open = gtk_gst_media_file_open;
file_class->close = gtk_gst_media_file_close;
stream_class->play = gtk_gst_media_file_play;
stream_class->pause = gtk_gst_media_file_pause;
stream_class->seek = gtk_gst_media_file_seek;
stream_class->update_audio = gtk_gst_media_file_update_audio;
stream_class->realize = gtk_gst_media_stream_realize;
stream_class->unrealize = gtk_gst_media_stream_unrealize;
gobject_class->dispose = gtk_gst_media_file_dispose;
}
static void
gtk_gst_media_file_init (GtkGstMediaFile *self)
{
self->paintable = gtk_gst_paintable_new ();
g_signal_connect_swapped (self->paintable, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
g_signal_connect_swapped (self->paintable, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_GST_MEDIA_FILE_H__
#define __GTK_GST_MEDIA_FILE_H__
#include <gtk/gtkmediafile.h>
G_BEGIN_DECLS
#define GTK_TYPE_GST_MEDIA_FILE (gtk_gst_media_file_get_type ())
G_DECLARE_FINAL_TYPE (GtkGstMediaFile, gtk_gst_media_file, GTK, GST_MEDIA_FILE, GtkMediaFile)
G_END_DECLS
#endif /* __GTK_GST_MEDIA_FILE_H__ */

View File

@@ -0,0 +1,274 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkgstpaintableprivate.h"
#include "gtkgstsinkprivate.h"
#include <gst/player/gstplayer-video-renderer.h>
struct _GtkGstPaintable
{
GObject parent_instance;
GdkGLContext *context;
GdkPaintable *image;
};
struct _GtkGstPaintableClass
{
GObjectClass parent_class;
};
static void
gtk_gst_paintable_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
if (self->image)
gdk_paintable_snapshot (self->image, snapshot, width, height);
}
static GdkPaintable *
gtk_gst_paintable_paintable_get_current_image (GdkPaintable *paintable)
{
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
if (self->image)
return GDK_PAINTABLE (g_object_ref (self->image));
g_warning ("FIXME: return empty something here");
return NULL;
}
static int
gtk_gst_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
if (self->image)
return gdk_paintable_get_intrinsic_width (self->image);
return 0;
}
static int
gtk_gst_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
if (self->image)
return gdk_paintable_get_intrinsic_height (self->image);
return 0;
}
static double
gtk_gst_paintable_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
{
GtkGstPaintable *self = GTK_GST_PAINTABLE (paintable);
if (self->image)
return gdk_paintable_get_intrinsic_aspect_ratio (self->image);
return 0.0;
};
static void
gtk_gst_paintable_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = gtk_gst_paintable_paintable_snapshot;
iface->get_current_image = gtk_gst_paintable_paintable_get_current_image;
iface->get_intrinsic_width = gtk_gst_paintable_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_gst_paintable_paintable_get_intrinsic_height;
iface->get_intrinsic_aspect_ratio = gtk_gst_paintable_paintable_get_intrinsic_aspect_ratio;
}
static GstElement *
gtk_gst_paintable_video_renderer_create_video_sink (GstPlayerVideoRenderer *renderer,
GstPlayer *player)
{
GtkGstPaintable *self = GTK_GST_PAINTABLE (renderer);
GstElement *element;
element = g_object_new (GTK_TYPE_GST_SINK,
"paintable", self,
"gl-context", self->context,
NULL);
return element;
}
static void
gtk_gst_paintable_video_renderer_init (GstPlayerVideoRendererInterface *iface)
{
iface->create_video_sink = gtk_gst_paintable_video_renderer_create_video_sink;
}
G_DEFINE_TYPE_WITH_CODE (GtkGstPaintable, gtk_gst_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_gst_paintable_paintable_init)
G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_VIDEO_RENDERER,
gtk_gst_paintable_video_renderer_init));
static void
gtk_gst_paintable_dispose (GObject *object)
{
GtkGstPaintable *self = GTK_GST_PAINTABLE (object);
g_clear_object (&self->image);
G_OBJECT_CLASS (gtk_gst_paintable_parent_class)->dispose (object);
}
static void
gtk_gst_paintable_class_init (GtkGstPaintableClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_gst_paintable_dispose;
}
static void
gtk_gst_paintable_init (GtkGstPaintable *self)
{
}
GdkPaintable *
gtk_gst_paintable_new (void)
{
return g_object_new (GTK_TYPE_GST_PAINTABLE, NULL);
}
void
gtk_gst_paintable_realize (GtkGstPaintable *self,
GdkWindow *window)
{
GError *error = NULL;
if (self->context)
return;
self->context = gdk_window_create_gl_context (window, &error);
if (self->context == NULL)
{
GST_INFO ("failed to create GDK GL context: %s", error->message);
g_error_free (error);
return;
}
if (!gdk_gl_context_realize (self->context, &error))
{
GST_INFO ("failed to realize GDK GL context: %s", error->message);
g_clear_object (&self->context);
g_error_free (error);
return;
}
}
void
gtk_gst_paintable_unrealize (GtkGstPaintable *self,
GdkWindow *window)
{
/* XXX: We could be smarter here and:
* - track how often we were realized with that window
* - track alternate windows
*/
if (self->context == NULL)
return;
if (gdk_gl_context_get_window (self->context) == window)
g_clear_object (&self->context);
}
static void
gtk_gst_paintable_set_paintable (GtkGstPaintable *self,
GdkPaintable *paintable)
{
gboolean size_changed;
if (self->image == paintable)
return;
if (self->image == NULL ||
gdk_paintable_get_intrinsic_width (self->image) != gdk_paintable_get_intrinsic_width (paintable) ||
gdk_paintable_get_intrinsic_height (self->image) != gdk_paintable_get_intrinsic_height (paintable) ||
gdk_paintable_get_intrinsic_aspect_ratio (self->image) != gdk_paintable_get_intrinsic_aspect_ratio (paintable))
size_changed = TRUE;
else
size_changed = FALSE;
g_set_object (&self->image, paintable);
if (size_changed)
gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
}
typedef struct _SetTextureInvocation SetTextureInvocation;
struct _SetTextureInvocation {
GtkGstPaintable *paintable;
GdkTexture *texture;
};
static void
set_texture_invocation_free (SetTextureInvocation *invoke)
{
g_object_unref (invoke->paintable);
g_object_unref (invoke->texture);
g_slice_free (SetTextureInvocation, invoke);
}
static gboolean
gtk_gst_paintable_set_texture_invoke (gpointer data)
{
SetTextureInvocation *invoke = data;
gtk_gst_paintable_set_paintable (invoke->paintable,
GDK_PAINTABLE (invoke->texture));
return G_SOURCE_REMOVE;
}
void
gtk_gst_paintable_queue_set_texture (GtkGstPaintable *self,
GdkTexture *texture)
{
SetTextureInvocation *invoke;
invoke = g_slice_new0 (SetTextureInvocation);
invoke->paintable = g_object_ref (self);
invoke->texture = g_object_ref (texture);
g_main_context_invoke_full (NULL,
G_PRIORITY_DEFAULT,
gtk_gst_paintable_set_texture_invoke,
invoke,
(GDestroyNotify) set_texture_invocation_free);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_GST_PAINTABLE_H__
#define __GTK_GST_PAINTABLE_H__
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define GTK_TYPE_GST_PAINTABLE (gtk_gst_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkGstPaintable, gtk_gst_paintable, GTK, GST_PAINTABLE, GObject)
GdkPaintable * gtk_gst_paintable_new (void);
void gtk_gst_paintable_realize (GtkGstPaintable *self,
GdkWindow *window);
void gtk_gst_paintable_unrealize (GtkGstPaintable *self,
GdkWindow *window);
void gtk_gst_paintable_queue_set_texture (GtkGstPaintable *self,
GdkTexture *texture);
G_END_DECLS
#endif /* __GTK_GST_PAINTABLE_H__ */

525
modules/media/gtkgstsink.c Normal file
View File

@@ -0,0 +1,525 @@
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
*/
#include "config.h"
#include "gtkgstsinkprivate.h"
#include "gtkgstpaintableprivate.h"
#include "gtkintl.h"
#if GST_GL_HAVE_WINDOW_X11 && GST_GL_HAVE_PLATFORM_GLX && defined (GDK_WINDOWING_X11)
#include <gdk/x11/gdkx.h>
#include <gst/gl/x11/gstgldisplay_x11.h>
#endif
#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
#include <gdk/wayland/gdkwayland.h>
#include <gst/gl/wayland/gstgldisplay_wayland.h>
#endif
enum {
PROP_0,
PROP_PAINTABLE,
PROP_GL_CONTEXT,
N_PROPS,
};
GST_DEBUG_CATEGORY (gtk_debug_gst_sink);
#define GST_CAT_DEFAULT gtk_debug_gst_sink
#define FORMATS "{ BGRA, ARGB, RGBA, ABGR, RGB, BGR }"
#define NOGL_CAPS GST_VIDEO_CAPS_MAKE (FORMATS)
#define GL_CAPS GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA")
static GstStaticPadTemplate gtk_gst_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GL_CAPS "; " NOGL_CAPS)
);
G_DEFINE_TYPE_WITH_CODE (GtkGstSink, gtk_gst_sink,
GST_TYPE_VIDEO_SINK,
GST_DEBUG_CATEGORY_INIT (gtk_debug_gst_sink,
"gtkgstsink", 0, "GtkGstMediaFile Video Sink"));
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_gst_sink_get_times (GstBaseSink *bsink,
GstBuffer *buf,
GstClockTime *start,
GstClockTime *end)
{
GtkGstSink *gtk_sink;
gtk_sink = GTK_GST_SINK (bsink);
if (GST_BUFFER_TIMESTAMP_IS_VALID (buf))
{
*start = GST_BUFFER_TIMESTAMP (buf);
if (GST_BUFFER_DURATION_IS_VALID (buf))
{
*end = *start + GST_BUFFER_DURATION (buf);
}
else
{
if (GST_VIDEO_INFO_FPS_N (&gtk_sink->v_info) > 0)
*end = *start + gst_util_uint64_scale_int (GST_SECOND,
GST_VIDEO_INFO_FPS_D (&gtk_sink->v_info),
GST_VIDEO_INFO_FPS_N (&gtk_sink->v_info));
}
}
}
static GstCaps *
gtk_gst_sink_get_caps (GstBaseSink *bsink,
GstCaps *filter)
{
GtkGstSink *self = GTK_GST_SINK (bsink);
GstCaps *tmp;
GstCaps *result;
if (self->gst_context)
{
tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
}
else
{
tmp = gst_caps_from_string (NOGL_CAPS);
}
GST_DEBUG_OBJECT (self, "advertising own caps %" GST_PTR_FORMAT, tmp);
if (filter)
{
GST_DEBUG_OBJECT (self, "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;
}
GST_DEBUG_OBJECT (self, "returning caps: %" GST_PTR_FORMAT, result);
return result;
}
static gboolean
gtk_gst_sink_set_caps (GstBaseSink *bsink,
GstCaps *caps)
{
GtkGstSink *self = GTK_GST_SINK (bsink);
GST_DEBUG_OBJECT (self, "set caps with %" GST_PTR_FORMAT, caps);
if (!gst_video_info_from_caps (&self->v_info, caps))
return FALSE;
return TRUE;
}
static gboolean
gtk_gst_sink_query (GstBaseSink *bsink,
GstQuery *query)
{
GtkGstSink *self = GTK_GST_SINK (bsink);
if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT &&
self->gst_display != NULL &&
gst_gl_handle_context_query (GST_ELEMENT (self), query, self->gst_display, self->gst_context, self->gst_app_context))
return TRUE;
return GST_BASE_SINK_CLASS (gtk_gst_sink_parent_class)->query (bsink, query);
}
static gboolean
gtk_gst_sink_propose_allocation (GstBaseSink *bsink,
GstQuery *query)
{
GtkGstSink *self = GTK_GST_SINK (bsink);
GstBufferPool *pool = NULL;
GstStructure *config;
GstCaps *caps;
guint size;
gboolean need_pool;
if (!self->gst_context)
return FALSE;
gst_query_parse_allocation (query, &caps, &need_pool);
if (caps == NULL)
{
GST_DEBUG_OBJECT (bsink, "no caps specified");
return FALSE;
}
if (!gst_caps_features_contains (gst_caps_get_features (caps, 0), GST_CAPS_FEATURE_MEMORY_GL_MEMORY))
return FALSE;
if (need_pool)
{
GstVideoInfo info;
if (!gst_video_info_from_caps (&info, caps))
{
GST_DEBUG_OBJECT (self, "invalid caps specified");
return FALSE;
}
GST_DEBUG_OBJECT (self, "create new pool");
pool = gst_gl_buffer_pool_new (self->gst_context);
/* the normal size of a frame */
size = info.size;
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_DEBUG_OBJECT (bsink, "failed setting config");
gst_object_unref (pool);
return FALSE;
}
/* we need at least 2 buffer because we hold on to the last one */
gst_query_add_allocation_pool (query, pool, size, 2, 0);
gst_object_unref (pool);
}
/* we also support various metadata */
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
return TRUE;
}
static GdkMemoryFormat
gtk_gst_memory_format_from_video (GstVideoFormat format)
{
switch ((guint) format)
{
case GST_VIDEO_FORMAT_BGRA:
return GDK_MEMORY_B8G8R8A8;
case GST_VIDEO_FORMAT_ARGB:
return GDK_MEMORY_A8R8G8B8;
case GST_VIDEO_FORMAT_RGBA:
return GDK_MEMORY_R8G8B8A8;
case GST_VIDEO_FORMAT_ABGR:
return GDK_MEMORY_A8B8G8R8;
case GST_VIDEO_FORMAT_RGB:
return GDK_MEMORY_R8G8B8;
case GST_VIDEO_FORMAT_BGR:
return GDK_MEMORY_B8G8R8;
default:
g_assert_not_reached ();
return GDK_MEMORY_A8R8G8B8;
}
}
static GdkTexture *
gtk_gst_sink_texture_from_buffer (GtkGstSink *self,
GstBuffer *buffer)
{
GstVideoFrame frame;
GdkTexture *texture;
if (buffer->pool && GST_IS_GL_BUFFER_POOL (buffer->pool) &&
gst_video_frame_map (&frame, &self->v_info, buffer, GST_MAP_READ | GST_MAP_GL))
{
texture = gdk_gl_texture_new (self->gdk_context,
*(guint *) frame.data[0],
frame.info.width,
frame.info.height,
(GDestroyNotify) gst_buffer_unref,
gst_buffer_ref (buffer));
gst_video_frame_unmap (&frame);
}
else if (gst_video_frame_map (&frame, &self->v_info, buffer, GST_MAP_READ))
{
GBytes *bytes;
bytes = g_bytes_new_with_free_func (frame.data[0],
frame.info.width * frame.info.stride[0],
(GDestroyNotify) gst_buffer_unref,
gst_buffer_ref (buffer));
texture = gdk_memory_texture_new (frame.info.width,
frame.info.height,
gtk_gst_memory_format_from_video (GST_VIDEO_FRAME_FORMAT (&frame)),
bytes,
frame.info.stride[0]);
gst_video_frame_unmap (&frame);
}
else
{
GST_ERROR_OBJECT (self, "Could not convert buffer to texture.");
texture = NULL;
}
return texture;
}
static GstFlowReturn
gtk_gst_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
{
GtkGstSink *self;
GdkTexture *texture;
GST_TRACE ("rendering buffer:%p", buf);
self = GTK_GST_SINK (vsink);
GST_OBJECT_LOCK (self);
texture = gtk_gst_sink_texture_from_buffer (self, buf);
if (texture)
{
gtk_gst_paintable_queue_set_texture (self->paintable, texture);
g_object_unref (texture);
}
GST_OBJECT_UNLOCK (self);
return GST_FLOW_OK;
}
static void
gtk_gst_sink_initialize_gl (GtkGstSink *self)
{
GdkDisplay *display;
GError *error = NULL;
display = gdk_gl_context_get_display (self->gdk_context);
gdk_gl_context_make_current (self->gdk_context);
#if GST_GL_HAVE_WINDOW_X11 && GST_GL_HAVE_PLATFORM_GLX && defined (GDK_WINDOWING_X11)
if (GDK_IS_X11_DISPLAY (display))
{
GstGLPlatform platform = GST_GL_PLATFORM_GLX;
GstGLAPI gl_api;
guintptr gl_handle;
GST_DEBUG_OBJECT (self, "got GLX on X11!");
gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
gl_handle = gst_gl_context_get_current_gl_context (platform);
if (gl_handle)
{
self->gst_display = GST_GL_DISPLAY (gst_gl_display_x11_new_with_display (gdk_x11_display_get_xdisplay (display)));
self->gst_app_context = gst_gl_context_new_wrapped (self->gst_display, gl_handle, platform, gl_api);
}
else
{
GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext, not using GLX");
return;
}
}
else
#endif
#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
if (GDK_IS_WAYLAND_DISPLAY (display))
{
GstGLPlatform platform = GST_GL_PLATFORM_GLX;
GstGLAPI gl_api;
guintptr gl_handle;
GST_DEBUG_OBJECT (self, "got EGL on Wayland!");
platform = GST_GL_PLATFORM_EGL;
gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
gl_handle = gst_gl_context_get_current_gl_context (platform);
if (gl_handle)
{
struct wl_display *wayland_display;
wayland_display = gdk_wayland_display_get_wl_display (display);
self->gst_display = GST_GL_DISPLAY (gst_gl_display_wayland_new_with_display (wayland_display));
self->gst_app_context = gst_gl_context_new_wrapped (self->gst_display, gl_handle, platform, gl_api);
}
else
{
GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext, not using Wayland EGL");
return;
}
}
else
#endif
{
GST_INFO_OBJECT (self, "Unsupported GDK display %s for GL", G_OBJECT_TYPE_NAME (display));
return;
}
g_assert (self->gst_app_context != NULL);
gst_gl_context_activate (self->gst_app_context, TRUE);
if (!gst_gl_context_fill_info (self->gst_app_context, &error))
{
GST_ERROR_OBJECT (self, "failed to retrieve GDK context info: %s", error->message);
g_clear_error (&error);
g_clear_object (&self->gst_app_context);
g_clear_object (&self->gst_display);
return;
} else {
gst_gl_context_activate (self->gst_app_context, FALSE);
}
if (!gst_gl_display_create_context (self->gst_display, self->gst_app_context, &self->gst_context, &error))
{
GST_ERROR_OBJECT (self, "Couldn't create GL context: %s", error->message);
g_error_free (error);
g_clear_object (&self->gst_app_context);
g_clear_object (&self->gst_display);
return;
}
}
static void
gtk_gst_sink_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkGstSink *self = GTK_GST_SINK (object);
switch (prop_id)
{
case PROP_PAINTABLE:
self->paintable = g_value_dup_object (value);
if (self->paintable == NULL)
self->paintable = GTK_GST_PAINTABLE (gtk_gst_paintable_new ());
break;
case PROP_GL_CONTEXT:
self->gdk_context = g_value_dup_object (value);
if (self->gdk_context != NULL)
gtk_gst_sink_initialize_gl (self);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_gst_sink_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkGstSink *self = GTK_GST_SINK (object);
switch (prop_id)
{
case PROP_PAINTABLE:
g_value_set_object (value, self->paintable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_gst_sink_dispose (GObject *object)
{
GtkGstSink *self = GTK_GST_SINK (object);
g_clear_object (&self->gst_app_context);
g_clear_object (&self->gst_display);
g_clear_object (&self->gdk_context);
g_clear_object (&self->paintable);
G_OBJECT_CLASS (gtk_gst_sink_parent_class)->dispose (object);
}
static void
gtk_gst_sink_class_init (GtkGstSinkClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
GstVideoSinkClass *gstvideosink_class = GST_VIDEO_SINK_CLASS (klass);
gobject_class->set_property = gtk_gst_sink_set_property;
gobject_class->get_property = gtk_gst_sink_get_property;
gobject_class->dispose = gtk_gst_sink_dispose;
gstbasesink_class->set_caps = gtk_gst_sink_set_caps;
gstbasesink_class->get_times = gtk_gst_sink_get_times;
gstbasesink_class->query = gtk_gst_sink_query;
gstbasesink_class->propose_allocation = gtk_gst_sink_propose_allocation;
gstbasesink_class->get_caps = gtk_gst_sink_get_caps;
gstvideosink_class->show_frame = gtk_gst_sink_show_frame;
/**
* GtkGstSink:paintable:
*
* The paintable that provides the picture for this sink.
*/
properties[PROP_PAINTABLE] =
g_param_spec_object ("paintable",
P_("paintable"),
P_("Paintable providing the picture"),
GTK_TYPE_GST_PAINTABLE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
/**
* GtkGstSink:gl-context:
*
* The #GdkGLContext to use for GL rendering.
*/
properties[PROP_GL_CONTEXT] =
g_param_spec_object ("gl-context",
P_("gl-context"),
P_("GL context to use for rendering"),
GDK_TYPE_GL_CONTEXT,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
gst_element_class_set_metadata (gstelement_class,
"GtkMediaStream Video Sink",
"Sink/Video", "The video sink used by GtkMediaStream",
"Matthew Waters <matthew@centricular.com>, "
"Benjamin Otte <otte@gnome.org>");
gst_element_class_add_static_pad_template (gstelement_class,
&gtk_gst_sink_template);
}
static void
gtk_gst_sink_init (GtkGstSink * gtk_sink)
{
}

View File

@@ -0,0 +1,68 @@
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
*/
#ifndef __GTK_GST_SINK_PRIVATE_H__
#define __GTK_GST_SINK_PRIVATE_H__
#include "gtkgstpaintableprivate.h"
#include <gst/gst.h>
#define GST_USE_UNSTABLE_API
#include <gst/gl/gl.h>
#include <gst/video/gstvideosink.h>
#include <gst/video/video.h>
#define GTK_TYPE_GST_SINK (gtk_gst_sink_get_type())
#define GTK_GST_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_SINK,GtkGstSink))
#define GTK_GST_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_SINK,GtkGstSinkClass))
#define GTK_GST_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_GST_SINK, GtkGstSinkClass))
#define GST_IS_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_SINK))
#define GST_IS_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_SINK))
#define GTK_GST_SINK_CAST(obj) ((GtkGstSink*)(obj))
G_BEGIN_DECLS
typedef struct _GtkGstSink GtkGstSink;
typedef struct _GtkGstSinkClass GtkGstSinkClass;
struct _GtkGstSink
{
/* <private> */
GstVideoSink parent;
GstVideoInfo v_info;
GtkGstPaintable * paintable;
GdkGLContext * gdk_context;
GstGLDisplay * gst_display;
GstGLContext * gst_app_context;
GstGLContext * gst_context;
};
struct _GtkGstSinkClass
{
GstVideoSinkClass object_class;
};
GType gtk_gst_sink_get_type (void);
G_END_DECLS
#endif /* __GTK_GST_SINK_PRIVATE_H__ */

26
modules/media/meson.build Normal file
View File

@@ -0,0 +1,26 @@
media_subdir = 'gtk-4.0/@0@/media'.format(gtk_binary_version)
media_install_dir = join_paths(get_option('libdir'), media_subdir)
if ffmpeg_enabled
shared_module('media-ffmpeg',
'gtkffmediafile.c',
c_args: [
'-DGTK_COMPILATION'
],
dependencies: [ libgtk_dep, ffmpeg_deps ],
install_dir: media_install_dir,
install : true)
endif
if gstreamer_enabled
shared_module('media-gstreamer',
'gtkgstmediafile.c',
'gtkgstpaintable.c',
'gtkgstsink.c',
c_args: [
'-DGTK_COMPILATION'
],
dependencies: [ libgtk_dep, gstreamer_deps ],
install_dir: media_install_dir,
install : true)
endif

View File

@@ -1,3 +1,5 @@
if os_unix
subdir('printbackends')
endif
subdir('media')

View File

@@ -4,12 +4,6 @@
/* maximum bytes per pixel */
#define MAX_BPP 4
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_B8G8R8A8_PREMULTIPLIED
#elif G_BYTE_ORDER == G_BIG_ENDIAN
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_A8R8G8B8_PREMULTIPLIED
#endif
typedef enum {
BLUE,
GREEN,
@@ -124,7 +118,7 @@ test_download_1x1 (gconstpointer data)
const TestData *test_data = data;
GdkTexture *expected, *test;
expected = create_texture (GDK_MEMORY_CAIRO_FORMAT_ARGB32, test_data->color, 1, 1, tests[test_data->format].bytes_per_pixel);
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 1, 1, tests[test_data->format].bytes_per_pixel);
test = create_texture (test_data->format, test_data->color, 1, 1, tests[test_data->format].bytes_per_pixel);
compare_textures (expected, test, tests[test_data->format].opaque);
@@ -139,7 +133,7 @@ test_download_1x1_with_stride (gconstpointer data)
const TestData *test_data = data;
GdkTexture *expected, *test;
expected = create_texture (GDK_MEMORY_CAIRO_FORMAT_ARGB32, test_data->color, 1, 1, 4);
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 1, 1, 4);
test = create_texture (test_data->format, test_data->color, 1, 1, 2 * MAX_BPP);
compare_textures (expected, test, tests[test_data->format].opaque);
@@ -154,7 +148,7 @@ test_download_4x4 (gconstpointer data)
const TestData *test_data = data;
GdkTexture *expected, *test;
expected = create_texture (GDK_MEMORY_CAIRO_FORMAT_ARGB32, test_data->color, 4, 4, 16);
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 4, 4, 16);
test = create_texture (test_data->format, test_data->color, 4, 4, 4 * tests[test_data->format].bytes_per_pixel);
compare_textures (expected, test, tests[test_data->format].opaque);
@@ -169,7 +163,7 @@ test_download_4x4_with_stride (gconstpointer data)
const TestData *test_data = data;
GdkTexture *expected, *test;
expected = create_texture (GDK_MEMORY_CAIRO_FORMAT_ARGB32, test_data->color, 4, 4, 16);
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 4, 4, 16);
test = create_texture (test_data->format, test_data->color, 4, 4, 4 * MAX_BPP);
compare_textures (expected, test, tests[test_data->format].opaque);