mssmanifest: Keep the stream qualities list sorted by bitrate
[platform/upstream/gstreamer.git] / ext / smoothstreaming / gstmssmanifest.c
1 /* GStreamer
2  * Copyright (C) 2012 Smart TV Alliance
3  *  Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd.
4  *
5  * gstmssmanifest.c:
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <glib.h>
24 #include <string.h>
25 #include <libxml/parser.h>
26 #include <libxml/tree.h>
27
28 /* for parsing h264 codec data */
29 #include <gst/codecparsers/gsth264parser.h>
30
31 #include "gstmssmanifest.h"
32
33 #define DEFAULT_TIMESCALE             10000000
34
35 #define MSS_NODE_STREAM_FRAGMENT      "c"
36 #define MSS_NODE_STREAM_QUALITY       "QualityLevel"
37
38 #define MSS_PROP_BITRATE              "Bitrate"
39 #define MSS_PROP_DURATION             "d"
40 #define MSS_PROP_NUMBER               "n"
41 #define MSS_PROP_STREAM_DURATION      "Duration"
42 #define MSS_PROP_TIME                 "t"
43 #define MSS_PROP_TIMESCALE            "TimeScale"
44 #define MSS_PROP_URL                  "Url"
45
46 /* TODO check if atoi is successful? */
47
48 typedef struct _GstMssStreamFragment
49 {
50   guint number;
51   guint64 time;
52   guint64 duration;
53 } GstMssStreamFragment;
54
55 typedef struct _GstMssStreamQuality
56 {
57   xmlNodePtr xmlnode;
58
59   gchar *bitrate_str;
60   guint64 bitrate;
61 } GstMssStreamQuality;
62
63 struct _GstMssStream
64 {
65   xmlNodePtr xmlnode;
66
67   gint selectedQualityIndex;
68
69   GList *fragments;
70   GList *qualities;
71
72   gchar *url;
73
74   GList *current_fragment;
75   GList *current_quality;
76
77   /* TODO move this to somewhere static */
78   GRegex *regex_bitrate;
79   GRegex *regex_position;
80 };
81
82 struct _GstMssManifest
83 {
84   xmlDocPtr xml;
85   xmlNodePtr xmlrootnode;
86
87   GSList *streams;
88 };
89
90 static gboolean
91 node_has_type (xmlNodePtr node, const gchar * name)
92 {
93   return strcmp ((gchar *) node->name, name) == 0;
94 }
95
96 static GstMssStreamQuality *
97 gst_mss_stream_quality_new (xmlNodePtr node)
98 {
99   GstMssStreamQuality *q = g_slice_new (GstMssStreamQuality);
100
101   q->xmlnode = node;
102   q->bitrate_str = (gchar *) xmlGetProp (node, (xmlChar *) MSS_PROP_BITRATE);
103   q->bitrate = strtoull (q->bitrate_str, NULL, 10);
104
105   return q;
106 }
107
108 static void
109 gst_mss_stream_quality_free (GstMssStreamQuality * quality)
110 {
111   g_return_if_fail (quality != NULL);
112
113   g_free (quality->bitrate_str);
114   g_slice_free (GstMssStreamQuality, quality);
115 }
116
117 static gint
118 compare_bitrate (GstMssStreamQuality * a, GstMssStreamQuality * b)
119 {
120   if (a->bitrate > b->bitrate)
121     return 1;
122   if (a->bitrate < b->bitrate)
123     return -1;
124   return 0;
125
126 }
127
128 static void
129 _gst_mss_stream_init (GstMssStream * stream, xmlNodePtr node)
130 {
131   xmlNodePtr iter;
132   GstMssStreamFragment *previous_fragment = NULL;
133   guint fragment_number = 0;
134   guint fragment_time_accum = 0;
135   GError *gerror = NULL;
136
137   stream->xmlnode = node;
138
139   /* get the base url path generator */
140   stream->url = (gchar *) xmlGetProp (node, (xmlChar *) MSS_PROP_URL);
141
142   for (iter = node->children; iter; iter = iter->next) {
143     if (node_has_type (iter, MSS_NODE_STREAM_FRAGMENT)) {
144       gchar *duration_str;
145       gchar *time_str;
146       gchar *seqnum_str;
147       GstMssStreamFragment *fragment = g_new (GstMssStreamFragment, 1);
148
149       duration_str = (gchar *) xmlGetProp (iter, (xmlChar *) MSS_PROP_DURATION);
150       time_str = (gchar *) xmlGetProp (iter, (xmlChar *) MSS_PROP_TIME);
151       seqnum_str = (gchar *) xmlGetProp (iter, (xmlChar *) MSS_PROP_NUMBER);
152
153       /* use the node's seq number or use the previous + 1 */
154       if (seqnum_str) {
155         fragment->number = atoi (seqnum_str);
156         g_free (seqnum_str);
157       } else {
158         fragment->number = fragment_number;
159       }
160       fragment_number = fragment->number + 1;
161
162       if (time_str) {
163         fragment->time = atoi (time_str);
164         g_free (time_str);
165       } else {
166         fragment->time = fragment_time_accum;
167       }
168
169       /* if we have a previous fragment, means we need to set its duration */
170       if (previous_fragment)
171         previous_fragment->duration = fragment->time - previous_fragment->time;
172
173       if (duration_str) {
174         fragment->duration = atoi (duration_str);
175
176         previous_fragment = NULL;
177         fragment_time_accum += fragment->duration;
178         g_free (duration_str);
179       } else {
180         /* store to set the duration at the next iteration */
181         previous_fragment = fragment;
182       }
183
184       /* we reverse it later */
185       stream->fragments = g_list_prepend (stream->fragments, fragment);
186
187     } else if (node_has_type (iter, MSS_NODE_STREAM_QUALITY)) {
188       GstMssStreamQuality *quality = gst_mss_stream_quality_new (iter);
189       stream->qualities = g_list_prepend (stream->qualities, quality);
190     } else {
191       /* TODO gst log this */
192     }
193   }
194
195   stream->fragments = g_list_reverse (stream->fragments);
196
197   /* order them from smaller to bigger based on bitrates */
198   stream->qualities =
199       g_list_sort (stream->qualities, (GCompareFunc) compare_bitrate);
200
201   stream->current_fragment = stream->fragments;
202   stream->current_quality = stream->qualities;
203
204   stream->regex_bitrate = g_regex_new ("\\{[Bb]itrate\\}", 0, 0, &gerror);
205   stream->regex_position = g_regex_new ("\\{start[ _]time\\}", 0, 0, &gerror);
206 }
207
208 GstMssManifest *
209 gst_mss_manifest_new (const GstBuffer * data)
210 {
211   GstMssManifest *manifest;
212   xmlNodePtr root;
213   xmlNodePtr nodeiter;
214
215   manifest = g_malloc0 (sizeof (GstMssManifest));
216
217   manifest->xml = xmlReadMemory ((const gchar *) GST_BUFFER_DATA (data),
218       GST_BUFFER_SIZE (data), "manifest", NULL, 0);
219   root = manifest->xmlrootnode = xmlDocGetRootElement (manifest->xml);
220
221   for (nodeiter = root->children; nodeiter; nodeiter = nodeiter->next) {
222     if (nodeiter->type == XML_ELEMENT_NODE
223         && (strcmp ((const char *) nodeiter->name, "StreamIndex") == 0)) {
224       GstMssStream *stream = g_new0 (GstMssStream, 1);
225
226       manifest->streams = g_slist_append (manifest->streams, stream);
227       _gst_mss_stream_init (stream, nodeiter);
228     }
229   }
230
231   return manifest;
232 }
233
234 static void
235 gst_mss_stream_free (GstMssStream * stream)
236 {
237   g_list_free_full (stream->fragments, g_free);
238   g_list_free_full (stream->qualities,
239       (GDestroyNotify) gst_mss_stream_quality_free);
240   g_free (stream->url);
241   g_regex_unref (stream->regex_position);
242   g_regex_unref (stream->regex_bitrate);
243   g_free (stream);
244 }
245
246 void
247 gst_mss_manifest_free (GstMssManifest * manifest)
248 {
249   g_return_if_fail (manifest != NULL);
250
251   g_slist_free_full (manifest->streams, (GDestroyNotify) gst_mss_stream_free);
252
253   xmlFreeDoc (manifest->xml);
254   g_free (manifest);
255 }
256
257 GSList *
258 gst_mss_manifest_get_streams (GstMssManifest * manifest)
259 {
260   return manifest->streams;
261 }
262
263 GstMssStreamType
264 gst_mss_stream_get_type (GstMssStream * stream)
265 {
266   gchar *prop = (gchar *) xmlGetProp (stream->xmlnode, (xmlChar *) "Type");
267   GstMssStreamType ret = MSS_STREAM_TYPE_UNKNOWN;
268
269   if (strcmp (prop, "video") == 0) {
270     ret = MSS_STREAM_TYPE_VIDEO;
271   } else if (strcmp (prop, "audio") == 0) {
272     ret = MSS_STREAM_TYPE_AUDIO;
273   }
274   xmlFree (prop);
275   return ret;
276 }
277
278 static GstCaps *
279 _gst_mss_stream_video_caps_from_fourcc (gchar * fourcc)
280 {
281   if (!fourcc)
282     return NULL;
283
284   if (strcmp (fourcc, "H264") == 0 || strcmp (fourcc, "AVC1") == 0) {
285     return gst_caps_new_simple ("video/x-h264", "stream-format", G_TYPE_STRING,
286         "avc", NULL);
287   } else if (strcmp (fourcc, "WVC1") == 0) {
288     return gst_caps_new_simple ("video/x-wmv", "wmvversion", G_TYPE_INT, 3,
289         NULL);
290   }
291   return NULL;
292 }
293
294 static GstCaps *
295 _gst_mss_stream_audio_caps_from_fourcc (gchar * fourcc)
296 {
297   if (!fourcc)
298     return NULL;
299
300   if (strcmp (fourcc, "AACL") == 0) {
301     return gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 4,
302         NULL);
303   } else if (strcmp (fourcc, "WmaPro") == 0) {
304     return gst_caps_new_simple ("audio/x-wma", "wmaversion", G_TYPE_INT, 2,
305         NULL);
306   }
307   return NULL;
308 }
309
310 /* copied and adapted from h264parse */
311 static GstBuffer *
312 _make_h264_codec_data (GstBuffer * sps, GstBuffer * pps)
313 {
314   GstBuffer *buf;
315   gint sps_size = 0, pps_size = 0, num_sps = 0, num_pps = 0;
316   guint8 profile_idc = 0, profile_comp = 0, level_idc = 0;
317   guint8 *data;
318   gint nl;
319
320   sps_size += GST_BUFFER_SIZE (sps) + 2;
321   profile_idc = GST_BUFFER_DATA (sps)[1];
322   profile_comp = GST_BUFFER_DATA (sps)[2];
323   level_idc = GST_BUFFER_DATA (sps)[3];
324   num_sps = 1;
325
326   pps_size += GST_BUFFER_SIZE (pps) + 2;
327   num_pps = 1;
328
329   buf = gst_buffer_new_and_alloc (5 + 1 + sps_size + 1 + pps_size);
330   data = GST_BUFFER_DATA (buf);
331   nl = 4;
332
333   data[0] = 1;                  /* AVC Decoder Configuration Record ver. 1 */
334   data[1] = profile_idc;        /* profile_idc                             */
335   data[2] = profile_comp;       /* profile_compability                     */
336   data[3] = level_idc;          /* level_idc                               */
337   data[4] = 0xfc | (nl - 1);    /* nal_length_size_minus1                  */
338   data[5] = 0xe0 | num_sps;     /* number of SPSs */
339
340   data += 6;
341   GST_WRITE_UINT16_BE (data, GST_BUFFER_SIZE (sps));
342   memcpy (data + 2, GST_BUFFER_DATA (sps), GST_BUFFER_SIZE (sps));
343   data += 2 + GST_BUFFER_SIZE (sps);
344
345   data[0] = num_pps;
346   data++;
347   GST_WRITE_UINT16_BE (data, GST_BUFFER_SIZE (pps));
348   memcpy (data + 2, GST_BUFFER_DATA (pps), GST_BUFFER_SIZE (pps));
349   data += 2 + GST_BUFFER_SIZE (pps);
350
351   return buf;
352 }
353
354 static void
355 _gst_mss_stream_add_h264_codec_data (GstCaps * caps, const gchar * codecdatastr)
356 {
357   GValue sps_value = { 0, };
358   GValue pps_value = { 0, };
359   GstBuffer *sps;
360   GstBuffer *pps;
361   GstBuffer *buffer;
362   gchar *sps_str;
363   gchar *pps_str;
364   GstH264NalUnit nalu;
365   GstH264SPS sps_struct;
366   GstH264ParserResult parseres;
367
368   /* search for the sps start */
369   if (g_str_has_prefix (codecdatastr, "00000001")) {
370     sps_str = (gchar *) codecdatastr + 8;
371   } else {
372     return;                     /* invalid mss codec data */
373   }
374
375   /* search for the pps start */
376   pps_str = g_strstr_len (sps_str, -1, "00000001");
377   if (!pps_str) {
378     return;                     /* invalid mss codec data */
379   }
380
381   g_value_init (&sps_value, GST_TYPE_BUFFER);
382   pps_str[0] = '\0';
383   gst_value_deserialize (&sps_value, sps_str);
384   pps_str[0] = '0';
385
386   g_value_init (&pps_value, GST_TYPE_BUFFER);
387   pps_str = pps_str + 8;
388   gst_value_deserialize (&pps_value, pps_str);
389
390   sps = gst_value_get_buffer (&sps_value);
391   pps = gst_value_get_buffer (&pps_value);
392
393   nalu.ref_idc = (GST_BUFFER_DATA (sps)[0] & 0x60) >> 5;
394   nalu.type = GST_H264_NAL_SPS;
395   nalu.size = GST_BUFFER_SIZE (sps);
396   nalu.data = GST_BUFFER_DATA (sps);
397   nalu.offset = 0;
398   nalu.sc_offset = 0;
399   nalu.valid = TRUE;
400
401   parseres = gst_h264_parse_sps (&nalu, &sps_struct, TRUE);
402   if (parseres == GST_H264_PARSER_OK) {
403     gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION,
404         sps_struct.fps_num, sps_struct.fps_den, NULL);
405   }
406
407   buffer = _make_h264_codec_data (sps, pps);
408   g_value_reset (&sps_value);
409   g_value_reset (&pps_value);
410
411   gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, buffer, NULL);
412   gst_buffer_unref (buffer);
413 }
414
415 static GstCaps *
416 _gst_mss_stream_video_caps_from_qualitylevel_xml (xmlNodePtr node)
417 {
418   GstCaps *caps;
419   GstStructure *structure;
420   gchar *fourcc = (gchar *) xmlGetProp (node, (xmlChar *) "FourCC");
421   gchar *max_width = (gchar *) xmlGetProp (node, (xmlChar *) "MaxWidth");
422   gchar *max_height = (gchar *) xmlGetProp (node, (xmlChar *) "MaxHeight");
423   gchar *codec_data =
424       (gchar *) xmlGetProp (node, (xmlChar *) "CodecPrivateData");
425
426   if (!max_width)
427     max_width = (gchar *) xmlGetProp (node, (xmlChar *) "Width");
428   if (!max_height)
429     max_height = (gchar *) xmlGetProp (node, (xmlChar *) "Height");
430
431   caps = _gst_mss_stream_video_caps_from_fourcc (fourcc);
432   if (!caps)
433     goto end;
434
435   structure = gst_caps_get_structure (caps, 0);
436
437   if (max_width)
438     gst_structure_set (structure, "width", G_TYPE_INT, atoi (max_width), NULL);
439   if (max_height)
440     gst_structure_set (structure, "height", G_TYPE_INT, atoi (max_height),
441         NULL);
442
443   if (codec_data && strlen (codec_data)) {
444     if (strcmp (fourcc, "H264") == 0 || strcmp (fourcc, "AVC1") == 0) {
445       _gst_mss_stream_add_h264_codec_data (caps, codec_data);
446     } else {
447       GValue *value = g_new0 (GValue, 1);
448       g_value_init (value, GST_TYPE_BUFFER);
449       gst_value_deserialize (value, (gchar *) codec_data);
450       gst_structure_take_value (structure, "codec_data", value);
451     }
452   }
453
454 end:
455   g_free (fourcc);
456   g_free (max_width);
457   g_free (max_height);
458   g_free (codec_data);
459
460   return caps;
461 }
462
463 static GstCaps *
464 _gst_mss_stream_audio_caps_from_qualitylevel_xml (xmlNodePtr node)
465 {
466   GstCaps *caps;
467   GstStructure *structure;
468   gchar *fourcc = (gchar *) xmlGetProp (node, (xmlChar *) "FourCC");
469   gchar *channels = (gchar *) xmlGetProp (node, (xmlChar *) "Channels");
470   gchar *rate = (gchar *) xmlGetProp (node, (xmlChar *) "SamplingRate");
471   gchar *codec_data =
472       (gchar *) xmlGetProp (node, (xmlChar *) "CodecPrivateData");
473
474   if (!fourcc)                  /* sometimes the fourcc is omitted, we fallback to the Subtype in the StreamIndex node */
475     fourcc = (gchar *) xmlGetProp (node->parent, (xmlChar *) "Subtype");
476   if (!codec_data)
477     codec_data = (gchar *) xmlGetProp (node, (xmlChar *) "WaveFormatEx");
478
479   caps = _gst_mss_stream_audio_caps_from_fourcc (fourcc);
480   if (!caps)
481     goto end;
482
483   structure = gst_caps_get_structure (caps, 0);
484
485   if (channels)
486     gst_structure_set (structure, "channels", G_TYPE_INT, atoi (channels),
487         NULL);
488   if (rate)
489     gst_structure_set (structure, "rate", G_TYPE_INT, atoi (rate), NULL);
490
491   if (codec_data && strlen (codec_data)) {
492     GValue *value = g_new0 (GValue, 1);
493     g_value_init (value, GST_TYPE_BUFFER);
494     gst_value_deserialize (value, (gchar *) codec_data);
495     gst_structure_take_value (structure, "codec_data", value);
496   }
497
498 end:
499   g_free (fourcc);
500   g_free (channels);
501   g_free (rate);
502   g_free (codec_data);
503
504   return caps;
505 }
506
507 guint64
508 gst_mss_stream_get_timescale (GstMssStream * stream)
509 {
510   gchar *timescale;
511   guint64 ts = DEFAULT_TIMESCALE;
512
513   timescale =
514       (gchar *) xmlGetProp (stream->xmlnode, (xmlChar *) MSS_PROP_TIMESCALE);
515   if (!timescale) {
516     timescale =
517         (gchar *) xmlGetProp (stream->xmlnode->parent,
518         (xmlChar *) MSS_PROP_TIMESCALE);
519   }
520
521   if (timescale) {
522     ts = strtoull (timescale, NULL, 10);
523     g_free (timescale);
524   }
525   return ts;
526 }
527
528 guint64
529 gst_mss_manifest_get_timescale (GstMssManifest * manifest)
530 {
531   gchar *timescale;
532   guint64 ts = DEFAULT_TIMESCALE;
533
534   timescale =
535       (gchar *) xmlGetProp (manifest->xmlrootnode,
536       (xmlChar *) MSS_PROP_TIMESCALE);
537   if (timescale) {
538     ts = strtoull (timescale, NULL, 10);
539     g_free (timescale);
540   }
541   return ts;
542 }
543
544 guint64
545 gst_mss_manifest_get_duration (GstMssManifest * manifest)
546 {
547   gchar *duration;
548   guint64 dur = -1;
549
550   duration =
551       (gchar *) xmlGetProp (manifest->xmlrootnode,
552       (xmlChar *) MSS_PROP_STREAM_DURATION);
553   if (duration) {
554     dur = strtoull (duration, NULL, 10);
555     g_free (duration);
556   }
557   return dur;
558 }
559
560
561 /**
562  * Gets the duration in nanoseconds
563  */
564 GstClockTime
565 gst_mss_manifest_get_gst_duration (GstMssManifest * manifest)
566 {
567   guint64 duration = -1;
568   guint64 timescale;
569   GstClockTime gstdur = GST_CLOCK_TIME_NONE;
570
571   duration = gst_mss_manifest_get_duration (manifest);
572   timescale = gst_mss_manifest_get_timescale (manifest);
573
574   if (duration != -1 && timescale != -1)
575     gstdur =
576         (GstClockTime) gst_util_uint64_scale_round (duration, GST_SECOND,
577         timescale);
578
579   return gstdur;
580 }
581
582 GstCaps *
583 gst_mss_stream_get_caps (GstMssStream * stream)
584 {
585   GstMssStreamType streamtype = gst_mss_stream_get_type (stream);
586   GstMssStreamQuality *qualitylevel = stream->current_quality->data;
587   GstCaps *caps = NULL;
588
589   if (streamtype == MSS_STREAM_TYPE_VIDEO)
590     caps =
591         _gst_mss_stream_video_caps_from_qualitylevel_xml
592         (qualitylevel->xmlnode);
593   else if (streamtype == MSS_STREAM_TYPE_AUDIO)
594     caps =
595         _gst_mss_stream_audio_caps_from_qualitylevel_xml
596         (qualitylevel->xmlnode);
597
598   return caps;
599 }
600
601 GstFlowReturn
602 gst_mss_stream_get_fragment_url (GstMssStream * stream, gchar ** url)
603 {
604   gchar *tmp;
605   gchar *start_time_str;
606   GstMssStreamFragment *fragment;
607   GstMssStreamQuality *quality = stream->current_quality->data;
608
609   if (stream->current_fragment == NULL) /* stream is over */
610     return GST_FLOW_UNEXPECTED;
611
612   fragment = stream->current_fragment->data;
613
614   start_time_str = g_strdup_printf ("%" G_GUINT64_FORMAT, fragment->time);
615
616   tmp = g_regex_replace_literal (stream->regex_bitrate, stream->url,
617       strlen (stream->url), 0, quality->bitrate_str, 0, NULL);
618   *url = g_regex_replace_literal (stream->regex_position, tmp,
619       strlen (tmp), 0, start_time_str, 0, NULL);
620
621   g_free (tmp);
622   g_free (start_time_str);
623   return GST_FLOW_OK;
624 }
625
626 GstClockTime
627 gst_mss_stream_get_fragment_gst_timestamp (GstMssStream * stream)
628 {
629   guint64 time;
630   guint64 timescale;
631   GstMssStreamFragment *fragment;
632
633   if (!stream->current_fragment)
634     return GST_CLOCK_TIME_NONE;
635
636   fragment = stream->current_fragment->data;
637
638   time = fragment->time;
639   timescale = gst_mss_stream_get_timescale (stream);
640   return (GstClockTime) gst_util_uint64_scale_round (time, GST_SECOND,
641       timescale);
642 }
643
644 GstClockTime
645 gst_mss_stream_get_fragment_gst_duration (GstMssStream * stream)
646 {
647   guint64 dur;
648   guint64 timescale;
649   GstMssStreamFragment *fragment;
650
651   if (!stream->current_fragment)
652     return GST_CLOCK_TIME_NONE;
653
654   fragment = stream->current_fragment->data;
655
656   dur = fragment->duration;
657   timescale = gst_mss_stream_get_timescale (stream);
658   return (GstClockTime) gst_util_uint64_scale_round (dur, GST_SECOND,
659       timescale);
660 }
661
662 GstFlowReturn
663 gst_mss_stream_advance_fragment (GstMssStream * stream)
664 {
665   if (stream->current_fragment == NULL)
666     return GST_FLOW_UNEXPECTED;
667
668   stream->current_fragment = g_list_next (stream->current_fragment);
669   if (stream->current_fragment == NULL)
670     return GST_FLOW_UNEXPECTED;
671   return GST_FLOW_OK;
672 }
673
674 const gchar *
675 gst_mss_stream_type_name (GstMssStreamType streamtype)
676 {
677   switch (streamtype) {
678     case MSS_STREAM_TYPE_VIDEO:
679       return "video";
680     case MSS_STREAM_TYPE_AUDIO:
681       return "audio";
682     case MSS_STREAM_TYPE_UNKNOWN:
683     default:
684       return "unknown";
685   }
686 }
687
688 /**
689  * Seeks all streams to the fragment that contains the set time
690  *
691  * @time: time in nanoseconds
692  */
693 gboolean
694 gst_mss_manifest_seek (GstMssManifest * manifest, guint64 time)
695 {
696   gboolean ret = TRUE;
697   GSList *iter;
698
699   for (iter = manifest->streams; iter; iter = g_slist_next (iter)) {
700     ret = gst_mss_stream_seek (iter->data, time) & ret;
701   }
702
703   return ret;
704 }
705
706 /**
707  * Seeks this stream to the fragment that contains the sample at time
708  *
709  * @time: time in nanoseconds
710  */
711 gboolean
712 gst_mss_stream_seek (GstMssStream * stream, guint64 time)
713 {
714   GList *iter;
715   guint64 timescale;
716
717   timescale = gst_mss_stream_get_timescale (stream);
718   time = gst_util_uint64_scale_round (time, timescale, GST_SECOND);
719
720   for (iter = stream->fragments; iter; iter = g_list_next (iter)) {
721     GList *next = g_list_next (iter);
722     if (next) {
723       GstMssStreamFragment *fragment = next->data;
724
725       if (fragment->time > time) {
726         stream->current_fragment = iter;
727         break;
728       }
729     } else {
730       GstMssStreamFragment *fragment = iter->data;
731       if (fragment->time + fragment->duration > time) {
732         stream->current_fragment = iter;
733       } else {
734         stream->current_fragment = NULL;        /* EOS */
735       }
736       break;
737     }
738   }
739
740   return TRUE;
741 }