1 /*
2  * Copyright © 2018 Benjamin Otte
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Benjamin Otte <otte@gnome.org>
18  */
19 
20 #include "config.h"
21 
22 #include "gtkffmediafileprivate.h"
23 
24 #include "gtkintl.h"
25 
26 #include "gdk/gdkmemorytextureprivate.h"
27 
28 #include <libavcodec/avcodec.h>
29 #include <libavformat/avformat.h>
30 #include <libavutil/pixdesc.h>
31 #include <libswscale/swscale.h>
32 
33 typedef struct _GtkVideoFrameFFMpeg GtkVideoFrameFFMpeg;
34 
35 struct _GtkVideoFrameFFMpeg
36 {
37   GdkTexture *texture;
38   gint64 timestamp;
39 };
40 
41 struct _GtkFfMediaFile
42 {
43   GtkMediaFile parent_instance;
44 
45   GFile *file;
46   GInputStream *input_stream;
47 
48   AVFormatContext *format_ctx;
49   AVCodecContext *codec_ctx;
50   int stream_id;
51   struct SwsContext *sws_ctx;
52   enum AVPixelFormat sws_pix_fmt;
53   GdkMemoryFormat memory_format;
54 
55   GtkVideoFrameFFMpeg current_frame;
56   GtkVideoFrameFFMpeg next_frame;
57 
58   gint64 start_time; /* monotonic time when we displayed the last frame */
59   guint next_frame_cb; /* Source ID of next frame callback */
60 };
61 
62 struct _GtkFfMediaFileClass
63 {
64   GtkMediaFileClass parent_class;
65 };
66 
67 static void
gtk_video_frame_ffmpeg_init(GtkVideoFrameFFMpeg * frame,GdkTexture * texture,gint64 timestamp)68 gtk_video_frame_ffmpeg_init (GtkVideoFrameFFMpeg *frame,
69                              GdkTexture          *texture,
70                              gint64               timestamp)
71 {
72   frame->texture = texture;
73   frame->timestamp = timestamp;
74 }
75 
76 static void
gtk_video_frame_ffmpeg_clear(GtkVideoFrameFFMpeg * frame)77 gtk_video_frame_ffmpeg_clear (GtkVideoFrameFFMpeg *frame)
78 {
79   g_clear_object (&frame->texture);
80   frame->timestamp = 0;
81 }
82 
83 static gboolean
gtk_video_frame_ffmpeg_is_empty(GtkVideoFrameFFMpeg * frame)84 gtk_video_frame_ffmpeg_is_empty (GtkVideoFrameFFMpeg *frame)
85 {
86   return frame->texture == NULL;
87 }
88 
89 static void
gtk_video_frame_ffmpeg_move(GtkVideoFrameFFMpeg * dest,GtkVideoFrameFFMpeg * src)90 gtk_video_frame_ffmpeg_move (GtkVideoFrameFFMpeg *dest,
91                              GtkVideoFrameFFMpeg *src)
92 {
93   *dest = *src;
94   src->texture = NULL;
95   src->timestamp = 0;
96 }
97 
98 static void
gtk_ff_media_file_paintable_snapshot(GdkPaintable * paintable,GdkSnapshot * snapshot,double width,double height)99 gtk_ff_media_file_paintable_snapshot (GdkPaintable *paintable,
100                                       GdkSnapshot  *snapshot,
101                                       double        width,
102                                       double        height)
103 {
104   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
105 
106   if (!gtk_video_frame_ffmpeg_is_empty (&video->current_frame))
107     {
108       gdk_paintable_snapshot (GDK_PAINTABLE (video->current_frame.texture), snapshot, width, height);
109     }
110 }
111 
112 static GdkPaintable *
gtk_ff_media_file_paintable_get_current_image(GdkPaintable * paintable)113 gtk_ff_media_file_paintable_get_current_image (GdkPaintable *paintable)
114 {
115   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
116 
117   if (gtk_video_frame_ffmpeg_is_empty (&video->current_frame))
118     {
119       if (video->codec_ctx)
120         return gdk_paintable_new_empty (video->codec_ctx->width, video->codec_ctx->height);
121       else
122         return gdk_paintable_new_empty (0, 0);
123     }
124 
125   return GDK_PAINTABLE (g_object_ref (video->current_frame.texture));
126 }
127 
128 static int
gtk_ff_media_file_paintable_get_intrinsic_width(GdkPaintable * paintable)129 gtk_ff_media_file_paintable_get_intrinsic_width (GdkPaintable *paintable)
130 {
131   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
132 
133   if (video->codec_ctx)
134     return video->codec_ctx->width;
135 
136   return 0;
137 }
138 
139 static int
gtk_ff_media_file_paintable_get_intrinsic_height(GdkPaintable * paintable)140 gtk_ff_media_file_paintable_get_intrinsic_height (GdkPaintable *paintable)
141 {
142   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
143 
144   if (video->codec_ctx)
145     return video->codec_ctx->height;
146 
147   return 0;
148 }
149 
gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio(GdkPaintable * paintable)150 static double gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
151 {
152   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (paintable);
153 
154   if (video->codec_ctx)
155     return (double) video->codec_ctx->width / video->codec_ctx->height;
156 
157   return 0.0;
158 };
159 
160 static void
gtk_ff_media_file_paintable_init(GdkPaintableInterface * iface)161 gtk_ff_media_file_paintable_init (GdkPaintableInterface *iface)
162 {
163   iface->snapshot = gtk_ff_media_file_paintable_snapshot;
164   iface->get_current_image = gtk_ff_media_file_paintable_get_current_image;
165   iface->get_intrinsic_width = gtk_ff_media_file_paintable_get_intrinsic_width;
166   iface->get_intrinsic_height = gtk_ff_media_file_paintable_get_intrinsic_height;
167   iface->get_intrinsic_aspect_ratio = gtk_ff_media_file_paintable_get_intrinsic_aspect_ratio;
168 }
169 
170 G_DEFINE_TYPE_EXTENDED (GtkFfMediaFile, gtk_ff_media_file, GTK_TYPE_MEDIA_FILE, 0,
171                         G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
172                                                gtk_ff_media_file_paintable_init))
173 
174 G_MODULE_EXPORT
175 void
g_io_module_load(GIOModule * module)176 g_io_module_load (GIOModule *module)
177 {
178   g_type_module_use (G_TYPE_MODULE (module));
179 
180 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT (58, 9, 100)
181   av_register_all ();
182 #endif
183 
184   g_io_extension_point_implement (GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
185                                   GTK_TYPE_FF_MEDIA_FILE,
186                                   "ffmpeg",
187                                   0);
188 }
189 
190 G_MODULE_EXPORT
191 G_GNUC_NORETURN
192 void
g_io_module_unload(GIOModule * module)193 g_io_module_unload (GIOModule *module)
194 {
195   g_assert_not_reached ();
196 }
197 
198 G_MODULE_EXPORT
199 char **
g_io_module_query(void)200 g_io_module_query (void)
201 {
202   char *eps[] = {
203     (char *) GTK_MEDIA_FILE_EXTENSION_POINT_NAME,
204     NULL
205   };
206 
207   return g_strdupv (eps);
208 }
209 
210 static void
gtk_ff_media_file_set_ffmpeg_error(GtkFfMediaFile * video,int av_errnum)211 gtk_ff_media_file_set_ffmpeg_error (GtkFfMediaFile *video,
212                                     int           av_errnum)
213 {
214   char s[AV_ERROR_MAX_STRING_SIZE];
215 
216   if (gtk_media_stream_get_error (GTK_MEDIA_STREAM (video)))
217     return;
218 
219   if (av_strerror (av_errnum, s, sizeof (s) != 0))
220     g_snprintf (s, sizeof (s), _("Unspecified error decoding video"));
221 
222   gtk_media_stream_error (GTK_MEDIA_STREAM (video),
223                           G_IO_ERROR,
224                           G_IO_ERROR_FAILED,
225                           "%s",
226                           s);
227 }
228 
229 static int
gtk_ff_media_file_read_packet_cb(void * data,uint8_t * buf,int buf_size)230 gtk_ff_media_file_read_packet_cb (void    *data,
231                                   uint8_t *buf,
232                                   int      buf_size)
233 {
234   GtkFfMediaFile *video = data;
235   GError *error = NULL;
236   gssize n_read;
237 
238   n_read = g_input_stream_read (video->input_stream,
239                                 buf,
240                                 buf_size,
241                                 NULL,
242                                 &error);
243   if (n_read < 0)
244     {
245       gtk_media_stream_gerror (GTK_MEDIA_STREAM (video), error);
246     }
247   else if (n_read == 0)
248     {
249       n_read = AVERROR_EOF;
250     }
251 
252   return n_read;
253 }
254 
255 static GdkMemoryFormat
memory_format_from_pix_fmt(enum AVPixelFormat pix_fmt)256 memory_format_from_pix_fmt (enum AVPixelFormat pix_fmt)
257 {
258   switch ((int) pix_fmt)
259     {
260     case AV_PIX_FMT_RGBA:
261       return GDK_MEMORY_R8G8B8A8;
262     case AV_PIX_FMT_RGB24:
263       return GDK_MEMORY_R8G8B8;
264     default:
265       g_assert_not_reached ();
266       return GDK_MEMORY_R8G8B8A8;
267     }
268 }
269 
270 static gboolean
gtk_ff_media_file_decode_frame(GtkFfMediaFile * video,GtkVideoFrameFFMpeg * result)271 gtk_ff_media_file_decode_frame (GtkFfMediaFile      *video,
272                                 GtkVideoFrameFFMpeg *result)
273 {
274   GdkTexture *texture;
275   AVPacket packet;
276   AVFrame *frame;
277   int errnum;
278   GBytes *bytes;
279   guchar *data;
280 
281   frame = av_frame_alloc ();
282 
283   for (errnum = av_read_frame (video->format_ctx, &packet);
284        errnum >= 0;
285        errnum = av_read_frame (video->format_ctx, &packet))
286     {
287       if (packet.stream_index == video->stream_id)
288         {
289           errnum = avcodec_send_packet (video->codec_ctx, &packet);
290               if (errnum < 0)
291                 G_BREAKPOINT();
292           if (errnum >= 0)
293             {
294               errnum = avcodec_receive_frame (video->codec_ctx, frame);
295               if (errnum < 0)
296                 G_BREAKPOINT();
297               if (errnum >= 0)
298                 {
299                   av_packet_unref (&packet);
300                   break;
301                 }
302             }
303         }
304 
305       av_packet_unref (&packet);
306     }
307 
308   if (errnum < 0)
309     {
310       if (errnum != AVERROR_EOF)
311         gtk_ff_media_file_set_ffmpeg_error (video, errnum);
312       av_frame_free (&frame);
313       return FALSE;
314     }
315 
316   data = g_try_malloc0 (video->codec_ctx->width * video->codec_ctx->height * 4);
317   if (data == NULL)
318     {
319       gtk_media_stream_error (GTK_MEDIA_STREAM (video),
320                               G_IO_ERROR,
321                               G_IO_ERROR_FAILED,
322                               _("Not enough memory"));
323       av_frame_free (&frame);
324       return FALSE;
325     }
326 
327   if (video->sws_ctx == NULL ||
328       video->sws_pix_fmt != frame->format)
329     {
330       const AVPixFmtDescriptor *desc;
331       enum AVPixelFormat gdk_pix_fmt;
332 
333       g_clear_pointer (&video->sws_ctx, sws_freeContext);
334       video->sws_pix_fmt = frame->format;
335       desc = av_pix_fmt_desc_get (video->sws_pix_fmt);
336       /* Use gdk-pixbuf formats because ffmpeg can't premultiply */
337       if (desc != NULL && (desc->flags & AV_PIX_FMT_FLAG_ALPHA))
338         gdk_pix_fmt = AV_PIX_FMT_RGBA;
339       else
340         gdk_pix_fmt = AV_PIX_FMT_RGB24;
341 
342       video->sws_ctx = sws_getContext (video->codec_ctx->width,
343                                        video->codec_ctx->height,
344                                        frame->format,
345                                        video->codec_ctx->width,
346                                        video->codec_ctx->height,
347                                        gdk_pix_fmt,
348                                        0,
349                                        NULL,
350                                        NULL,
351                                        NULL);
352 
353       video->memory_format = memory_format_from_pix_fmt (gdk_pix_fmt);
354     }
355 
356   sws_scale(video->sws_ctx,
357             (const uint8_t * const *) frame->data, frame->linesize,
358             0, video->codec_ctx->height,
359             (uint8_t *[1]) { data }, (int[1]) { video->codec_ctx->width * 4 });
360 
361   bytes = g_bytes_new_take (data, video->codec_ctx->width * video->codec_ctx->height * 4);
362   texture = gdk_memory_texture_new (video->codec_ctx->width,
363                                     video->codec_ctx->height,
364                                     video->memory_format,
365                                     bytes,
366                                     video->codec_ctx->width * 4);
367 
368   g_bytes_unref (bytes);
369 
370   gtk_video_frame_ffmpeg_init (result,
371                                texture,
372                                av_rescale_q (frame->best_effort_timestamp,
373                                              video->format_ctx->streams[video->stream_id]->time_base,
374                                              (AVRational) { 1, G_USEC_PER_SEC }));
375 
376   av_frame_free (&frame);
377 
378   return TRUE;
379 }
380 
381 static int64_t
gtk_ff_media_file_seek_cb(void * data,int64_t offset,int whence)382 gtk_ff_media_file_seek_cb (void    *data,
383                            int64_t  offset,
384                            int      whence)
385 {
386   GtkFfMediaFile *video = data;
387   GSeekType seek_type;
388   gboolean result;
389 
390   switch (whence)
391     {
392     case SEEK_SET:
393       seek_type = G_SEEK_SET;
394       break;
395 
396     case SEEK_CUR:
397       seek_type = G_SEEK_CUR;
398       break;
399 
400     case SEEK_END:
401       seek_type = G_SEEK_END;
402       break;
403 
404     case AVSEEK_SIZE:
405       /* FIXME: Handle size querying */
406       return -1;
407 
408     default:
409       g_assert_not_reached ();
410       return -1;
411     }
412 
413   result = g_seekable_seek (G_SEEKABLE (video->input_stream),
414                             offset,
415                             seek_type,
416                             NULL,
417                             NULL);
418   if (!result)
419     return -1;
420 
421   return g_seekable_tell (G_SEEKABLE (video->input_stream));
422 }
423 
424 static gboolean
gtk_ff_media_file_create_input_stream(GtkFfMediaFile * video)425 gtk_ff_media_file_create_input_stream (GtkFfMediaFile *video)
426 {
427   GError *error = NULL;
428   GFile *file;
429 
430   file = gtk_media_file_get_file (GTK_MEDIA_FILE (video));
431   if (file)
432     {
433       video->input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
434       if (video->input_stream == NULL)
435         {
436           gtk_media_stream_gerror (GTK_MEDIA_STREAM (video), error);
437           g_error_free (error);
438           return FALSE;
439         }
440     }
441   else
442     {
443       video->input_stream = g_object_ref (gtk_media_file_get_input_stream (GTK_MEDIA_FILE (video)));
444     }
445 
446   return TRUE;
447 }
448 
449 static AVIOContext *
gtk_ff_media_file_create_io_context(GtkFfMediaFile * video)450 gtk_ff_media_file_create_io_context (GtkFfMediaFile *video)
451 {
452   AVIOContext *result;
453   int buffer_size = 4096; /* it's what everybody else uses... */
454   unsigned char *buffer;
455 
456   if (!gtk_ff_media_file_create_input_stream (video))
457     return NULL;
458 
459   buffer = av_malloc (buffer_size);
460   if (buffer == NULL)
461     return NULL;
462 
463   result = avio_alloc_context (buffer,
464                                buffer_size,
465                                AVIO_FLAG_READ,
466                                video,
467                                gtk_ff_media_file_read_packet_cb,
468                                NULL,
469                                G_IS_SEEKABLE (video->input_stream)
470                                ? gtk_ff_media_file_seek_cb
471                                : NULL);
472 
473   result->buf_ptr = result->buf_end;
474   result->write_flag = 0;
475 
476   return result;
477 }
478 
479 static gboolean gtk_ff_media_file_play (GtkMediaStream *stream);
480 
481 static void
gtk_ff_media_file_open(GtkMediaFile * file)482 gtk_ff_media_file_open (GtkMediaFile *file)
483 {
484   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (file);
485   AVStream *stream;
486   AVCodec *codec;
487   int errnum;
488 
489   video->format_ctx = avformat_alloc_context ();
490   video->format_ctx->pb = gtk_ff_media_file_create_io_context (video);
491   if (video->format_ctx->pb == NULL)
492     {
493       gtk_media_stream_error (GTK_MEDIA_STREAM (video),
494                               G_IO_ERROR,
495                               G_IO_ERROR_FAILED,
496                               _("Not enough memory"));
497       return;
498     }
499   errnum = avformat_open_input (&video->format_ctx, NULL, NULL, NULL);
500   if (errnum != 0)
501     {
502       gtk_ff_media_file_set_ffmpeg_error (video, errnum);
503       return;
504     }
505 
506   errnum = avformat_find_stream_info (video->format_ctx, NULL);
507   if (errnum < 0)
508     {
509       gtk_ff_media_file_set_ffmpeg_error (video, errnum);
510       return;
511     }
512 
513   video->stream_id = av_find_best_stream (video->format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
514   if (video->stream_id < 0)
515     {
516       gtk_media_stream_error (GTK_MEDIA_STREAM (video),
517                               G_IO_ERROR,
518                               G_IO_ERROR_INVALID_DATA,
519                               _("Not a video file"));
520       return;
521     }
522 
523   stream = video->format_ctx->streams[video->stream_id];
524   /* alpha transparency requires the libvpx codecs, not the ffmpeg builtin ones */
525   if (stream->codecpar->codec_id == AV_CODEC_ID_VP8)
526     codec = avcodec_find_decoder_by_name ("libvpx");
527   else if (stream->codecpar->codec_id == AV_CODEC_ID_VP9)
528     codec = avcodec_find_decoder_by_name ("libvpx-vp9");
529   else
530     codec = NULL;
531   if (codec == NULL)
532     codec = avcodec_find_decoder (stream->codecpar->codec_id);
533   if (codec == NULL)
534     {
535       gtk_media_stream_error (GTK_MEDIA_STREAM (video),
536                               G_IO_ERROR,
537                               G_IO_ERROR_NOT_SUPPORTED,
538                               _("Unsupported video codec"));
539       return;
540     }
541 
542   video->codec_ctx = avcodec_alloc_context3 (codec);
543   errnum = avcodec_parameters_to_context (video->codec_ctx, stream->codecpar);
544   if (errnum < 0)
545     {
546       gtk_ff_media_file_set_ffmpeg_error (video, errnum);
547       return;
548     }
549   errnum = avcodec_open2 (video->codec_ctx, codec, &stream->metadata);
550   if (errnum < 0)
551     {
552       gtk_ff_media_file_set_ffmpeg_error (video, errnum);
553       return;
554     }
555 
556   gtk_media_stream_stream_prepared (GTK_MEDIA_STREAM (video),
557                                     FALSE,
558                                     video->codec_ctx != NULL,
559                                     TRUE,
560                                     video->format_ctx->duration != AV_NOPTS_VALUE
561                                       ? av_rescale (video->format_ctx->duration, G_USEC_PER_SEC, AV_TIME_BASE)
562                                       : 0);
563 
564   gdk_paintable_invalidate_size (GDK_PAINTABLE (video));
565 
566   if (gtk_ff_media_file_decode_frame (video, &video->current_frame))
567     gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
568 
569   if (gtk_media_stream_get_playing (GTK_MEDIA_STREAM (video)))
570     gtk_ff_media_file_play (GTK_MEDIA_STREAM (video));
571 }
572 
573 static void
gtk_ff_media_file_close(GtkMediaFile * file)574 gtk_ff_media_file_close (GtkMediaFile *file)
575 {
576   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (file);
577 
578   g_clear_object (&video->input_stream);
579 
580   g_clear_pointer (&video->sws_ctx, sws_freeContext);
581   g_clear_pointer (&video->codec_ctx, avcodec_close);
582   avformat_close_input (&video->format_ctx);
583   video->stream_id = -1;
584   gtk_video_frame_ffmpeg_clear (&video->next_frame);
585   gtk_video_frame_ffmpeg_clear (&video->current_frame);
586 
587   gdk_paintable_invalidate_size (GDK_PAINTABLE (video));
588   gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
589 }
590 
591 static gboolean
592 gtk_ff_media_file_next_frame_cb (gpointer data);
593 static void
gtk_ff_media_file_queue_frame(GtkFfMediaFile * video)594 gtk_ff_media_file_queue_frame (GtkFfMediaFile *video)
595 {
596   gint64 time, frame_time;
597   guint delay;
598 
599   time = g_get_monotonic_time ();
600   frame_time = video->start_time + video->next_frame.timestamp;
601   delay = time > frame_time ? 0 : (frame_time - time) / 1000;
602 
603   video->next_frame_cb = g_timeout_add (delay, gtk_ff_media_file_next_frame_cb, video);
604 }
605 
606 static gboolean
gtk_ff_media_file_restart(GtkFfMediaFile * video)607 gtk_ff_media_file_restart (GtkFfMediaFile *video)
608 {
609   if (av_seek_frame (video->format_ctx,
610                      video->stream_id,
611                      av_rescale_q (0,
612                                    (AVRational) { 1, G_USEC_PER_SEC },
613                                    video->format_ctx->streams[video->stream_id]->time_base),
614                      AVSEEK_FLAG_BACKWARD) < 0)
615     return FALSE;
616 
617   if (!gtk_ff_media_file_decode_frame (video, &video->next_frame))
618     return FALSE;
619 
620   return TRUE;
621 }
622 
623 static gboolean
gtk_ff_media_file_next_frame_cb(gpointer data)624 gtk_ff_media_file_next_frame_cb (gpointer data)
625 {
626   GtkFfMediaFile *video = data;
627 
628   video->next_frame_cb = 0;
629 
630   if (gtk_video_frame_ffmpeg_is_empty (&video->next_frame))
631     {
632       if (!gtk_media_stream_get_loop (GTK_MEDIA_STREAM (video)) ||
633           !gtk_ff_media_file_restart (video))
634         {
635           gtk_media_stream_stream_ended (GTK_MEDIA_STREAM (video));
636           return G_SOURCE_REMOVE;
637         }
638 
639       video->start_time += video->current_frame.timestamp - video->next_frame.timestamp;
640     }
641 
642   gtk_video_frame_ffmpeg_clear (&video->current_frame);
643   gtk_video_frame_ffmpeg_move (&video->current_frame,
644                                &video->next_frame);
645 
646   gtk_media_stream_update (GTK_MEDIA_STREAM (video),
647                            video->current_frame.timestamp);
648   gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
649 
650   /* ignore failure here, we'll handle the empty frame case above
651    * the next time we're called. */
652   gtk_ff_media_file_decode_frame (video, &video->next_frame);
653   gtk_ff_media_file_queue_frame (video);
654 
655   return G_SOURCE_REMOVE;
656 }
657 
658 static gboolean
gtk_ff_media_file_play(GtkMediaStream * stream)659 gtk_ff_media_file_play (GtkMediaStream *stream)
660 {
661   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
662 
663   if (video->format_ctx == NULL)
664     return FALSE;
665 
666   if (!gtk_media_stream_is_prepared (stream))
667     return TRUE;
668 
669   if (gtk_video_frame_ffmpeg_is_empty (&video->next_frame) &&
670       !gtk_ff_media_file_decode_frame (video, &video->next_frame))
671     {
672       if (gtk_ff_media_file_restart (video))
673         {
674           video->start_time = g_get_monotonic_time () - video->next_frame.timestamp;
675         }
676       else
677         {
678           return FALSE;
679         }
680     }
681   else
682     {
683       video->start_time = g_get_monotonic_time () - video->current_frame.timestamp;
684     }
685 
686   gtk_ff_media_file_queue_frame (video);
687 
688   return TRUE;
689 }
690 
691 static void
gtk_ff_media_file_pause(GtkMediaStream * stream)692 gtk_ff_media_file_pause (GtkMediaStream *stream)
693 {
694   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
695 
696   if (video->next_frame_cb)
697     {
698       g_source_remove (video->next_frame_cb);
699       video->next_frame_cb = 0;
700     }
701 
702   video->start_time = 0;
703 }
704 
705 static void
gtk_ff_media_file_seek(GtkMediaStream * stream,gint64 timestamp)706 gtk_ff_media_file_seek (GtkMediaStream *stream,
707                         gint64          timestamp)
708 {
709   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (stream);
710   int errnum;
711 
712   errnum = av_seek_frame (video->format_ctx,
713                           video->stream_id,
714                           av_rescale_q (timestamp,
715                                         (AVRational) { 1, G_USEC_PER_SEC },
716                                         video->format_ctx->streams[video->stream_id]->time_base),
717                                         AVSEEK_FLAG_BACKWARD);
718   if (errnum < 0)
719     {
720       gtk_media_stream_seek_failed (stream);
721       return;
722     }
723 
724   gtk_media_stream_seek_success (stream);
725 
726   gtk_video_frame_ffmpeg_clear (&video->next_frame);
727   gtk_video_frame_ffmpeg_clear (&video->current_frame);
728   if (gtk_ff_media_file_decode_frame (video, &video->current_frame))
729     gtk_media_stream_update (stream, video->current_frame.timestamp);
730   gdk_paintable_invalidate_contents (GDK_PAINTABLE (video));
731 
732   if (gtk_media_stream_get_playing (stream))
733     {
734       gtk_ff_media_file_pause (stream);
735       if (!gtk_ff_media_file_play (stream))
736         gtk_media_stream_stream_ended (stream);
737     }
738 }
739 
740 static void
gtk_ff_media_file_dispose(GObject * object)741 gtk_ff_media_file_dispose (GObject *object)
742 {
743   GtkFfMediaFile *video = GTK_FF_MEDIA_FILE (object);
744 
745   gtk_ff_media_file_pause (GTK_MEDIA_STREAM (video));
746   gtk_ff_media_file_close (GTK_MEDIA_FILE (video));
747 
748   G_OBJECT_CLASS (gtk_ff_media_file_parent_class)->dispose (object);
749 }
750 
751 static void
gtk_ff_media_file_class_init(GtkFfMediaFileClass * klass)752 gtk_ff_media_file_class_init (GtkFfMediaFileClass *klass)
753 {
754   GtkMediaFileClass *file_class = GTK_MEDIA_FILE_CLASS (klass);
755   GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
756   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
757 
758   file_class->open = gtk_ff_media_file_open;
759   file_class->close = gtk_ff_media_file_close;
760   stream_class->play = gtk_ff_media_file_play;
761   stream_class->pause = gtk_ff_media_file_pause;
762   stream_class->seek = gtk_ff_media_file_seek;
763 
764   gobject_class->dispose = gtk_ff_media_file_dispose;
765 }
766 
767 static void
gtk_ff_media_file_init(GtkFfMediaFile * video)768 gtk_ff_media_file_init (GtkFfMediaFile *video)
769 {
770   video->stream_id = -1;
771 }
772 
773 
774