1/* Paintable/Media Stream
2 *
3 * GdkPaintable is also used by the GtkMediaStream class.
4 *
5 * This demo code turns the nuclear media_stream into the object
6 * GTK uses for videos. This allows treating the icon like a
7 * regular video, so we can for example attach controls to it.
8 *
9 * After all, what good is a media_stream if one cannot pause
10 * it.
11 */
12
13#include <gtk/gtk.h>
14
15#include "paintable.h"
16
17static GtkWidget *window = NULL;
18
19/* First, add the boilerplate for the object itself.
20 * This part would normally go in the header.
21 */
22#define GTK_TYPE_NUCLEAR_MEDIA_STREAM (gtk_nuclear_media_stream_get_type ())
23G_DECLARE_FINAL_TYPE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK, NUCLEAR_MEDIA_STREAM, GtkMediaStream)
24
25/* Do a full rotation in 5 seconds.
26 *
27 * We do not save steps here but real timestamps.
28 * GtkMediaStream uses microseconds, so we will do so, too.
29 */
30#define DURATION (5 * G_USEC_PER_SEC)
31
32/* Declare the struct. */
33struct _GtkNuclearMediaStream
34{
35 /* We now inherit from the media stream object. */
36 GtkMediaStream parent_instance;
37
38 /* This variable stores the progress of our video.
39 */
40 gint64 progress;
41
42 /* This variable stores the timestamp of the last
43 * time we updated the progress variable when the
44 * video is currently playing.
45 * This is so that we can always accurately compute the
46 * progress we've had, even if the timeout does not
47 * exactly work.
48 */
49 gint64 last_time;
50
51 /* This variable again holds the ID of the timer that
52 * updates our progress variable. Nothing changes about
53 * how this works compared to the previous example.
54 */
55 guint source_id;
56};
57
58struct _GtkNuclearMediaStreamClass
59{
60 GObjectClass parent_class;
61};
62
63/* GtkMediaStream is a GdkPaintable. So when we want to display video,
64 * we have to implement the interface, just like in the animation example.
65 */
66static void
67gtk_nuclear_media_stream_snapshot (GdkPaintable *paintable,
68 GdkSnapshot *snapshot,
69 double width,
70 double height)
71{
72 GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (ptr: paintable);
73
74 /* We call the function from the previous example here. */
75 gtk_nuclear_snapshot (snapshot,
76 foreground: &(GdkRGBA) { 0, 0, 0, 1 }, /* black */
77 background: &(GdkRGBA) { 0.9, 0.75, 0.15, 1.0 }, /* yellow */
78 width, height,
79 rotation: 2 * G_PI * nuclear->progress / DURATION);
80}
81
82static GdkPaintable *
83gtk_nuclear_media_stream_get_current_image (GdkPaintable *paintable)
84{
85 GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (ptr: paintable);
86
87 /* Same thing as with the animation */
88 return gtk_nuclear_icon_new (rotation: 2 * G_PI * nuclear->progress / DURATION);
89}
90
91static GdkPaintableFlags
92gtk_nuclear_media_stream_get_flags (GdkPaintable *paintable)
93{
94 /* And same thing as with the animation over here, too. */
95 return GDK_PAINTABLE_STATIC_SIZE;
96}
97
98static void
99gtk_nuclear_media_stream_paintable_init (GdkPaintableInterface *iface)
100{
101 iface->snapshot = gtk_nuclear_media_stream_snapshot;
102 iface->get_current_image = gtk_nuclear_media_stream_get_current_image;
103 iface->get_flags = gtk_nuclear_media_stream_get_flags;
104}
105
106/* This time, we inherit from GTK_TYPE_MEDIA_STREAM */
107G_DEFINE_TYPE_WITH_CODE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK_TYPE_MEDIA_STREAM,
108 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
109 gtk_nuclear_media_stream_paintable_init))
110
111static gboolean
112gtk_nuclear_media_stream_step (gpointer data)
113{
114 GtkNuclearMediaStream *nuclear = data;
115 gint64 current_time;
116
117 /* Compute the time that has elapsed since the last time we were called
118 * and add it to our current progress.
119 */
120 current_time = g_source_get_time (source: g_main_current_source ());
121 nuclear->progress += current_time - nuclear->last_time;
122
123 /* Check if we've ended */
124 if (nuclear->progress > DURATION)
125 {
126 if (gtk_media_stream_get_loop (self: GTK_MEDIA_STREAM (ptr: nuclear)))
127 {
128 /* We're looping. So make the progress loop using modulo */
129 nuclear->progress %= DURATION;
130 }
131 else
132 {
133 /* Just make sure we don't overflow */
134 nuclear->progress = DURATION;
135 }
136 }
137
138 /* Update the last time to the current timestamp. */
139 nuclear->last_time = current_time;
140
141 /* Update the timestamp of the media stream */
142 gtk_media_stream_update (self: GTK_MEDIA_STREAM (ptr: nuclear), timestamp: nuclear->progress);
143
144 /* We also need to invalidate our contents again.
145 * After all, we are a video and not just an audio stream.
146 */
147 gdk_paintable_invalidate_contents (paintable: GDK_PAINTABLE (ptr: nuclear));
148
149 /* Now check if we have finished playing and if so,
150 * tell the media stream. The media stream will then
151 * call our pause function to pause the stream.
152 */
153 if (nuclear->progress >= DURATION)
154 gtk_media_stream_stream_ended (self: GTK_MEDIA_STREAM (ptr: nuclear));
155
156 /* The timeout function is removed by the pause function,
157 * so we can just always return this value.
158 */
159 return G_SOURCE_CONTINUE;
160}
161
162static gboolean
163gtk_nuclear_media_stream_play (GtkMediaStream *stream)
164{
165 GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (ptr: stream);
166
167 /* If we're already at the end of the stream, we don't want
168 * to start playing and exit early.
169 */
170 if (nuclear->progress >= DURATION)
171 return FALSE;
172
173 /* This time, we add the source only when we start playing.
174 */
175 nuclear->source_id = g_timeout_add (interval: 10,
176 function: gtk_nuclear_media_stream_step,
177 data: nuclear);
178
179 /* We also want to initialize our time, so that we can
180 * do accurate updates.
181 */
182 nuclear->last_time = g_get_monotonic_time ();
183
184 /* We successfully started playing, so we return TRUE here. */
185 return TRUE;
186}
187
188static void
189gtk_nuclear_media_stream_pause (GtkMediaStream *stream)
190{
191 GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (ptr: stream);
192
193 /* This function will be called when a playing stream
194 * gets paused.
195 * So we remove the updating source here and set it
196 * back to 0 so that the finalize function doesn't try
197 * to remove it again.
198 */
199 g_source_remove (tag: nuclear->source_id);
200 nuclear->source_id = 0;
201 nuclear->last_time = 0;
202}
203
204static void
205gtk_nuclear_media_stream_seek (GtkMediaStream *stream,
206 gint64 timestamp)
207{
208 GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (ptr: stream);
209
210 /* This is optional functionality for media streams,
211 * but not being able to seek is kinda boring.
212 * And it's trivial to implement, so let's go for it.
213 */
214 nuclear->progress = timestamp;
215
216 /* Media streams are asynchronous, so seeking can take a while.
217 * We however don't need that functionality, so we can just
218 * report success.
219 */
220 gtk_media_stream_seek_success (self: stream);
221
222 /* We also have to update our timestamp and tell the
223 * paintable interface about the seek
224 */
225 gtk_media_stream_update (self: stream, timestamp: nuclear->progress);
226 gdk_paintable_invalidate_contents (paintable: GDK_PAINTABLE (ptr: nuclear));
227}
228
229/* Again, we need to implement the finalize function.
230 */
231static void
232gtk_nuclear_media_stream_finalize (GObject *object)
233{
234 GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (ptr: object);
235
236 /* This time, we need to check if the source exists before
237 * removing it as it only exists while we are playing.
238 */
239 if (nuclear->source_id > 0)
240 g_source_remove (tag: nuclear->source_id);
241
242 /* Don't forget to chain up to the parent class' implementation
243 * of the finalize function.
244 */
245 G_OBJECT_CLASS (gtk_nuclear_media_stream_parent_class)->finalize (object);
246}
247
248/* In the class declaration, we need to implement the media stream */
249static void
250gtk_nuclear_media_stream_class_init (GtkNuclearMediaStreamClass *klass)
251{
252 GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (ptr: klass);
253 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
254
255 stream_class->play = gtk_nuclear_media_stream_play;
256 stream_class->pause = gtk_nuclear_media_stream_pause;
257 stream_class->seek = gtk_nuclear_media_stream_seek;
258
259 gobject_class->finalize = gtk_nuclear_media_stream_finalize;
260}
261
262static void
263gtk_nuclear_media_stream_init (GtkNuclearMediaStream *nuclear)
264{
265 /* This time, we don't have to add a timer here, because media
266 * streams start paused.
267 *
268 * However, media streams need to tell GTK once they are initialized,
269 * so we do that here.
270 */
271 gtk_media_stream_stream_prepared (self: GTK_MEDIA_STREAM (ptr: nuclear),
272 FALSE,
273 TRUE,
274 TRUE,
275 DURATION);
276}
277
278/* And finally, we add the simple constructor we declared in the header. */
279GtkMediaStream *
280gtk_nuclear_media_stream_new (void)
281{
282 return g_object_new (GTK_TYPE_NUCLEAR_MEDIA_STREAM, NULL);
283}
284
285GtkWidget *
286do_paintable_mediastream (GtkWidget *do_widget)
287{
288 GtkMediaStream *nuclear;
289 GtkWidget *video;
290
291 if (!window)
292 {
293 window = gtk_window_new ();
294 gtk_window_set_display (GTK_WINDOW (window),
295 display: gtk_widget_get_display (widget: do_widget));
296 gtk_window_set_title (GTK_WINDOW (window), title: "Nuclear MediaStream");
297 gtk_window_set_default_size (GTK_WINDOW (window), width: 300, height: 200);
298 g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window);
299
300 nuclear = gtk_nuclear_media_stream_new ();
301 gtk_media_stream_set_loop (self: GTK_MEDIA_STREAM (ptr: nuclear), TRUE);
302
303 video = gtk_video_new_for_media_stream (stream: nuclear);
304 gtk_window_set_child (GTK_WINDOW (window), child: video);
305
306 g_object_unref (object: nuclear);
307 }
308
309 if (!gtk_widget_get_visible (widget: window))
310 gtk_widget_show (widget: window);
311 else
312 gtk_window_destroy (GTK_WINDOW (window));
313
314 return window;
315}
316

source code of gtk/demos/gtk-demo/paintable_mediastream.c