• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
3  * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:element-hlssink2
23  * @title: hlssink2
24  *
25  * HTTP Live Streaming sink/server. Unlike the old hlssink which took a muxed
26  * MPEG-TS stream as input, this element takes elementary audio and video
27  * streams as input and handles the muxing internally. This allows hlssink2
28  * to make better decisions as to when to start a new fragment and also works
29  * better with input streams where there isn't an encoder element upstream
30  * that can generate keyframes on demand as needed.
31  *
32  * This element only writes fragments and a playlist file into a specified
33  * directory, it does not contain an actual HTTP server to serve these files.
34  * Just point an external webserver to the directory with the playlist and
35  * fragment files.
36  *
37  * ## Example launch line
38  * |[
39  * gst-launch-1.0 videotestsrc is-live=true ! x264enc ! h264parse ! hlssink2 max-files=5
40  * ]|
41  *
42  */
43 #ifdef HAVE_CONFIG_H
44 #include "config.h"
45 #endif
46 
47 #include "gsthlselements.h"
48 #include "gsthlssink2.h"
49 #include <gst/pbutils/pbutils.h>
50 #include <gst/video/video.h>
51 #include <glib/gstdio.h>
52 #include <memory.h>
53 
54 
55 GST_DEBUG_CATEGORY_STATIC (gst_hls_sink2_debug);
56 #define GST_CAT_DEFAULT gst_hls_sink2_debug
57 
58 #define DEFAULT_LOCATION "segment%05d.ts"
59 #define DEFAULT_PLAYLIST_LOCATION "playlist.m3u8"
60 #define DEFAULT_PLAYLIST_ROOT NULL
61 #define DEFAULT_MAX_FILES 10
62 #define DEFAULT_TARGET_DURATION 15
63 #define DEFAULT_PLAYLIST_LENGTH 5
64 #define DEFAULT_SEND_KEYFRAME_REQUESTS TRUE
65 
66 #define GST_M3U8_PLAYLIST_VERSION 3
67 
68 enum
69 {
70   PROP_0,
71   PROP_LOCATION,
72   PROP_PLAYLIST_LOCATION,
73   PROP_PLAYLIST_ROOT,
74   PROP_MAX_FILES,
75   PROP_TARGET_DURATION,
76   PROP_PLAYLIST_LENGTH,
77   PROP_SEND_KEYFRAME_REQUESTS,
78 };
79 
80 enum
81 {
82   SIGNAL_GET_PLAYLIST_STREAM,
83   SIGNAL_GET_FRAGMENT_STREAM,
84   SIGNAL_DELETE_FRAGMENT,
85   SIGNAL_LAST
86 };
87 
88 static guint signals[SIGNAL_LAST];
89 
90 static GstStaticPadTemplate video_template = GST_STATIC_PAD_TEMPLATE ("video",
91     GST_PAD_SINK,
92     GST_PAD_REQUEST,
93     GST_STATIC_CAPS_ANY);
94 static GstStaticPadTemplate audio_template = GST_STATIC_PAD_TEMPLATE ("audio",
95     GST_PAD_SINK,
96     GST_PAD_REQUEST,
97     GST_STATIC_CAPS_ANY);
98 
99 #define gst_hls_sink2_parent_class parent_class
100 G_DEFINE_TYPE (GstHlsSink2, gst_hls_sink2, GST_TYPE_BIN);
101 #define _do_init \
102   hls_element_init (plugin); \
103   GST_DEBUG_CATEGORY_INIT (gst_hls_sink2_debug, "hlssink2", 0, "HlsSink2");
104 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (hlssink2, "hlssink2", GST_RANK_NONE,
105     GST_TYPE_HLS_SINK2, _do_init);
106 
107 static void gst_hls_sink2_set_property (GObject * object, guint prop_id,
108     const GValue * value, GParamSpec * spec);
109 static void gst_hls_sink2_get_property (GObject * object, guint prop_id,
110     GValue * value, GParamSpec * spec);
111 static void gst_hls_sink2_handle_message (GstBin * bin, GstMessage * message);
112 static void gst_hls_sink2_reset (GstHlsSink2 * sink);
113 static GstStateChangeReturn
114 gst_hls_sink2_change_state (GstElement * element, GstStateChange trans);
115 static GstPad *gst_hls_sink2_request_new_pad (GstElement * element,
116     GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
117 static void gst_hls_sink2_release_pad (GstElement * element, GstPad * pad);
118 
119 static void
gst_hls_sink2_dispose(GObject * object)120 gst_hls_sink2_dispose (GObject * object)
121 {
122   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
123 
124   G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
125 }
126 
127 static void
gst_hls_sink2_finalize(GObject * object)128 gst_hls_sink2_finalize (GObject * object)
129 {
130   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
131 
132   g_free (sink->location);
133   g_free (sink->playlist_location);
134   g_free (sink->playlist_root);
135   g_free (sink->current_location);
136   if (sink->playlist)
137     gst_m3u8_playlist_free (sink->playlist);
138 
139   g_queue_foreach (&sink->old_locations, (GFunc) g_free, NULL);
140   g_queue_clear (&sink->old_locations);
141 
142   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
143 }
144 
145 /* Default implementations for the signal handlers */
146 static GOutputStream *
gst_hls_sink2_get_playlist_stream(GstHlsSink2 * sink,const gchar * location)147 gst_hls_sink2_get_playlist_stream (GstHlsSink2 * sink, const gchar * location)
148 {
149   GFile *file = g_file_new_for_path (location);
150   GOutputStream *ostream;
151   GError *err = NULL;
152 
153   ostream =
154       G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
155           G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
156   if (!ostream) {
157     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
158         (("Got no output stream for playlist '%s': %s."), location,
159             err->message), (NULL));
160     g_clear_error (&err);
161   }
162 
163   g_object_unref (file);
164 
165   return ostream;
166 }
167 
168 static GOutputStream *
gst_hls_sink2_get_fragment_stream(GstHlsSink2 * sink,const gchar * location)169 gst_hls_sink2_get_fragment_stream (GstHlsSink2 * sink, const gchar * location)
170 {
171   GFile *file = g_file_new_for_path (location);
172   GOutputStream *ostream;
173   GError *err = NULL;
174 
175   ostream =
176       G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
177           G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
178   if (!ostream) {
179     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
180         (("Got no output stream for fragment '%s': %s."), location,
181             err->message), (NULL));
182     g_clear_error (&err);
183   }
184 
185   g_object_unref (file);
186 
187   return ostream;
188 }
189 
190 static void
gst_hls_sink2_class_init(GstHlsSink2Class * klass)191 gst_hls_sink2_class_init (GstHlsSink2Class * klass)
192 {
193   GObjectClass *gobject_class;
194   GstElementClass *element_class;
195   GstBinClass *bin_class;
196 
197   gobject_class = (GObjectClass *) klass;
198   element_class = GST_ELEMENT_CLASS (klass);
199   bin_class = GST_BIN_CLASS (klass);
200 
201   gst_element_class_add_static_pad_template (element_class, &video_template);
202   gst_element_class_add_static_pad_template (element_class, &audio_template);
203 
204   gst_element_class_set_static_metadata (element_class,
205       "HTTP Live Streaming sink", "Sink/Muxer", "HTTP Live Streaming sink",
206       "Alessandro Decina <alessandro.d@gmail.com>, "
207       "Sebastian Dröge <sebastian@centricular.com>");
208 
209   element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_sink2_change_state);
210   element_class->request_new_pad =
211       GST_DEBUG_FUNCPTR (gst_hls_sink2_request_new_pad);
212   element_class->release_pad = GST_DEBUG_FUNCPTR (gst_hls_sink2_release_pad);
213 
214   bin_class->handle_message = gst_hls_sink2_handle_message;
215 
216   gobject_class->dispose = gst_hls_sink2_dispose;
217   gobject_class->finalize = gst_hls_sink2_finalize;
218   gobject_class->set_property = gst_hls_sink2_set_property;
219   gobject_class->get_property = gst_hls_sink2_get_property;
220 
221   g_object_class_install_property (gobject_class, PROP_LOCATION,
222       g_param_spec_string ("location", "File Location",
223           "Location of the file to write", DEFAULT_LOCATION,
224           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
225   g_object_class_install_property (gobject_class, PROP_PLAYLIST_LOCATION,
226       g_param_spec_string ("playlist-location", "Playlist Location",
227           "Location of the playlist to write", DEFAULT_PLAYLIST_LOCATION,
228           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
229   g_object_class_install_property (gobject_class, PROP_PLAYLIST_ROOT,
230       g_param_spec_string ("playlist-root", "Playlist Root",
231           "Location of the playlist to write", DEFAULT_PLAYLIST_ROOT,
232           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
233   g_object_class_install_property (gobject_class, PROP_MAX_FILES,
234       g_param_spec_uint ("max-files", "Max files",
235           "Maximum number of files to keep on disk. Once the maximum is reached,"
236           "old files start to be deleted to make room for new ones.",
237           0, G_MAXUINT, DEFAULT_MAX_FILES,
238           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
239   g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
240       g_param_spec_uint ("target-duration", "Target duration",
241           "The target duration in seconds of a segment/file. "
242           "(0 - disabled, useful for management of segment duration by the "
243           "streaming server)",
244           0, G_MAXUINT, DEFAULT_TARGET_DURATION,
245           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
246   g_object_class_install_property (gobject_class, PROP_PLAYLIST_LENGTH,
247       g_param_spec_uint ("playlist-length", "Playlist length",
248           "Length of HLS playlist. To allow players to conform to section 6.3.3 "
249           "of the HLS specification, this should be at least 3. If set to 0, "
250           "the playlist will be infinite.",
251           0, G_MAXUINT, DEFAULT_PLAYLIST_LENGTH,
252           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
253   g_object_class_install_property (gobject_class, PROP_SEND_KEYFRAME_REQUESTS,
254       g_param_spec_boolean ("send-keyframe-requests", "Send Keyframe Requests",
255           "Send keyframe requests to ensure correct fragmentation. If this is disabled "
256           "then the input must have keyframes in regular intervals",
257           DEFAULT_SEND_KEYFRAME_REQUESTS,
258           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
259 
260   /**
261    * GstHlsSink2::get-playlist-stream:
262    * @sink: the #GstHlsSink2
263    * @location: Location for the playlist file
264    *
265    * Returns: #GOutputStream for writing the playlist file.
266    *
267    * Since: 1.18
268    */
269   signals[SIGNAL_GET_PLAYLIST_STREAM] =
270       g_signal_new ("get-playlist-stream", G_TYPE_FROM_CLASS (klass),
271       G_SIGNAL_RUN_LAST,
272       G_STRUCT_OFFSET (GstHlsSink2Class, get_playlist_stream),
273       g_signal_accumulator_first_wins, NULL, NULL, G_TYPE_OUTPUT_STREAM, 1,
274       G_TYPE_STRING);
275 
276   /**
277    * GstHlsSink2::get-fragment-stream:
278    * @sink: the #GstHlsSink2
279    * @location: Location for the fragment file
280    *
281    * Returns: #GOutputStream for writing the fragment file.
282    *
283    * Since: 1.18
284    */
285   signals[SIGNAL_GET_FRAGMENT_STREAM] =
286       g_signal_new ("get-fragment-stream", G_TYPE_FROM_CLASS (klass),
287       G_SIGNAL_RUN_LAST,
288       G_STRUCT_OFFSET (GstHlsSink2Class, get_fragment_stream),
289       g_signal_accumulator_first_wins, NULL, NULL, G_TYPE_OUTPUT_STREAM, 1,
290       G_TYPE_STRING);
291 
292   /**
293    * GstHlsSink2::delete-fragment:
294    * @sink: the #GstHlsSink2
295    * @location: Location for the fragment file to delete
296    *
297    * Requests deletion of an old fragment file that is not needed anymore.
298    *
299    * Since: 1.18
300    */
301   signals[SIGNAL_DELETE_FRAGMENT] =
302       g_signal_new ("delete-fragment", G_TYPE_FROM_CLASS (klass),
303       G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
304 
305   klass->get_playlist_stream = gst_hls_sink2_get_playlist_stream;
306   klass->get_fragment_stream = gst_hls_sink2_get_fragment_stream;
307 }
308 
309 static gchar *
on_format_location(GstElement * splitmuxsink,guint fragment_id,GstHlsSink2 * sink)310 on_format_location (GstElement * splitmuxsink, guint fragment_id,
311     GstHlsSink2 * sink)
312 {
313   GOutputStream *stream = NULL;
314   gchar *location;
315 
316   location = g_strdup_printf (sink->location, fragment_id);
317   g_signal_emit (sink, signals[SIGNAL_GET_FRAGMENT_STREAM], 0, location,
318       &stream);
319 
320   if (!stream) {
321     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
322         (("Got no output stream for fragment '%s'."), location), (NULL));
323     g_free (sink->current_location);
324     sink->current_location = NULL;
325   } else {
326     g_free (sink->current_location);
327     sink->current_location = g_steal_pointer (&location);
328   }
329   g_object_set (sink->giostreamsink, "stream", stream, NULL);
330 
331   if (stream)
332     g_object_unref (stream);
333 
334   g_free (location);
335 
336   return NULL;
337 }
338 
339 static void
gst_hls_sink2_init(GstHlsSink2 * sink)340 gst_hls_sink2_init (GstHlsSink2 * sink)
341 {
342   GstElement *mux;
343 
344   sink->location = g_strdup (DEFAULT_LOCATION);
345   sink->playlist_location = g_strdup (DEFAULT_PLAYLIST_LOCATION);
346   sink->playlist_root = g_strdup (DEFAULT_PLAYLIST_ROOT);
347   sink->playlist_length = DEFAULT_PLAYLIST_LENGTH;
348   sink->max_files = DEFAULT_MAX_FILES;
349   sink->target_duration = DEFAULT_TARGET_DURATION;
350   sink->send_keyframe_requests = DEFAULT_SEND_KEYFRAME_REQUESTS;
351   g_queue_init (&sink->old_locations);
352 
353   sink->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL);
354   gst_bin_add (GST_BIN (sink), sink->splitmuxsink);
355 
356   sink->giostreamsink = gst_element_factory_make ("giostreamsink", NULL);
357 
358   mux = gst_element_factory_make ("mpegtsmux", NULL);
359   g_object_set (sink->splitmuxsink, "location", NULL, "max-size-time",
360       ((GstClockTime) sink->target_duration * GST_SECOND),
361       "send-keyframe-requests", TRUE, "muxer", mux, "sink", sink->giostreamsink,
362       "reset-muxer", FALSE, NULL);
363 
364   g_signal_connect (sink->splitmuxsink, "format-location",
365       G_CALLBACK (on_format_location), sink);
366 
367   GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
368 
369   gst_hls_sink2_reset (sink);
370 }
371 
372 static void
gst_hls_sink2_reset(GstHlsSink2 * sink)373 gst_hls_sink2_reset (GstHlsSink2 * sink)
374 {
375   sink->index = 0;
376 
377   if (sink->playlist)
378     gst_m3u8_playlist_free (sink->playlist);
379   sink->playlist =
380       gst_m3u8_playlist_new (GST_M3U8_PLAYLIST_VERSION, sink->playlist_length);
381 
382   g_queue_foreach (&sink->old_locations, (GFunc) g_free, NULL);
383   g_queue_clear (&sink->old_locations);
384 
385   sink->state = GST_M3U8_PLAYLIST_RENDER_INIT;
386 }
387 
388 static void
gst_hls_sink2_write_playlist(GstHlsSink2 * sink)389 gst_hls_sink2_write_playlist (GstHlsSink2 * sink)
390 {
391   char *playlist_content;
392   GError *error = NULL;
393   GOutputStream *stream = NULL;
394   gsize bytes_to_write;
395 
396   g_signal_emit (sink, signals[SIGNAL_GET_PLAYLIST_STREAM], 0,
397       sink->playlist_location, &stream);
398   if (!stream) {
399     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
400         (("Got no output stream for playlist '%s'."), sink->playlist_location),
401         (NULL));
402     return;
403   }
404 
405   playlist_content = gst_m3u8_playlist_render (sink->playlist);
406   bytes_to_write = strlen (playlist_content);
407   if (!g_output_stream_write_all (stream, playlist_content, bytes_to_write,
408           NULL, NULL, &error)) {
409     GST_ERROR ("Failed to write playlist: %s", error->message);
410     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
411         (("Failed to write playlist '%s'."), error->message), (NULL));
412     g_error_free (error);
413     error = NULL;
414   }
415 
416   g_free (playlist_content);
417   g_object_unref (stream);
418 }
419 
420 static void
gst_hls_sink2_handle_message(GstBin * bin,GstMessage * message)421 gst_hls_sink2_handle_message (GstBin * bin, GstMessage * message)
422 {
423   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (bin);
424 
425   switch (message->type) {
426     case GST_MESSAGE_ELEMENT:
427     {
428       const GstStructure *s = gst_message_get_structure (message);
429       if (message->src == GST_OBJECT_CAST (sink->splitmuxsink)) {
430         if (gst_structure_has_name (s, "splitmuxsink-fragment-opened")) {
431           gst_structure_get_clock_time (s, "running-time",
432               &sink->current_running_time_start);
433         } else if (gst_structure_has_name (s, "splitmuxsink-fragment-closed")) {
434           GstClockTime running_time;
435           gchar *entry_location;
436 
437           if (!sink->current_location) {
438             GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE, ((NULL)),
439                 ("Fragment closed without knowing its location"));
440             break;
441           }
442 
443           gst_structure_get_clock_time (s, "running-time", &running_time);
444 
445           GST_INFO_OBJECT (sink, "COUNT %d", sink->index);
446           if (sink->playlist_root == NULL) {
447             entry_location = g_path_get_basename (sink->current_location);
448           } else {
449             gchar *name = g_path_get_basename (sink->current_location);
450             entry_location = g_build_filename (sink->playlist_root, name, NULL);
451             g_free (name);
452           }
453 
454           gst_m3u8_playlist_add_entry (sink->playlist, entry_location,
455               NULL, running_time - sink->current_running_time_start,
456               sink->index++, FALSE);
457           g_free (entry_location);
458 
459           gst_hls_sink2_write_playlist (sink);
460           sink->state |= GST_M3U8_PLAYLIST_RENDER_STARTED;
461 
462           g_queue_push_tail (&sink->old_locations,
463               g_strdup (sink->current_location));
464 
465           if (sink->max_files > 0) {
466             while (g_queue_get_length (&sink->old_locations) > sink->max_files) {
467               gchar *old_location = g_queue_pop_head (&sink->old_locations);
468 
469 
470               if (g_signal_has_handler_pending (sink,
471                       signals[SIGNAL_DELETE_FRAGMENT], 0, FALSE)) {
472                 g_signal_emit (sink, signals[SIGNAL_DELETE_FRAGMENT], 0,
473                     old_location);
474               } else {
475                 GFile *file = g_file_new_for_path (old_location);
476                 GError *err = NULL;
477 
478                 if (!g_file_delete (file, NULL, &err)) {
479                   GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
480                       (("Failed to delete fragment file '%s': %s."),
481                           old_location, err->message), (NULL));
482                   g_clear_error (&err);
483                 }
484 
485                 g_object_unref (file);
486               }
487               g_free (old_location);
488             }
489           }
490 
491           g_free (sink->current_location);
492           sink->current_location = NULL;
493         }
494       }
495       break;
496     }
497     case GST_MESSAGE_EOS:{
498       sink->playlist->end_list = TRUE;
499       gst_hls_sink2_write_playlist (sink);
500       sink->state |= GST_M3U8_PLAYLIST_RENDER_ENDED;
501       break;
502     }
503     default:
504       break;
505   }
506 
507   GST_BIN_CLASS (parent_class)->handle_message (bin, message);
508 }
509 
510 static GstPad *
gst_hls_sink2_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * name,const GstCaps * caps)511 gst_hls_sink2_request_new_pad (GstElement * element, GstPadTemplate * templ,
512     const gchar * name, const GstCaps * caps)
513 {
514   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
515   GstPad *pad, *peer;
516   gboolean is_audio;
517 
518   g_return_val_if_fail (strcmp (templ->name_template, "audio") == 0
519       || strcmp (templ->name_template, "video") == 0, NULL);
520   g_return_val_if_fail (strcmp (templ->name_template, "audio") != 0
521       || !sink->audio_sink, NULL);
522   g_return_val_if_fail (strcmp (templ->name_template, "video") != 0
523       || !sink->video_sink, NULL);
524 
525   is_audio = strcmp (templ->name_template, "audio") == 0;
526 
527   peer =
528       gst_element_request_pad_simple (sink->splitmuxsink,
529       is_audio ? "audio_0" : "video");
530   if (!peer)
531     return NULL;
532 
533   pad = gst_ghost_pad_new_from_template (templ->name_template, peer, templ);
534   gst_pad_set_active (pad, TRUE);
535   gst_element_add_pad (element, pad);
536   gst_object_unref (peer);
537 
538   if (is_audio)
539     sink->audio_sink = pad;
540   else
541     sink->video_sink = pad;
542 
543   return pad;
544 }
545 
546 static void
gst_hls_sink2_release_pad(GstElement * element,GstPad * pad)547 gst_hls_sink2_release_pad (GstElement * element, GstPad * pad)
548 {
549   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
550   GstPad *peer;
551 
552   g_return_if_fail (pad == sink->audio_sink || pad == sink->video_sink);
553 
554   peer = gst_ghost_pad_get_target (GST_GHOST_PAD (pad));
555   if (peer) {
556     gst_element_release_request_pad (sink->splitmuxsink, peer);
557     gst_object_unref (peer);
558   }
559 
560   gst_object_ref (pad);
561   gst_element_remove_pad (element, pad);
562   gst_pad_set_active (pad, FALSE);
563   if (pad == sink->audio_sink)
564     sink->audio_sink = NULL;
565   else
566     sink->video_sink = NULL;
567 
568   gst_object_unref (pad);
569 }
570 
571 static GstStateChangeReturn
gst_hls_sink2_change_state(GstElement * element,GstStateChange trans)572 gst_hls_sink2_change_state (GstElement * element, GstStateChange trans)
573 {
574   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
575   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
576 
577   switch (trans) {
578     case GST_STATE_CHANGE_NULL_TO_READY:
579       if (!sink->splitmuxsink) {
580         return GST_STATE_CHANGE_FAILURE;
581       }
582       break;
583     default:
584       break;
585   }
586 
587   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
588 
589   switch (trans) {
590     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
591       break;
592     case GST_STATE_CHANGE_PAUSED_TO_READY:
593       /* drain playlist with #EXT-X-ENDLIST */
594       if (sink->playlist && (sink->state & GST_M3U8_PLAYLIST_RENDER_STARTED) &&
595           !(sink->state & GST_M3U8_PLAYLIST_RENDER_ENDED)) {
596         sink->playlist->end_list = TRUE;
597         gst_hls_sink2_write_playlist (sink);
598       }
599       /* fall-through */
600     case GST_STATE_CHANGE_READY_TO_NULL:
601       gst_hls_sink2_reset (sink);
602       break;
603     default:
604       break;
605   }
606 
607   return ret;
608 }
609 
610 static void
gst_hls_sink2_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)611 gst_hls_sink2_set_property (GObject * object, guint prop_id,
612     const GValue * value, GParamSpec * pspec)
613 {
614   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
615 
616   switch (prop_id) {
617     case PROP_LOCATION:
618       g_free (sink->location);
619       sink->location = g_value_dup_string (value);
620       if (sink->splitmuxsink)
621         g_object_set (sink->splitmuxsink, "location", sink->location, NULL);
622       break;
623     case PROP_PLAYLIST_LOCATION:
624       g_free (sink->playlist_location);
625       sink->playlist_location = g_value_dup_string (value);
626       break;
627     case PROP_PLAYLIST_ROOT:
628       g_free (sink->playlist_root);
629       sink->playlist_root = g_value_dup_string (value);
630       break;
631     case PROP_MAX_FILES:
632       sink->max_files = g_value_get_uint (value);
633       break;
634     case PROP_TARGET_DURATION:
635       sink->target_duration = g_value_get_uint (value);
636       if (sink->splitmuxsink) {
637         g_object_set (sink->splitmuxsink, "max-size-time",
638             ((GstClockTime) sink->target_duration * GST_SECOND), NULL);
639       }
640       break;
641     case PROP_PLAYLIST_LENGTH:
642       sink->playlist_length = g_value_get_uint (value);
643       sink->playlist->window_size = sink->playlist_length;
644       break;
645     case PROP_SEND_KEYFRAME_REQUESTS:
646       sink->send_keyframe_requests = g_value_get_boolean (value);
647       if (sink->splitmuxsink) {
648         g_object_set (sink->splitmuxsink, "send-keyframe-requests",
649             sink->send_keyframe_requests, NULL);
650       }
651       break;
652     default:
653       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
654       break;
655   }
656 }
657 
658 static void
gst_hls_sink2_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)659 gst_hls_sink2_get_property (GObject * object, guint prop_id,
660     GValue * value, GParamSpec * pspec)
661 {
662   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
663 
664   switch (prop_id) {
665     case PROP_LOCATION:
666       g_value_set_string (value, sink->location);
667       break;
668     case PROP_PLAYLIST_LOCATION:
669       g_value_set_string (value, sink->playlist_location);
670       break;
671     case PROP_PLAYLIST_ROOT:
672       g_value_set_string (value, sink->playlist_root);
673       break;
674     case PROP_MAX_FILES:
675       g_value_set_uint (value, sink->max_files);
676       break;
677     case PROP_TARGET_DURATION:
678       g_value_set_uint (value, sink->target_duration);
679       break;
680     case PROP_PLAYLIST_LENGTH:
681       g_value_set_uint (value, sink->playlist_length);
682       break;
683     case PROP_SEND_KEYFRAME_REQUESTS:
684       g_value_set_boolean (value, sink->send_keyframe_requests);
685       break;
686     default:
687       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
688       break;
689   }
690 }
691