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