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); }
gboolean gst_m3u8_client_get_next_fragment (GstM3U8Client * client, gboolean * discontinuity, const gchar ** uri, GstClockTime * duration, GstClockTime * timestamp) { GList *l; GstM3U8MediaFile *file; g_return_val_if_fail (client != NULL, FALSE); g_return_val_if_fail (client->current != NULL, FALSE); g_return_val_if_fail (discontinuity != NULL, FALSE); GST_M3U8_CLIENT_LOCK (client); GST_DEBUG ("Looking for fragment %d", client->sequence); l = g_list_find_custom (client->current->files, client, (GCompareFunc) _find_next); if (l == NULL) { GST_M3U8_CLIENT_UNLOCK (client); return FALSE; } gst_m3u8_client_get_current_position (client, timestamp); file = GST_M3U8_MEDIA_FILE (l->data); *discontinuity = client->sequence != file->sequence; client->sequence = file->sequence + 1; *uri = file->uri; *duration = file->duration; GST_M3U8_CLIENT_UNLOCK (client); return TRUE; }
static gboolean gst_hls_demux_switch_playlist (GstHLSDemux * demux) { GTimeVal now; GstClockTime diff; gsize size; gint bitrate; GstFragment *fragment = g_queue_peek_tail (demux->queue); GstBuffer *buffer; GST_M3U8_CLIENT_LOCK (demux->client); if (!demux->client->main->lists) { GST_M3U8_CLIENT_UNLOCK (demux->client); return TRUE; } GST_M3U8_CLIENT_UNLOCK (demux->client); /* compare the time when the fragment was downloaded with the time when it was * scheduled */ g_get_current_time (&now); diff = (GST_TIMEVAL_TO_TIME (now) - GST_TIMEVAL_TO_TIME (demux->next_update)); buffer = gst_fragment_get_buffer (fragment); size = gst_buffer_get_size (buffer); bitrate = (size * 8) / ((double) diff / GST_SECOND); GST_DEBUG ("Downloaded %d bytes in %" GST_TIME_FORMAT ". Bitrate is : %d", size, GST_TIME_ARGS (diff), bitrate); gst_buffer_unref (buffer); return gst_hls_demux_change_playlist (demux, bitrate * demux->bitrate_limit); }
static gboolean gst_hls_demux_select_bitrate (GstAdaptiveDemuxStream * stream, guint64 bitrate) { GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX_CAST (stream->demux); GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux); gboolean changed = FALSE; GST_M3U8_CLIENT_LOCK (hlsdemux->client); if (!hlsdemux->client->main->lists) { GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); return FALSE; } GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); /* FIXME: Currently several issues have be found when letting bitrate adaptation * happen using trick modes (such as 'All streams finished without buffers') and * the adaptive algorithm does not properly behave. */ if (demux->segment.rate != 1.0) return FALSE; gst_hls_demux_change_playlist (hlsdemux, bitrate, &changed); if (changed) gst_hls_demux_setup_streams (GST_ADAPTIVE_DEMUX_CAST (hlsdemux)); return changed; }
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 gboolean gst_hls_demux_change_playlist (GstHLSDemux * demux, gboolean is_fast) { GList *list; GstStructure *s; gint new_bandwidth; GST_M3U8_CLIENT_LOCK (demux->client); if (is_fast) list = g_list_next (demux->client->main->current_variant); else list = g_list_previous (demux->client->main->current_variant); /* Don't do anything else if the playlist is the same */ if (!list || list->data == demux->client->current) { GST_M3U8_CLIENT_UNLOCK (demux->client); return TRUE; } demux->client->main->current_variant = list; GST_M3U8_CLIENT_UNLOCK (demux->client); gst_m3u8_client_set_current (demux->client, list->data); GST_M3U8_CLIENT_LOCK (demux->client); new_bandwidth = demux->client->current->bandwidth; GST_M3U8_CLIENT_UNLOCK (demux->client); gst_hls_demux_update_playlist (demux); GST_INFO_OBJECT (demux, "Client is %s, switching to bitrate %d", is_fast ? "fast" : "slow", new_bandwidth); 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)); /* Force typefinding since we might have changed media type */ demux->do_typefind = TRUE; return TRUE; }
gboolean gst_m3u8_client_has_variant_playlist (GstM3U8Client * client) { gboolean ret; g_return_val_if_fail (client != NULL, FALSE); GST_M3U8_CLIENT_LOCK (client); ret = (client->main->lists != NULL); GST_M3U8_CLIENT_UNLOCK (client); return ret; }
const gchar * gst_m3u8_client_get_current_uri (GstM3U8Client * client) { const gchar *uri; g_return_val_if_fail (client != NULL, NULL); GST_M3U8_CLIENT_LOCK (client); uri = client->current->uri; GST_M3U8_CLIENT_UNLOCK (client); return uri; }
GstClockTime gst_m3u8_client_get_target_duration (GstM3U8Client * client) { GstClockTime duration = 0; g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); GST_M3U8_CLIENT_LOCK (client); duration = client->current->targetduration; GST_M3U8_CLIENT_UNLOCK (client); return duration; }
void gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8) { g_return_if_fail (self != NULL); GST_M3U8_CLIENT_LOCK (self); if (m3u8 != self->current) { self->current = m3u8; self->update_failed_count = 0; } GST_M3U8_CLIENT_UNLOCK (self); }
gboolean gst_m3u8_client_is_live (GstM3U8Client * client) { gboolean ret; g_return_val_if_fail (client != NULL, FALSE); GST_M3U8_CLIENT_LOCK (client); if (!client->current || client->current->endlist) ret = FALSE; else ret = TRUE; GST_M3U8_CLIENT_UNLOCK (client); return ret; }
static gboolean gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update) { GstFragment *download; GstBuffer *buf; gchar *playlist; gboolean updated = FALSE; const gchar *uri = gst_m3u8_client_get_current_uri (demux->client); download = gst_uri_downloader_fetch_uri (demux->downloader, uri); if (download == NULL) return FALSE; buf = gst_fragment_get_buffer (download); playlist = gst_hls_src_buf_to_utf8_playlist (buf); g_object_unref (download); if (playlist == NULL) { GST_WARNING_OBJECT (demux, "Couldn't not validate playlist encoding"); return FALSE; } updated = gst_m3u8_client_update (demux->client, playlist); /* If it's a live source, do not let the sequence number go beyond * three fragments before the end of the list */ if (updated && update == FALSE && demux->client->current && gst_m3u8_client_is_live (demux->client)) { guint last_sequence; GST_M3U8_CLIENT_LOCK (demux->client); last_sequence = GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current-> files)->data)->sequence; if (demux->client->sequence >= last_sequence - 3) { GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %d", last_sequence - 3); demux->need_segment = TRUE; demux->client->sequence = last_sequence - 3; } GST_M3U8_CLIENT_UNLOCK (demux->client); } return updated; }
GstClockTime gst_m3u8_client_get_duration (GstM3U8Client * client) { GstClockTime duration = 0; g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); GST_M3U8_CLIENT_LOCK (client); /* We can only get the duration for on-demand streams */ if (!client->current->endlist) { GST_M3U8_CLIENT_UNLOCK (client); return GST_CLOCK_TIME_NONE; } g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration); GST_M3U8_CLIENT_UNLOCK (client); return duration; }
static gboolean gst_hls_demux_switch_playlist (GstHLSDemux * demux) { GTimeVal now; gint64 diff, limit; g_get_current_time (&now); GST_M3U8_CLIENT_LOCK (demux->client); if (!demux->client->main->lists) { GST_M3U8_CLIENT_UNLOCK (demux->client); return TRUE; } GST_M3U8_CLIENT_UNLOCK (demux->client); /* compare the time when the fragment was downloaded with the time when it was * scheduled */ diff = (GST_TIMEVAL_TO_TIME (demux->next_update) - GST_TIMEVAL_TO_TIME (now)); limit = gst_m3u8_client_get_target_duration (demux->client) * demux->bitrate_switch_tol; GST_DEBUG ("diff:%s%" GST_TIME_FORMAT ", limit:%" GST_TIME_FORMAT, diff < 0 ? "-" : " ", GST_TIME_ARGS (ABS (diff)), GST_TIME_ARGS (limit)); /* if we are on time switch to a higher bitrate */ if (diff > limit) { while (diff > limit) { gst_hls_demux_change_playlist (demux, TRUE); diff -= limit; } demux->accumulated_delay = 0; } else if (diff < 0) { /* if the client is too slow wait until it has accumulated a certain delay to * switch to a lower bitrate */ demux->accumulated_delay -= diff; if (demux->accumulated_delay >= limit) { while (demux->accumulated_delay >= limit) { gst_hls_demux_change_playlist (demux, FALSE); demux->accumulated_delay -= limit; } demux->accumulated_delay = 0; } } return TRUE; }
gboolean gst_m3u8_client_update (GstM3U8Client * self, gchar * data) { GstM3U8 *m3u8; gboolean updated = FALSE; gboolean ret = FALSE; g_return_val_if_fail (self != NULL, FALSE); GST_M3U8_CLIENT_LOCK (self); m3u8 = self->current ? self->current : self->main; if (!gst_m3u8_update (m3u8, data, &updated)) goto out; if (!updated) { self->update_failed_count++; goto out; } /* select the first playlist, for now */ if (!self->current) { if (self->main->lists) { self->current = self->main->current_variant->data; } else { self->current = self->main; } } if (m3u8->files && self->sequence == -1) { self->sequence = GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence; GST_DEBUG ("Setting first sequence at %d", self->sequence); } ret = TRUE; out: GST_M3U8_CLIENT_UNLOCK (self); return ret; }
static gboolean gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) { GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); GstFormat format; GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 start, stop; gdouble rate; GList *walk, *current_file = NULL; GstClockTime current_pos, target_pos; gint64 current_sequence; GstM3U8MediaFile *file; guint64 bitrate; gboolean snap_before, snap_after, snap_nearest, keyunit; gboolean reverse; gst_event_parse_seek (seek, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); bitrate = gst_hls_demux_get_bitrate (hlsdemux); /* properly cleanup pending decryption status */ if (flags & GST_SEEK_FLAG_FLUSH) { gst_hls_demux_clear_pending_data (hlsdemux); } /* Use I-frame variants for trick modes */ if (hlsdemux->client->main->iframe_lists && rate < -1.0 && demux->segment.rate >= -1.0 && demux->segment.rate <= 1.0) { GError *err = NULL; GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to I-frame variant */ hlsdemux->client->main->current_variant = hlsdemux->client->main->iframe_lists; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); gst_m3u8_client_set_current (hlsdemux->client, hlsdemux->client->main->iframe_lists->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } //hlsdemux->discont = TRUE; hlsdemux->do_typefind = TRUE; gst_hls_demux_change_playlist (hlsdemux, bitrate / ABS (rate), NULL); } else if (rate > -1.0 && rate <= 1.0 && (demux->segment.rate < -1.0 || demux->segment.rate > 1.0)) { GError *err = NULL; GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to normal variant */ hlsdemux->client->main->current_variant = hlsdemux->client->main->lists; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); gst_m3u8_client_set_current (hlsdemux->client, hlsdemux->client->main->lists->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } //hlsdemux->discont = TRUE; hlsdemux->do_typefind = TRUE; /* TODO why not continue using the same? that was being used up to now? */ gst_hls_demux_change_playlist (hlsdemux, bitrate, NULL); } GST_M3U8_CLIENT_LOCK (hlsdemux->client); file = GST_M3U8_MEDIA_FILE (hlsdemux->client->current->files->data); current_sequence = file->sequence; current_pos = 0; reverse = rate < 0; target_pos = reverse ? stop : start; /* Snap to segment boundary. Improves seek performance on slow machines. */ keyunit = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); snap_nearest = (flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST; snap_before = ! !(flags & GST_SEEK_FLAG_SNAP_BEFORE); snap_after = ! !(flags & GST_SEEK_FLAG_SNAP_AFTER); /* FIXME: Here we need proper discont handling */ for (walk = hlsdemux->client->current->files; walk; walk = walk->next) { file = walk->data; current_sequence = file->sequence; current_file = walk; if ((!reverse && snap_after) || snap_nearest) { if (current_pos >= target_pos) break; if (snap_nearest && target_pos - current_pos < file->duration / 2) break; } else if (reverse && snap_after) { /* check if the next fragment is our target, in this case we want to * start from the previous fragment */ GstClockTime next_pos = current_pos + file->duration; if (next_pos <= target_pos && target_pos < next_pos + file->duration) { break; } } else if (current_pos <= target_pos && target_pos < current_pos + file->duration) { break; } current_pos += file->duration; } if (walk == NULL) { GST_DEBUG_OBJECT (demux, "seeking further than track duration"); current_sequence++; } GST_DEBUG_OBJECT (demux, "seeking to sequence %u", (guint) current_sequence); hlsdemux->reset_pts = TRUE; hlsdemux->client->sequence = current_sequence; hlsdemux->client->current_file = current_file ? current_file : hlsdemux->client->current->files; hlsdemux->client->sequence_position = current_pos; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); /* Play from the end of the current selected segment */ if (reverse && (snap_before || snap_after || snap_nearest)) current_pos += file->duration; if (keyunit || snap_before || snap_after || snap_nearest) { if (!reverse) gst_segment_do_seek (&demux->segment, rate, format, flags, start_type, current_pos, stop_type, stop, NULL); else gst_segment_do_seek (&demux->segment, rate, format, flags, start_type, start, stop_type, current_pos, NULL); } return TRUE; }
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_seek (GstAdaptiveDemux * demux, GstEvent * seek) { GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); GstFormat format; GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 start, stop; gdouble rate; GList *walk, *current_file = NULL; GstClockTime current_pos, target_pos; gint64 current_sequence; GstM3U8MediaFile *file; guint64 bitrate; gst_event_parse_seek (seek, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); bitrate = gst_hls_demux_get_bitrate (hlsdemux); /* properly cleanup pending decryption status */ if (flags & GST_SEEK_FLAG_FLUSH) { gst_hls_demux_decrypt_end (hlsdemux); } /* Use I-frame variants for trick modes */ if (hlsdemux->client->main->iframe_lists && rate < -1.0 && demux->segment.rate >= -1.0 && demux->segment.rate <= 1.0) { GError *err = NULL; GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to I-frame variant */ hlsdemux->client->main->current_variant = hlsdemux->client->main->iframe_lists; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); gst_m3u8_client_set_current (hlsdemux->client, hlsdemux->client->main->iframe_lists->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } //hlsdemux->discont = TRUE; hlsdemux->do_typefind = TRUE; gst_hls_demux_change_playlist (hlsdemux, bitrate / ABS (rate), NULL); } else if (rate > -1.0 && rate <= 1.0 && (demux->segment.rate < -1.0 || demux->segment.rate > 1.0)) { GError *err = NULL; GST_M3U8_CLIENT_LOCK (hlsdemux->client); /* Switch to normal variant */ hlsdemux->client->main->current_variant = hlsdemux->client->main->lists; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); gst_m3u8_client_set_current (hlsdemux->client, hlsdemux->client->main->lists->data); gst_uri_downloader_reset (demux->downloader); if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) { GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err); return FALSE; } //hlsdemux->discont = TRUE; hlsdemux->do_typefind = TRUE; /* TODO why not continue using the same? that was being used up to now? */ gst_hls_demux_change_playlist (hlsdemux, bitrate, NULL); } GST_M3U8_CLIENT_LOCK (hlsdemux->client); file = GST_M3U8_MEDIA_FILE (hlsdemux->client->current->files->data); current_sequence = file->sequence; current_pos = 0; target_pos = rate > 0 ? start : stop; /* FIXME: Here we need proper discont handling */ for (walk = hlsdemux->client->current->files; walk; walk = walk->next) { file = walk->data; current_sequence = file->sequence; current_file = walk; if (current_pos <= target_pos && target_pos < current_pos + file->duration) { break; } current_pos += file->duration; } if (walk == NULL) { GST_DEBUG_OBJECT (demux, "seeking further than track duration"); current_sequence++; } GST_DEBUG_OBJECT (demux, "seeking to sequence %u", (guint) current_sequence); hlsdemux->reset_pts = TRUE; hlsdemux->client->sequence = current_sequence; hlsdemux->client->current_file = current_file ? current_file : hlsdemux->client->current->files; hlsdemux->client->sequence_position = current_pos; GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); return TRUE; }
static gboolean gst_hls_demux_src_event (GstPad * pad, GstEvent * event) { GstHLSDemux *demux; demux = GST_HLS_DEMUX (gst_pad_get_element_private (pad)); switch (event->type) { case GST_EVENT_SEEK: { gdouble rate; GstFormat format; GstSeekFlags flags; GstSeekType start_type, stop_type; gint64 start, stop; GList *walk; GstClockTime current_pos, target_pos; gint current_sequence; GstM3U8MediaFile *file; GST_INFO_OBJECT (demux, "Received GST_EVENT_SEEK"); if (gst_m3u8_client_is_live (demux->client)) { GST_WARNING_OBJECT (demux, "Received seek event for live stream"); return FALSE; } gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); if (format != GST_FORMAT_TIME) return FALSE; GST_DEBUG_OBJECT (demux, "seek event, rate: %f start: %" GST_TIME_FORMAT " stop: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); GST_M3U8_CLIENT_LOCK (demux->client); file = GST_M3U8_MEDIA_FILE (demux->client->current->files->data); current_sequence = file->sequence; current_pos = 0; target_pos = (GstClockTime) start; for (walk = demux->client->current->files; walk; walk = walk->next) { file = walk->data; current_sequence = file->sequence; if (current_pos <= target_pos && target_pos < current_pos + file->duration) { break; } current_pos += file->duration; } GST_M3U8_CLIENT_UNLOCK (demux->client); if (walk == NULL) { GST_WARNING_OBJECT (demux, "Could not find seeked fragment"); return FALSE; } if (flags & GST_SEEK_FLAG_FLUSH) { GST_DEBUG_OBJECT (demux, "sending flush start"); gst_pad_push_event (demux->srcpad, gst_event_new_flush_start ()); } demux->cancelled = TRUE; gst_task_pause (demux->task); g_mutex_lock (demux->fetcher_lock); gst_hls_demux_stop_fetcher_locked (demux, TRUE); g_mutex_unlock (demux->fetcher_lock); gst_hls_demux_stop_update (demux); gst_task_pause (demux->task); /* wait for streaming to finish */ g_static_rec_mutex_lock (&demux->task_lock); demux->need_cache = TRUE; while (!g_queue_is_empty (demux->queue)) { GstBuffer *buf = g_queue_pop_head (demux->queue); gst_buffer_unref (buf); } g_queue_clear (demux->queue); gst_adapter_clear (demux->download); GST_M3U8_CLIENT_LOCK (demux->client); GST_DEBUG_OBJECT (demux, "seeking to sequence %d", current_sequence); demux->client->sequence = current_sequence; gst_m3u8_client_get_current_position (demux->client, &demux->position); demux->position_shift = start - demux->position; demux->need_segment = TRUE; GST_M3U8_CLIENT_UNLOCK (demux->client); if (flags & GST_SEEK_FLAG_FLUSH) { GST_DEBUG_OBJECT (demux, "sending flush stop"); gst_pad_push_event (demux->srcpad, gst_event_new_flush_stop ()); } demux->cancelled = FALSE; gst_task_start (demux->task); g_static_rec_mutex_unlock (&demux->task_lock); return TRUE; } default: break; } return gst_pad_event_default (pad, event); }
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; }
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; GST_M3U8_CLIENT_LOCK (demux->client); child = demux->client->main->current_variant->data; GST_M3U8_CLIENT_UNLOCK (demux->client); gst_m3u8_client_set_current (demux->client, child); if (!gst_hls_demux_update_playlist (demux)) { GST_ERROR_OBJECT (demux, "Could not fetch the child playlist %s", child->uri); return FALSE; } } /* If it's a live source, set the sequence number to the end of the list * and substract the 'fragmets_cache' to start from the last fragment*/ if (gst_m3u8_client_is_live (demux->client)) { GST_M3U8_CLIENT_LOCK (demux->client); demux->client->sequence += g_list_length (demux->client->current->files); if (demux->client->sequence >= demux->fragments_cache) demux->client->sequence -= demux->fragments_cache; else demux->client->sequence = 0; gst_m3u8_client_get_current_position (demux->client, &demux->position); GST_M3U8_CLIENT_UNLOCK (demux->client); } else { 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); g_time_val_add (&demux->next_update, gst_m3u8_client_get_target_duration (demux->client) / GST_SECOND * G_USEC_PER_SEC); if (!gst_hls_demux_get_next_fragment (demux)) { 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_update_playlist (GstHLSDemux * demux, gboolean update, GError ** err) { GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX (demux); GstFragment *download; GstBuffer *buf; gchar *playlist; gboolean main_checked = FALSE, updated = FALSE; gchar *uri, *main_uri; retry: uri = gst_m3u8_client_get_current_uri (demux->client); main_uri = gst_m3u8_client_get_uri (demux->client); download = gst_uri_downloader_fetch_uri (adaptive_demux->downloader, uri, main_uri, TRUE, TRUE, TRUE, err); g_free (main_uri); if (download == NULL) { gchar *base_uri; if (!update || main_checked || !gst_m3u8_client_has_variant_playlist (demux->client)) { g_free (uri); return FALSE; } g_clear_error (err); main_uri = gst_m3u8_client_get_uri (demux->client); GST_INFO_OBJECT (demux, "Updating playlist %s failed, attempt to refresh variant playlist %s", uri, main_uri); download = gst_uri_downloader_fetch_uri (adaptive_demux->downloader, main_uri, NULL, TRUE, TRUE, TRUE, err); g_free (main_uri); if (download == NULL) { g_free (uri); return FALSE; } buf = gst_fragment_get_buffer (download); playlist = gst_hls_src_buf_to_utf8_playlist (buf); gst_buffer_unref (buf); if (playlist == NULL) { GST_WARNING_OBJECT (demux, "Failed to validate variant playlist encoding"); g_free (uri); g_object_unref (download); g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE, "Couldn't validate playlist encoding"); return FALSE; } g_free (uri); if (download->redirect_permanent && download->redirect_uri) { uri = download->redirect_uri; base_uri = NULL; } else { uri = download->uri; base_uri = download->redirect_uri; } if (!gst_m3u8_client_update_variant_playlist (demux->client, playlist, uri, base_uri)) { GST_WARNING_OBJECT (demux, "Failed to update the variant playlist"); g_object_unref (download); g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_FAILED, "Couldn't update playlist"); return FALSE; } g_object_unref (download); main_checked = TRUE; goto retry; } g_free (uri); /* Set the base URI of the playlist to the redirect target if any */ GST_M3U8_CLIENT_LOCK (demux->client); g_free (demux->client->current->uri); g_free (demux->client->current->base_uri); if (download->redirect_permanent && download->redirect_uri) { demux->client->current->uri = g_strdup (download->redirect_uri); demux->client->current->base_uri = NULL; } else { demux->client->current->uri = g_strdup (download->uri); demux->client->current->base_uri = g_strdup (download->redirect_uri); } GST_M3U8_CLIENT_UNLOCK (demux->client); buf = gst_fragment_get_buffer (download); playlist = gst_hls_src_buf_to_utf8_playlist (buf); gst_buffer_unref (buf); g_object_unref (download); if (playlist == NULL) { GST_WARNING_OBJECT (demux, "Couldn't validate playlist encoding"); g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE, "Couldn't validate playlist encoding"); return FALSE; } updated = gst_m3u8_client_update (demux->client, playlist); if (!updated) { GST_WARNING_OBJECT (demux, "Couldn't update playlist"); g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_FAILED, "Couldn't update playlist"); return FALSE; } /* If it's a live source, do not let the sequence number go beyond * three fragments before the end of the list */ if (update == FALSE && demux->client->current && gst_m3u8_client_is_live (demux->client)) { gint64 last_sequence, first_sequence; GST_M3U8_CLIENT_LOCK (demux->client); last_sequence = GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current-> files)->data)->sequence; first_sequence = GST_M3U8_MEDIA_FILE (demux->client->current->files->data)->sequence; GST_DEBUG_OBJECT (demux, "sequence:%" G_GINT64_FORMAT " , first_sequence:%" G_GINT64_FORMAT " , last_sequence:%" G_GINT64_FORMAT, demux->client->sequence, first_sequence, last_sequence); if (demux->client->sequence >= last_sequence - 3) { //demux->need_segment = TRUE; /* Make sure we never go below the minimum sequence number */ demux->client->sequence = MAX (first_sequence, last_sequence - 3); GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %" G_GINT64_FORMAT, demux->client->sequence); } GST_M3U8_CLIENT_UNLOCK (demux->client); } else if (demux->client->current && !gst_m3u8_client_is_live (demux->client)) { GstClockTime current_pos, target_pos; guint sequence = 0; GList *walk; /* Sequence numbers are not guaranteed to be the same in different * playlists, so get the correct fragment here based on the current * position */ GST_M3U8_CLIENT_LOCK (demux->client); /* Valid because hlsdemux only has a single output */ if (GST_ADAPTIVE_DEMUX_CAST (demux)->streams) { GstAdaptiveDemuxStream *stream = GST_ADAPTIVE_DEMUX_CAST (demux)->streams->data; target_pos = stream->segment.position; } else { target_pos = 0; } if (GST_CLOCK_TIME_IS_VALID (demux->client->sequence_position)) { target_pos = MAX (target_pos, demux->client->sequence_position); } GST_LOG_OBJECT (demux, "Looking for sequence position %" GST_TIME_FORMAT " in updated playlist", GST_TIME_ARGS (target_pos)); current_pos = 0; for (walk = demux->client->current->files; walk; walk = walk->next) { GstM3U8MediaFile *file = walk->data; sequence = file->sequence; if (current_pos <= target_pos && target_pos < current_pos + file->duration) { break; } current_pos += file->duration; } /* End of playlist */ if (!walk) sequence++; demux->client->sequence = sequence; demux->client->sequence_position = current_pos; GST_M3U8_CLIENT_UNLOCK (demux->client); } return updated; }
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; }