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 
17 static 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 ())
23 G_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. */
33 struct _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 
58 struct _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  */
66 static void
gtk_nuclear_media_stream_snapshot(GdkPaintable * paintable,GdkSnapshot * snapshot,double width,double height)67 gtk_nuclear_media_stream_snapshot (GdkPaintable *paintable,
68                                    GdkSnapshot  *snapshot,
69                                    double        width,
70                                    double        height)
71 {
72   GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (paintable);
73 
74   /* We call the function from the previous example here. */
75   gtk_nuclear_snapshot (snapshot,
76                         width, height,
77                         2 * G_PI * nuclear->progress / DURATION,
78                         TRUE);
79 }
80 
81 static GdkPaintable *
gtk_nuclear_media_stream_get_current_image(GdkPaintable * paintable)82 gtk_nuclear_media_stream_get_current_image (GdkPaintable *paintable)
83 {
84   GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (paintable);
85 
86   /* Same thing as with the animation */
87   return gtk_nuclear_icon_new (2 * G_PI * nuclear->progress / DURATION);
88 }
89 
90 static GdkPaintableFlags
gtk_nuclear_media_stream_get_flags(GdkPaintable * paintable)91 gtk_nuclear_media_stream_get_flags (GdkPaintable *paintable)
92 {
93   /* And same thing as with the animation over here, too. */
94   return GDK_PAINTABLE_STATIC_SIZE;
95 }
96 
97 static void
gtk_nuclear_media_stream_paintable_init(GdkPaintableInterface * iface)98 gtk_nuclear_media_stream_paintable_init (GdkPaintableInterface *iface)
99 {
100   iface->snapshot = gtk_nuclear_media_stream_snapshot;
101   iface->get_current_image = gtk_nuclear_media_stream_get_current_image;
102   iface->get_flags = gtk_nuclear_media_stream_get_flags;
103 }
104 
105 /* 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))106 G_DEFINE_TYPE_WITH_CODE (GtkNuclearMediaStream, gtk_nuclear_media_stream, GTK_TYPE_MEDIA_STREAM,
107                          G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
108                                                 gtk_nuclear_media_stream_paintable_init))
109 
110 static gboolean
111 gtk_nuclear_media_stream_step (gpointer data)
112 {
113   GtkNuclearMediaStream *nuclear = data;
114   gint64 current_time;
115 
116   /* Compute the time that has elapsed since the last time we were called
117    * and add it to our current progress.
118    */
119   current_time = g_source_get_time (g_main_current_source ());
120   nuclear->progress += current_time - nuclear->last_time;
121 
122   /* Check if we've ended */
123   if (nuclear->progress > DURATION)
124     {
125       if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (nuclear)))
126         {
127           /* We're looping. So make the progress loop using modulo */
128           nuclear->progress %= DURATION;
129         }
130       else
131         {
132           /* Just make sure we don't overflow */
133           nuclear->progress = DURATION;
134         }
135     }
136 
137   /* Update the last time to the current timestamp. */
138   nuclear->last_time = current_time;
139 
140   /* Update the timestamp of the media stream */
141   gtk_media_stream_update (GTK_MEDIA_STREAM (nuclear), nuclear->progress);
142 
143   /* We also need to invalidate our contents again.
144    * After all, we are a video and not just an audio stream.
145    */
146   gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
147 
148   /* Now check if we have finished playing and if so,
149    * tell the media stream. The media stream will then
150    * call our pause function to pause the stream.
151    */
152   if (nuclear->progress >= DURATION)
153     gtk_media_stream_stream_ended (GTK_MEDIA_STREAM (nuclear));
154 
155   /* The timeout function is removed by the pause function,
156    * so we can just always return this value.
157    */
158   return G_SOURCE_CONTINUE;
159 }
160 
161 static gboolean
gtk_nuclear_media_stream_play(GtkMediaStream * stream)162 gtk_nuclear_media_stream_play (GtkMediaStream *stream)
163 {
164   GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
165 
166   /* If we're already at the end of the stream, we don't want
167    * to start playing and exit early.
168    */
169   if (nuclear->progress >= DURATION)
170     return FALSE;
171 
172   /* This time, we add the source only when we start playing.
173    */
174   nuclear->source_id = g_timeout_add (10,
175                                       gtk_nuclear_media_stream_step,
176                                       nuclear);
177 
178   /* We also want to initialize our time, so that we can
179    * do accurate updates.
180    */
181   nuclear->last_time = g_get_monotonic_time ();
182 
183   /* We successfully started playing, so we return TRUE here. */
184   return TRUE;
185 }
186 
187 static void
gtk_nuclear_media_stream_pause(GtkMediaStream * stream)188 gtk_nuclear_media_stream_pause (GtkMediaStream *stream)
189 {
190   GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
191 
192   /* This function will be called when a playing stream
193    * gets paused.
194    * So we remove the updating source here and set it
195    * back to 0 so that the finalize function doesn't try
196    * to remove it again.
197    */
198   g_source_remove (nuclear->source_id);
199   nuclear->source_id = 0;
200   nuclear->last_time = 0;
201 }
202 
203 static void
gtk_nuclear_media_stream_seek(GtkMediaStream * stream,gint64 timestamp)204 gtk_nuclear_media_stream_seek (GtkMediaStream *stream,
205                                gint64          timestamp)
206 {
207   GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (stream);
208 
209   /* This is optional functionality for media streams,
210    * but not being able to seek is kinda boring.
211    * And it's trivial to implement, so let's go for it.
212    */
213   nuclear->progress = timestamp;
214 
215   /* Media streams are asynchronous, so seeking can take a while.
216    * We however don't need that functionality, so we can just
217    * report success.
218    */
219   gtk_media_stream_seek_success (stream);
220 
221   /* We also have to update our timestamp and tell the
222    * paintable interface about the seek
223    */
224   gtk_media_stream_update (stream, nuclear->progress);
225   gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
226 }
227 
228 /* Again, we need to implement the finalize function.
229  */
230 static void
gtk_nuclear_media_stream_finalize(GObject * object)231 gtk_nuclear_media_stream_finalize (GObject *object)
232 {
233   GtkNuclearMediaStream *nuclear = GTK_NUCLEAR_MEDIA_STREAM (object);
234 
235   /* This time, we need to check if the source exists before
236    * removing it as it only exists while we are playing.
237    */
238   if (nuclear->source_id > 0)
239     g_source_remove (nuclear->source_id);
240 
241   /* Don't forget to chain up to the parent class' implementation
242    * of the finalize function.
243    */
244   G_OBJECT_CLASS (gtk_nuclear_media_stream_parent_class)->finalize (object);
245 }
246 
247 /* In the class declaration, we need to implement the media stream */
248 static void
gtk_nuclear_media_stream_class_init(GtkNuclearMediaStreamClass * klass)249 gtk_nuclear_media_stream_class_init (GtkNuclearMediaStreamClass *klass)
250 {
251   GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
252   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
253 
254   stream_class->play = gtk_nuclear_media_stream_play;
255   stream_class->pause = gtk_nuclear_media_stream_pause;
256   stream_class->seek = gtk_nuclear_media_stream_seek;
257 
258   gobject_class->finalize = gtk_nuclear_media_stream_finalize;
259 }
260 
261 static void
gtk_nuclear_media_stream_init(GtkNuclearMediaStream * nuclear)262 gtk_nuclear_media_stream_init (GtkNuclearMediaStream *nuclear)
263 {
264   /* This time, we don't have to add a timer here, because media
265    * streams start paused.
266    *
267    * However, media streams need to tell GTK once they are initialized,
268    * so we do that here.
269    */
270   gtk_media_stream_stream_prepared (GTK_MEDIA_STREAM (nuclear),
271                                     FALSE,
272                                     TRUE,
273                                     TRUE,
274                                     DURATION);
275 }
276 
277 /* And finally, we add the simple constructor we declared in the header. */
278 GtkMediaStream *
gtk_nuclear_media_stream_new(void)279 gtk_nuclear_media_stream_new (void)
280 {
281   return g_object_new (GTK_TYPE_NUCLEAR_MEDIA_STREAM, NULL);
282 }
283 
284 GtkWidget *
do_paintable_mediastream(GtkWidget * do_widget)285 do_paintable_mediastream (GtkWidget *do_widget)
286 {
287   GtkMediaStream *nuclear;
288   GtkWidget *video;
289 
290   if (!window)
291     {
292       window = gtk_window_new ();
293       gtk_window_set_display (GTK_WINDOW (window),
294                               gtk_widget_get_display (do_widget));
295       gtk_window_set_title (GTK_WINDOW (window), "Nuclear MediaStream");
296       gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
297       g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
298 
299       nuclear = gtk_nuclear_media_stream_new ();
300       gtk_media_stream_set_loop (GTK_MEDIA_STREAM (nuclear), TRUE);
301 
302       video = gtk_video_new_for_media_stream (nuclear);
303       gtk_window_set_child (GTK_WINDOW (window), video);
304 
305       g_object_unref (nuclear);
306     }
307 
308   if (!gtk_widget_get_visible (window))
309     gtk_widget_show (window);
310   else
311     gtk_window_destroy (GTK_WINDOW (window));
312 
313   return window;
314 }
315