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 |
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 (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 | |
82 | static GdkPaintable * |
83 | gtk_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 | |
91 | static GdkPaintableFlags |
92 | gtk_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 | |
98 | static void |
99 | gtk_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 */ |
107 | G_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 | |
111 | static gboolean |
112 | gtk_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 | |
162 | static gboolean |
163 | gtk_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 | |
188 | static void |
189 | gtk_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 | |
204 | static void |
205 | gtk_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 | */ |
231 | static void |
232 | gtk_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 */ |
249 | static void |
250 | gtk_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 | |
262 | static void |
263 | gtk_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. */ |
279 | GtkMediaStream * |
280 | gtk_nuclear_media_stream_new (void) |
281 | { |
282 | return g_object_new (GTK_TYPE_NUCLEAR_MEDIA_STREAM, NULL); |
283 | } |
284 | |
285 | GtkWidget * |
286 | do_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 | |