GList * gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint bitrate) { GList *list, *current_variant; GST_M3U8_CLIENT_LOCK (client); current_variant = client->main->current_variant; /* Go to the highest possible bandwidth allowed */ while (GST_M3U8 (current_variant->data)->bandwidth < bitrate) { list = g_list_next (current_variant); if (!list) break; current_variant = list; } while (GST_M3U8 (current_variant->data)->bandwidth > bitrate) { list = g_list_previous (current_variant); if (!list) break; current_variant = list; } GST_M3U8_CLIENT_UNLOCK (client); return current_variant; }
static void do_test_load_main_playlist_variant (const gchar * playlist) { GstM3U8Client *client; GstM3U8 *stream; GList *tmp; client = load_playlist (playlist); assert_equals_int (g_list_length (client->main->lists), 4); /* Audio-Only */ tmp = g_list_first (client->main->lists); stream = GST_M3U8 (tmp->data); assert_equals_int (stream->bandwidth, 65000); assert_equals_int (stream->program_id, 1); assert_equals_string (stream->uri, "http://example.com/audio-only.m3u8"); assert_equals_string (stream->codecs, "\"mp4a.40.5\""); /* Low */ tmp = g_list_next (tmp); stream = GST_M3U8 (tmp->data); assert_equals_int (stream->bandwidth, 128000); assert_equals_int (stream->program_id, 1); assert_equals_string (stream->uri, "http://example.com/low.m3u8"); /* Mid */ tmp = g_list_next (tmp); stream = GST_M3U8 (tmp->data); assert_equals_int (stream->bandwidth, 256000); assert_equals_int (stream->program_id, 1); assert_equals_string (stream->uri, "http://example.com/mid.m3u8"); /* High */ tmp = g_list_next (tmp); stream = GST_M3U8 (tmp->data); assert_equals_int (stream->bandwidth, 768000); assert_equals_int (stream->program_id, 1); assert_equals_string (stream->uri, "http://example.com/hi.m3u8"); /* Check the first playlist is selected */ assert_equals_int (client->current != NULL, TRUE); assert_equals_int (client->current->bandwidth, 128000); gst_m3u8_client_free (client); }
static gboolean gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf) { GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); gchar *playlist = NULL; if (hlsdemux->client) gst_m3u8_client_free (hlsdemux->client); hlsdemux->client = gst_m3u8_client_new (demux->manifest_uri, demux->manifest_base_uri); GST_INFO_OBJECT (demux, "Changed location: %s (base uri: %s)", demux->manifest_uri, GST_STR_NULL (demux->manifest_base_uri)); playlist = gst_hls_src_buf_to_utf8_playlist (buf); if (playlist == NULL) { GST_WARNING_OBJECT (demux, "Error validating first playlist."); return FALSE; } else if (!gst_m3u8_client_update (hlsdemux->client, playlist)) { /* In most cases, this will happen if we set a wrong url in the * source element and we have received the 404 HTML response instead of * the playlist */ GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), (NULL)); return FALSE; } /* If this playlist is a variant playlist, select the first one * and update it */ if (gst_m3u8_client_has_variant_playlist (hlsdemux->client)) { GstM3U8 *child = NULL; GError *err = NULL; if (demux->connection_speed == 0) { GST_M3U8_CLIENT_LOCK (hlsdemux->client); child = hlsdemux->client->main->current_variant->data; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); } else { GList *tmp = gst_m3u8_client_get_playlist_for_bitrate (hlsdemux->client, demux->connection_speed); GST_M3U8_CLIENT_LOCK (hlsdemux->client); hlsdemux->client->main->current_variant = tmp; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); child = GST_M3U8 (tmp->data); } gst_m3u8_client_set_current (hlsdemux->client, child); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (demux, "Could not fetch the child playlist", err); return FALSE; } } return gst_hls_demux_setup_streams (demux); }
static gboolean gst_hls_demux_change_playlist (GstHLSDemux * demux, guint max_bitrate, gboolean * changed) { GList *previous_variant, *current_variant; gint old_bandwidth, new_bandwidth; GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX_CAST (demux); GstAdaptiveDemuxStream *stream; g_return_val_if_fail (adaptive_demux->streams != NULL, FALSE); stream = adaptive_demux->streams->data; previous_variant = demux->client->main->current_variant; current_variant = gst_m3u8_client_get_playlist_for_bitrate (demux->client, max_bitrate); GST_M3U8_CLIENT_LOCK (demux->client); retry_failover_protection: old_bandwidth = GST_M3U8 (previous_variant->data)->bandwidth; new_bandwidth = GST_M3U8 (current_variant->data)->bandwidth; /* Don't do anything else if the playlist is the same */ if (new_bandwidth == old_bandwidth) { GST_M3U8_CLIENT_UNLOCK (demux->client); return TRUE; } demux->client->main->current_variant = current_variant; GST_M3U8_CLIENT_UNLOCK (demux->client); gst_m3u8_client_set_current (demux->client, current_variant->data); GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching" " to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth); if (gst_hls_demux_update_playlist (demux, TRUE, NULL)) { gchar *uri; gchar *main_uri; uri = gst_m3u8_client_get_current_uri (demux->client); main_uri = gst_m3u8_client_get_uri (demux->client); gst_element_post_message (GST_ELEMENT_CAST (demux), gst_message_new_element (GST_OBJECT_CAST (demux), gst_structure_new (GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME, "manifest-uri", G_TYPE_STRING, main_uri, "uri", G_TYPE_STRING, uri, "bitrate", G_TYPE_INT, new_bandwidth, NULL))); g_free (uri); g_free (main_uri); if (changed) *changed = TRUE; stream->discont = TRUE; } else { GList *failover = NULL; GST_INFO_OBJECT (demux, "Unable to update playlist. Switching back"); GST_M3U8_CLIENT_LOCK (demux->client); failover = g_list_previous (current_variant); if (failover && new_bandwidth == GST_M3U8 (failover->data)->bandwidth) { current_variant = failover; goto retry_failover_protection; } demux->client->main->current_variant = previous_variant; GST_M3U8_CLIENT_UNLOCK (demux->client); gst_m3u8_client_set_current (demux->client, previous_variant->data); /* Try a lower bitrate (or stop if we just tried the lowest) */ if (GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == GST_M3U8 (g_list_first (demux->client->main->iframe_lists)->data)-> bandwidth) return FALSE; else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth) return FALSE; else return gst_hls_demux_change_playlist (demux, new_bandwidth - 1, changed); } /* Force typefinding since we might have changed media type */ demux->do_typefind = TRUE; return TRUE; }
static gboolean gst_hls_demux_cache_fragments (GstHLSDemux * demux) { gint i; /* If this playlist is a variant playlist, select the first one * and update it */ if (gst_m3u8_client_has_variant_playlist (demux->client)) { GstM3U8 *child = NULL; if (demux->connection_speed == 0) { GST_M3U8_CLIENT_LOCK (demux->client); child = demux->client->main->current_variant->data; GST_M3U8_CLIENT_UNLOCK (demux->client); } else { GList *tmp = gst_m3u8_client_get_playlist_for_bitrate (demux->client, demux->connection_speed); child = GST_M3U8 (tmp->data); } gst_m3u8_client_set_current (demux->client, child); if (!gst_hls_demux_update_playlist (demux, FALSE)) { GST_ERROR_OBJECT (demux, "Could not fetch the child playlist %s", child->uri); return FALSE; } } if (!gst_m3u8_client_is_live (demux->client)) { GstClockTime duration = gst_m3u8_client_get_duration (demux->client); GST_DEBUG_OBJECT (demux, "Sending duration message : %" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); if (duration != GST_CLOCK_TIME_NONE) gst_element_post_message (GST_ELEMENT (demux), gst_message_new_duration (GST_OBJECT (demux), GST_FORMAT_TIME, duration)); } /* Cache the first fragments */ for (i = 0; i < demux->fragments_cache; i++) { gst_element_post_message (GST_ELEMENT (demux), gst_message_new_buffering (GST_OBJECT (demux), 100 * i / demux->fragments_cache)); g_get_current_time (&demux->next_update); if (!gst_hls_demux_get_next_fragment (demux, TRUE)) { if (demux->end_of_playlist) break; if (!demux->cancelled) GST_ERROR_OBJECT (demux, "Error caching the first fragments"); return FALSE; } /* make sure we stop caching fragments if something cancelled it */ if (demux->cancelled) return FALSE; gst_hls_demux_switch_playlist (demux); } gst_element_post_message (GST_ELEMENT (demux), gst_message_new_buffering (GST_OBJECT (demux), 100)); g_get_current_time (&demux->next_update); demux->need_cache = FALSE; return TRUE; }
static gboolean gst_hls_demux_change_playlist (GstHLSDemux * demux, guint max_bitrate) { GList *previous_variant, *current_variant; gint old_bandwidth, new_bandwidth; /* If user specifies a connection speed never use a playlist with a bandwidth * superior than it */ if (demux->connection_speed != 0 && max_bitrate > demux->connection_speed) max_bitrate = demux->connection_speed; previous_variant = demux->client->main->current_variant; current_variant = gst_m3u8_client_get_playlist_for_bitrate (demux->client, max_bitrate); retry_failover_protection: old_bandwidth = GST_M3U8 (previous_variant->data)->bandwidth; new_bandwidth = GST_M3U8 (current_variant->data)->bandwidth; /* Don't do anything else if the playlist is the same */ if (new_bandwidth == old_bandwidth) { return TRUE; } demux->client->main->current_variant = current_variant; GST_M3U8_CLIENT_UNLOCK (demux->client); gst_m3u8_client_set_current (demux->client, current_variant->data); GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching" " to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth); if (gst_hls_demux_update_playlist (demux, FALSE)) { GstStructure *s; s = gst_structure_new ("playlist", "uri", G_TYPE_STRING, gst_m3u8_client_get_current_uri (demux->client), "bitrate", G_TYPE_INT, new_bandwidth, NULL); gst_element_post_message (GST_ELEMENT_CAST (demux), gst_message_new_element (GST_OBJECT_CAST (demux), s)); } else { GList *failover = NULL; GST_INFO_OBJECT (demux, "Unable to update playlist. Switching back"); GST_M3U8_CLIENT_LOCK (demux->client); failover = g_list_previous (current_variant); if (failover && new_bandwidth == GST_M3U8 (failover->data)->bandwidth) { current_variant = failover; goto retry_failover_protection; } demux->client->main->current_variant = previous_variant; GST_M3U8_CLIENT_UNLOCK (demux->client); gst_m3u8_client_set_current (demux->client, previous_variant->data); /* Try a lower bitrate (or stop if we just tried the lowest) */ if (new_bandwidth == GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth) return FALSE; else return gst_hls_demux_change_playlist (demux, new_bandwidth - 1); } /* Force typefinding since we might have changed media type */ demux->do_typefind = TRUE; return TRUE; }
/* * @data: a m3u8 playlist text data, taking ownership */ static gboolean gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) { gint val; GstClockTime duration; gchar *title, *end; // gboolean discontinuity; GstM3U8 *list; g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (updated != NULL, FALSE); *updated = TRUE; /* check if the data changed since last update */ if (self->last_data && g_str_equal (self->last_data, data)) { GST_DEBUG ("Playlist is the same as previous one"); *updated = FALSE; g_free (data); return TRUE; } if (!g_str_has_prefix (data, "#EXTM3U")) { GST_WARNING ("Data doesn't start with #EXTM3U"); *updated = FALSE; g_free (data); return FALSE; } g_free (self->last_data); self->last_data = data; if (self->files) { g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL); g_list_free (self->files); self->files = NULL; } list = NULL; duration = 0; title = NULL; data += 7; while (TRUE) { end = g_utf8_strchr (data, -1, '\n'); if (end) *end = '\0'; if (data[0] != '#') { gchar *r; if (duration <= 0 && list == NULL) { GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data); goto next_line; } if (!gst_uri_is_valid (data)) { gchar *slash; if (!self->uri) { GST_WARNING ("uri not set, can't build a valid uri"); goto next_line; } slash = g_utf8_strrchr (self->uri, -1, '/'); if (!slash) { GST_WARNING ("Can't build a valid uri"); goto next_line; } *slash = '\0'; data = g_strdup_printf ("%s/%s", self->uri, data); *slash = '/'; } else { data = g_strdup (data); } r = g_utf8_strchr (data, -1, '\r'); if (r) *r = '\0'; if (list != NULL) { if (g_list_find_custom (self->lists, data, (GCompareFunc) _m3u8_compare_uri)) { GST_DEBUG ("Already have a list with this URI"); gst_m3u8_free (list); g_free (data); } else { gst_m3u8_set_uri (list, data); self->lists = g_list_append (self->lists, list); } list = NULL; } else { GstM3U8MediaFile *file; file = gst_m3u8_media_file_new (data, title, duration, self->mediasequence++); duration = 0; title = NULL; self->files = g_list_append (self->files, file); } } else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) { self->endlist = TRUE; } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) { if (int_from_string (data + 15, &data, &val)) self->version = val; } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) { gchar *v, *a; if (list != NULL) { GST_WARNING ("Found a list without a uri..., dropping"); gst_m3u8_free (list); } list = gst_m3u8_new (); data = data + 18; while (data && parse_attributes (&data, &a, &v)) { if (g_str_equal (a, "BANDWIDTH")) { if (!int_from_string (v, NULL, &list->bandwidth)) GST_WARNING ("Error while reading BANDWIDTH"); } else if (g_str_equal (a, "PROGRAM-ID")) { if (!int_from_string (v, NULL, &list->program_id)) GST_WARNING ("Error while reading PROGRAM-ID"); } else if (g_str_equal (a, "CODECS")) { g_free (list->codecs); list->codecs = g_strdup (v); } else if (g_str_equal (a, "RESOLUTION")) { if (!int_from_string (v, &v, &list->width)) GST_WARNING ("Error while reading RESOLUTION width"); if (!v || *v != '=') { GST_WARNING ("Missing height"); } else { v = g_utf8_next_char (v); if (!int_from_string (v, NULL, &list->height)) GST_WARNING ("Error while reading RESOLUTION height"); } } } } else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) { if (int_from_string (data + 22, &data, &val)) self->targetduration = val * GST_SECOND; } else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) { if (int_from_string (data + 22, &data, &val)) self->mediasequence = val; } else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) { /* discontinuity = TRUE; */ } else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) { /* <YYYY-MM-DDThh:mm:ssZ> */ GST_DEBUG ("FIXME parse date"); } else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) { g_free (self->allowcache); self->allowcache = g_strdup (data + 19); } else if (g_str_has_prefix (data, "#EXTINF:")) { gdouble fval; if (!double_from_string (data + 8, &data, &fval)) { GST_WARNING ("Can't read EXTINF duration"); goto next_line; } duration = fval * (gdouble) GST_SECOND; if (duration > self->targetduration) GST_WARNING ("EXTINF duration > TARGETDURATION"); if (!data || *data != ',') goto next_line; data = g_utf8_next_char (data); if (data != end) { g_free (title); title = g_strdup (data); } } else { GST_LOG ("Ignored line: %s", data); } next_line: if (!end) break; data = g_utf8_next_char (end); /* skip \n */ } /* redorder playlists by bitrate */ if (self->lists) { gchar *top_variant_uri = NULL; if (!self->current_variant) top_variant_uri = GST_M3U8 (self->lists->data)->uri; else top_variant_uri = GST_M3U8 (self->current_variant->data)->uri; self->lists = g_list_sort (self->lists, (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate); self->current_variant = g_list_find_custom (self->lists, top_variant_uri, (GCompareFunc) _m3u8_compare_uri); } return TRUE; }