• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /**
21  * SECTION:element-hlssink
22  * @title: hlssink
23  *
24  * HTTP Live Streaming sink/server
25  *
26  * ## Example launch line
27  * |[
28  * gst-launch-1.0 videotestsrc is-live=true ! x264enc ! mpegtsmux ! hlssink max-files=5
29  * ]|
30  *
31  */
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35 
36 #include "gsthlselements.h"
37 #include "gsthlssink.h"
38 #include <gst/pbutils/pbutils.h>
39 #include <gst/video/video.h>
40 #include <glib/gstdio.h>
41 #include <memory.h>
42 
43 
44 GST_DEBUG_CATEGORY_STATIC (gst_hls_sink_debug);
45 #define GST_CAT_DEFAULT gst_hls_sink_debug
46 
47 #define DEFAULT_LOCATION "segment%05d.ts"
48 #define DEFAULT_PLAYLIST_LOCATION "playlist.m3u8"
49 #define DEFAULT_PLAYLIST_ROOT NULL
50 #define DEFAULT_MAX_FILES 10
51 #define DEFAULT_TARGET_DURATION 15
52 #define DEFAULT_PLAYLIST_LENGTH 5
53 
54 #define GST_M3U8_PLAYLIST_VERSION 3
55 
56 enum
57 {
58   PROP_0,
59   PROP_LOCATION,
60   PROP_PLAYLIST_LOCATION,
61   PROP_PLAYLIST_ROOT,
62   PROP_MAX_FILES,
63   PROP_TARGET_DURATION,
64   PROP_PLAYLIST_LENGTH
65 };
66 
67 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
68     GST_PAD_SINK,
69     GST_PAD_ALWAYS,
70     GST_STATIC_CAPS_ANY);
71 
72 #define gst_hls_sink_parent_class parent_class
73 G_DEFINE_TYPE (GstHlsSink, gst_hls_sink, GST_TYPE_BIN);
74 #define _do_init \
75   hls_element_init (plugin); \
76   GST_DEBUG_CATEGORY_INIT (gst_hls_sink_debug, "hlssink", 0, "HlsSink");
77 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (hlssink, "hlssink", GST_RANK_NONE,
78     GST_TYPE_HLS_SINK, _do_init);
79 
80 static void gst_hls_sink_set_property (GObject * object, guint prop_id,
81     const GValue * value, GParamSpec * spec);
82 static void gst_hls_sink_get_property (GObject * object, guint prop_id,
83     GValue * value, GParamSpec * spec);
84 static void gst_hls_sink_handle_message (GstBin * bin, GstMessage * message);
85 static GstPadProbeReturn gst_hls_sink_ghost_event_probe (GstPad * pad,
86     GstPadProbeInfo * info, gpointer data);
87 static GstPadProbeReturn gst_hls_sink_ghost_buffer_probe (GstPad * pad,
88     GstPadProbeInfo * info, gpointer data);
89 static void gst_hls_sink_reset (GstHlsSink * sink);
90 static GstStateChangeReturn
91 gst_hls_sink_change_state (GstElement * element, GstStateChange trans);
92 static gboolean schedule_next_key_unit (GstHlsSink * sink);
93 static GstFlowReturn gst_hls_sink_chain_list (GstPad * pad, GstObject * parent,
94     GstBufferList * list);
95 
96 static void
gst_hls_sink_dispose(GObject * object)97 gst_hls_sink_dispose (GObject * object)
98 {
99   GstHlsSink *sink = GST_HLS_SINK_CAST (object);
100 
101   G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
102 }
103 
104 static void
gst_hls_sink_finalize(GObject * object)105 gst_hls_sink_finalize (GObject * object)
106 {
107   GstHlsSink *sink = GST_HLS_SINK_CAST (object);
108 
109   g_free (sink->location);
110   g_free (sink->playlist_location);
111   g_free (sink->playlist_root);
112   if (sink->playlist)
113     gst_m3u8_playlist_free (sink->playlist);
114 
115   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
116 }
117 
118 static void
gst_hls_sink_class_init(GstHlsSinkClass * klass)119 gst_hls_sink_class_init (GstHlsSinkClass * klass)
120 {
121   GObjectClass *gobject_class;
122   GstElementClass *element_class;
123   GstBinClass *bin_class;
124 
125   gobject_class = (GObjectClass *) klass;
126   element_class = GST_ELEMENT_CLASS (klass);
127   bin_class = GST_BIN_CLASS (klass);
128 
129   gst_element_class_add_static_pad_template (element_class, &sink_template);
130 
131   gst_element_class_set_static_metadata (element_class,
132       "HTTP Live Streaming sink", "Sink", "HTTP Live Streaming sink",
133       "Alessandro Decina <alessandro.d@gmail.com>");
134 
135   element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_sink_change_state);
136 
137   bin_class->handle_message = gst_hls_sink_handle_message;
138 
139   gobject_class->dispose = gst_hls_sink_dispose;
140   gobject_class->finalize = gst_hls_sink_finalize;
141   gobject_class->set_property = gst_hls_sink_set_property;
142   gobject_class->get_property = gst_hls_sink_get_property;
143 
144   g_object_class_install_property (gobject_class, PROP_LOCATION,
145       g_param_spec_string ("location", "File Location",
146           "Location of the file to write", DEFAULT_LOCATION,
147           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
148   g_object_class_install_property (gobject_class, PROP_PLAYLIST_LOCATION,
149       g_param_spec_string ("playlist-location", "Playlist Location",
150           "Location of the playlist to write", DEFAULT_PLAYLIST_LOCATION,
151           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
152   g_object_class_install_property (gobject_class, PROP_PLAYLIST_ROOT,
153       g_param_spec_string ("playlist-root", "Playlist Root",
154           "Location of the playlist to write", DEFAULT_PLAYLIST_ROOT,
155           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
156   g_object_class_install_property (gobject_class, PROP_MAX_FILES,
157       g_param_spec_uint ("max-files", "Max files",
158           "Maximum number of files to keep on disk. Once the maximum is reached,"
159           "old files start to be deleted to make room for new ones.",
160           0, G_MAXUINT, DEFAULT_MAX_FILES,
161           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
162   g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
163       g_param_spec_uint ("target-duration", "Target duration",
164           "The target duration in seconds of a segment/file. "
165           "(0 - disabled, useful for management of segment duration by the "
166           "streaming server)",
167           0, G_MAXUINT, DEFAULT_TARGET_DURATION,
168           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
169   g_object_class_install_property (gobject_class, PROP_PLAYLIST_LENGTH,
170       g_param_spec_uint ("playlist-length", "Playlist length",
171           "Length of HLS playlist. To allow players to conform to section 6.3.3 "
172           "of the HLS specification, this should be at least 3. If set to 0, "
173           "the playlist will be infinite.",
174           0, G_MAXUINT, DEFAULT_PLAYLIST_LENGTH,
175           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
176 }
177 
178 static void
gst_hls_sink_init(GstHlsSink * sink)179 gst_hls_sink_init (GstHlsSink * sink)
180 {
181   GstPadTemplate *templ = gst_static_pad_template_get (&sink_template);
182   sink->ghostpad = gst_ghost_pad_new_no_target_from_template ("sink", templ);
183   gst_object_unref (templ);
184   gst_element_add_pad (GST_ELEMENT_CAST (sink), sink->ghostpad);
185   gst_pad_add_probe (sink->ghostpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
186       gst_hls_sink_ghost_event_probe, sink, NULL);
187   gst_pad_add_probe (sink->ghostpad, GST_PAD_PROBE_TYPE_BUFFER,
188       gst_hls_sink_ghost_buffer_probe, sink, NULL);
189   gst_pad_set_chain_list_function (sink->ghostpad, gst_hls_sink_chain_list);
190 
191   sink->location = g_strdup (DEFAULT_LOCATION);
192   sink->playlist_location = g_strdup (DEFAULT_PLAYLIST_LOCATION);
193   sink->playlist_root = g_strdup (DEFAULT_PLAYLIST_ROOT);
194   sink->playlist_length = DEFAULT_PLAYLIST_LENGTH;
195   sink->max_files = DEFAULT_MAX_FILES;
196   sink->target_duration = DEFAULT_TARGET_DURATION;
197 
198   /* haven't added a sink yet, make it is detected as a sink meanwhile */
199   GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
200 
201   gst_hls_sink_reset (sink);
202 }
203 
204 static void
gst_hls_sink_reset(GstHlsSink * sink)205 gst_hls_sink_reset (GstHlsSink * sink)
206 {
207   sink->index = 0;
208   sink->last_running_time = 0;
209   sink->waiting_fku = FALSE;
210   gst_event_replace (&sink->force_key_unit_event, NULL);
211   gst_segment_init (&sink->segment, GST_FORMAT_UNDEFINED);
212 
213   if (sink->playlist)
214     gst_m3u8_playlist_free (sink->playlist);
215   sink->playlist =
216       gst_m3u8_playlist_new (GST_M3U8_PLAYLIST_VERSION, sink->playlist_length);
217 
218   sink->state = GST_M3U8_PLAYLIST_RENDER_INIT;
219 }
220 
221 static gboolean
gst_hls_sink_create_elements(GstHlsSink * sink)222 gst_hls_sink_create_elements (GstHlsSink * sink)
223 {
224   GstPad *pad = NULL;
225 
226   GST_DEBUG_OBJECT (sink, "Creating internal elements");
227 
228   if (sink->elements_created)
229     return TRUE;
230 
231   sink->multifilesink = gst_element_factory_make ("multifilesink", NULL);
232   if (sink->multifilesink == NULL)
233     goto missing_element;
234 
235   g_object_set (sink->multifilesink, "location", sink->location,
236       "next-file", 3, "post-messages", TRUE, "max-files", sink->max_files,
237       NULL);
238 
239   gst_bin_add (GST_BIN_CAST (sink), sink->multifilesink);
240 
241   pad = gst_element_get_static_pad (sink->multifilesink, "sink");
242   gst_ghost_pad_set_target (GST_GHOST_PAD (sink->ghostpad), pad);
243   gst_object_unref (pad);
244 
245   sink->elements_created = TRUE;
246   return TRUE;
247 
248 missing_element:
249   gst_element_post_message (GST_ELEMENT_CAST (sink),
250       gst_missing_element_message_new (GST_ELEMENT_CAST (sink),
251           "multifilesink"));
252   GST_ELEMENT_ERROR (sink, CORE, MISSING_PLUGIN,
253       (("Missing element '%s' - check your GStreamer installation."),
254           "multifilesink"), (NULL));
255   return FALSE;
256 }
257 
258 static void
gst_hls_sink_write_playlist(GstHlsSink * sink)259 gst_hls_sink_write_playlist (GstHlsSink * sink)
260 {
261   char *playlist_content;
262   GError *error = NULL;
263 
264   playlist_content = gst_m3u8_playlist_render (sink->playlist);
265   if (!g_file_set_contents (sink->playlist_location,
266           playlist_content, -1, &error)) {
267     GST_ERROR ("Failed to write playlist: %s", error->message);
268     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
269         (("Failed to write playlist '%s'."), error->message), (NULL));
270     g_error_free (error);
271     error = NULL;
272   }
273   g_free (playlist_content);
274 
275 }
276 
277 static void
gst_hls_sink_handle_message(GstBin * bin,GstMessage * message)278 gst_hls_sink_handle_message (GstBin * bin, GstMessage * message)
279 {
280   GstHlsSink *sink = GST_HLS_SINK_CAST (bin);
281 
282   switch (message->type) {
283     case GST_MESSAGE_ELEMENT:
284     {
285       const char *filename;
286       GstClockTime running_time, duration;
287       gboolean discont = FALSE;
288       gchar *entry_location;
289       const GstStructure *structure;
290 
291       structure = gst_message_get_structure (message);
292       if (strcmp (gst_structure_get_name (structure), "GstMultiFileSink"))
293         break;
294 
295       filename = gst_structure_get_string (structure, "filename");
296       gst_structure_get_clock_time (structure, "running-time", &running_time);
297       duration = running_time - sink->last_running_time;
298       sink->last_running_time = running_time;
299 
300       GST_INFO_OBJECT (sink, "COUNT %d", sink->index);
301       if (sink->playlist_root == NULL)
302         entry_location = g_path_get_basename (filename);
303       else {
304         gchar *name = g_path_get_basename (filename);
305         entry_location = g_build_filename (sink->playlist_root, name, NULL);
306         g_free (name);
307       }
308 
309       gst_m3u8_playlist_add_entry (sink->playlist, entry_location,
310           NULL, duration, sink->index, discont);
311       g_free (entry_location);
312 
313       gst_hls_sink_write_playlist (sink);
314       sink->state |= GST_M3U8_PLAYLIST_RENDER_STARTED;
315 
316       /* multifilesink is starting a new file. It means that upstream sent a key
317        * unit and we can schedule the next key unit now.
318        */
319       sink->waiting_fku = FALSE;
320       schedule_next_key_unit (sink);
321 
322       /* multifilesink is an internal implementation detail. If applications
323        * need a notification, we should probably do our own message */
324       GST_DEBUG_OBJECT (bin, "dropping message %" GST_PTR_FORMAT, message);
325       gst_message_unref (message);
326       message = NULL;
327       break;
328     }
329     case GST_MESSAGE_EOS:{
330       sink->playlist->end_list = TRUE;
331       gst_hls_sink_write_playlist (sink);
332       sink->state |= GST_M3U8_PLAYLIST_RENDER_ENDED;
333       break;
334     }
335     default:
336       break;
337   }
338 
339   if (message)
340     GST_BIN_CLASS (parent_class)->handle_message (bin, message);
341 }
342 
343 static GstStateChangeReturn
gst_hls_sink_change_state(GstElement * element,GstStateChange trans)344 gst_hls_sink_change_state (GstElement * element, GstStateChange trans)
345 {
346   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
347   GstHlsSink *sink = GST_HLS_SINK_CAST (element);
348 
349   switch (trans) {
350     case GST_STATE_CHANGE_NULL_TO_READY:
351       if (!gst_hls_sink_create_elements (sink)) {
352         return GST_STATE_CHANGE_FAILURE;
353       }
354       break;
355     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
356       break;
357     default:
358       break;
359   }
360 
361   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
362 
363   switch (trans) {
364     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
365       break;
366     case GST_STATE_CHANGE_PAUSED_TO_READY:
367       /* drain playlist with #EXT-X-ENDLIST */
368       if (sink->playlist && (sink->state & GST_M3U8_PLAYLIST_RENDER_STARTED) &&
369           !(sink->state & GST_M3U8_PLAYLIST_RENDER_ENDED)) {
370         sink->playlist->end_list = TRUE;
371         gst_hls_sink_write_playlist (sink);
372       }
373       gst_hls_sink_reset (sink);
374       break;
375     case GST_STATE_CHANGE_READY_TO_NULL:
376       gst_hls_sink_reset (sink);
377       break;
378     default:
379       break;
380   }
381 
382   return ret;
383 }
384 
385 static void
gst_hls_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)386 gst_hls_sink_set_property (GObject * object, guint prop_id,
387     const GValue * value, GParamSpec * pspec)
388 {
389   GstHlsSink *sink = GST_HLS_SINK_CAST (object);
390 
391   switch (prop_id) {
392     case PROP_LOCATION:
393       g_free (sink->location);
394       sink->location = g_value_dup_string (value);
395       if (sink->multifilesink)
396         g_object_set (sink->multifilesink, "location", sink->location, NULL);
397       break;
398     case PROP_PLAYLIST_LOCATION:
399       g_free (sink->playlist_location);
400       sink->playlist_location = g_value_dup_string (value);
401       break;
402     case PROP_PLAYLIST_ROOT:
403       g_free (sink->playlist_root);
404       sink->playlist_root = g_value_dup_string (value);
405       break;
406     case PROP_MAX_FILES:
407       sink->max_files = g_value_get_uint (value);
408       if (sink->multifilesink) {
409         g_object_set (sink->multifilesink, "location", sink->location,
410             "next-file", 3, "post-messages", TRUE, "max-files", sink->max_files,
411             NULL);
412       }
413       break;
414     case PROP_TARGET_DURATION:
415       sink->target_duration = g_value_get_uint (value);
416       break;
417     case PROP_PLAYLIST_LENGTH:
418       sink->playlist_length = g_value_get_uint (value);
419       sink->playlist->window_size = sink->playlist_length;
420       break;
421     default:
422       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
423       break;
424   }
425 }
426 
427 static void
gst_hls_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)428 gst_hls_sink_get_property (GObject * object, guint prop_id,
429     GValue * value, GParamSpec * pspec)
430 {
431   GstHlsSink *sink = GST_HLS_SINK_CAST (object);
432 
433   switch (prop_id) {
434     case PROP_LOCATION:
435       g_value_set_string (value, sink->location);
436       break;
437     case PROP_PLAYLIST_LOCATION:
438       g_value_set_string (value, sink->playlist_location);
439       break;
440     case PROP_PLAYLIST_ROOT:
441       g_value_set_string (value, sink->playlist_root);
442       break;
443     case PROP_MAX_FILES:
444       g_value_set_uint (value, sink->max_files);
445       break;
446     case PROP_TARGET_DURATION:
447       g_value_set_uint (value, sink->target_duration);
448       break;
449     case PROP_PLAYLIST_LENGTH:
450       g_value_set_uint (value, sink->playlist_length);
451       break;
452     default:
453       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
454       break;
455   }
456 }
457 
458 static GstPadProbeReturn
gst_hls_sink_ghost_event_probe(GstPad * pad,GstPadProbeInfo * info,gpointer data)459 gst_hls_sink_ghost_event_probe (GstPad * pad, GstPadProbeInfo * info,
460     gpointer data)
461 {
462   GstHlsSink *sink = GST_HLS_SINK_CAST (data);
463   GstEvent *event = gst_pad_probe_info_get_event (info);
464 
465   switch (GST_EVENT_TYPE (event)) {
466     case GST_EVENT_SEGMENT:
467     {
468       gst_event_copy_segment (event, &sink->segment);
469       break;
470     }
471     case GST_EVENT_FLUSH_STOP:
472       gst_segment_init (&sink->segment, GST_FORMAT_UNDEFINED);
473       break;
474     case GST_EVENT_CUSTOM_DOWNSTREAM:
475     {
476       GstClockTime timestamp;
477       GstClockTime running_time, stream_time;
478       gboolean all_headers;
479       guint count;
480 
481       if (!gst_video_event_is_force_key_unit (event))
482         break;
483 
484       gst_event_replace (&sink->force_key_unit_event, event);
485       gst_video_event_parse_downstream_force_key_unit (event,
486           &timestamp, &stream_time, &running_time, &all_headers, &count);
487       GST_INFO_OBJECT (sink, "setting index %d", count);
488       sink->index = count;
489       break;
490     }
491     default:
492       break;
493   }
494 
495   return GST_PAD_PROBE_OK;
496 }
497 
498 static gboolean
schedule_next_key_unit(GstHlsSink * sink)499 schedule_next_key_unit (GstHlsSink * sink)
500 {
501   gboolean res = TRUE;
502   GstClockTime running_time;
503   GstPad *sinkpad = gst_element_get_static_pad (GST_ELEMENT (sink), "sink");
504 
505   if (sink->target_duration == 0)
506     /* target-duration == 0 means that the app schedules key units itself */
507     goto out;
508 
509   running_time = sink->last_running_time + sink->target_duration * GST_SECOND;
510   GST_INFO_OBJECT (sink, "sending upstream force-key-unit, index %d "
511       "now %" GST_TIME_FORMAT " target %" GST_TIME_FORMAT,
512       sink->index + 1, GST_TIME_ARGS (sink->last_running_time),
513       GST_TIME_ARGS (running_time));
514 
515   if (!(res = gst_pad_push_event (sinkpad,
516               gst_video_event_new_upstream_force_key_unit (running_time,
517                   TRUE, sink->index + 1)))) {
518     GST_ERROR_OBJECT (sink, "Failed to push upstream force key unit event");
519   }
520 
521 out:
522   /* mark as waiting for a fku event if the app schedules them or if we just
523    * successfully scheduled one
524    */
525   sink->waiting_fku = res;
526   gst_object_unref (sinkpad);
527   return res;
528 }
529 
530 static void
gst_hls_sink_check_schedule_next_key_unit(GstHlsSink * sink,GstBuffer * buf)531 gst_hls_sink_check_schedule_next_key_unit (GstHlsSink * sink, GstBuffer * buf)
532 {
533   GstClockTime timestamp;
534 
535   timestamp = GST_BUFFER_TIMESTAMP (buf);
536   if (!GST_CLOCK_TIME_IS_VALID (timestamp))
537     return;
538 
539   sink->last_running_time = gst_segment_to_running_time (&sink->segment,
540       GST_FORMAT_TIME, timestamp);
541   schedule_next_key_unit (sink);
542 }
543 
544 static GstPadProbeReturn
gst_hls_sink_ghost_buffer_probe(GstPad * pad,GstPadProbeInfo * info,gpointer data)545 gst_hls_sink_ghost_buffer_probe (GstPad * pad, GstPadProbeInfo * info,
546     gpointer data)
547 {
548   GstHlsSink *sink = GST_HLS_SINK_CAST (data);
549   GstBuffer *buffer = gst_pad_probe_info_get_buffer (info);
550 
551   if (sink->target_duration == 0 || sink->waiting_fku)
552     return GST_PAD_PROBE_OK;
553 
554   gst_hls_sink_check_schedule_next_key_unit (sink, buffer);
555   return GST_PAD_PROBE_OK;
556 }
557 
558 static GstFlowReturn
gst_hls_sink_chain_list(GstPad * pad,GstObject * parent,GstBufferList * list)559 gst_hls_sink_chain_list (GstPad * pad, GstObject * parent, GstBufferList * list)
560 {
561   guint i, len;
562   GstBuffer *buffer;
563   GstFlowReturn ret;
564   GstHlsSink *sink = GST_HLS_SINK_CAST (parent);
565 
566   if (sink->target_duration == 0 || sink->waiting_fku)
567     return gst_proxy_pad_chain_list_default (pad, parent, list);
568 
569   GST_DEBUG_OBJECT (pad, "chaining each group in list as a merged buffer");
570 
571   len = gst_buffer_list_length (list);
572 
573   ret = GST_FLOW_OK;
574   for (i = 0; i < len; i++) {
575     buffer = gst_buffer_list_get (list, i);
576 
577     if (!sink->waiting_fku)
578       gst_hls_sink_check_schedule_next_key_unit (sink, buffer);
579 
580     ret = gst_pad_chain (pad, gst_buffer_ref (buffer));
581     if (ret != GST_FLOW_OK)
582       break;
583   }
584   gst_buffer_list_unref (list);
585 
586   return ret;
587 }
588