2022-10-12 18:19:57 +00:00
|
|
|
/* GStreamer
|
|
|
|
Copyright (C) 2022 Jan Schmidt <jan@centricular.com>
|
|
|
|
*
|
|
|
|
* gsthlsdemux-preloader.c:
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Library General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Library General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Library General Public
|
|
|
|
* License along with this library; if not, write to the
|
|
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "gsthlsdemux-preloader.h"
|
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
/* Everything is called from the scheduler thread, including
|
|
|
|
* download handling callbacks */
|
|
|
|
|
2022-10-12 18:19:57 +00:00
|
|
|
GST_DEBUG_CATEGORY_EXTERN (gst_hls_demux2_debug);
|
|
|
|
#define GST_CAT_DEFAULT gst_hls_demux2_debug
|
|
|
|
|
|
|
|
typedef struct _GstHLSDemuxPreloadRequest GstHLSDemuxPreloadRequest;
|
|
|
|
struct _GstHLSDemuxPreloadRequest
|
|
|
|
{
|
|
|
|
GstHLSDemuxPreloader *preloader; /* Parent preloader */
|
|
|
|
GstM3U8PreloadHint *hint;
|
2022-10-13 19:25:28 +00:00
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
/* Incoming download tracking for the resource */
|
2022-10-12 18:19:57 +00:00
|
|
|
DownloadRequest *download_request;
|
2022-10-13 19:25:28 +00:00
|
|
|
gboolean download_is_finished; /* TRUE if the input download request completed / failed */
|
|
|
|
guint64 download_cur_offset; /* offset of the next expected received data */
|
2022-10-14 12:32:56 +00:00
|
|
|
guint64 download_content_length; /* Content length (filled in when response headers arrive) */
|
|
|
|
GstClockTime request_latency; /* original HTTP request to data latency */
|
|
|
|
GstClockTime download_first_data_time; /* Arrival timestamp of the first data in the download chunk */
|
|
|
|
guint64 download_first_data_offset; /* First data byte offset of the download chunk */
|
|
|
|
|
|
|
|
/* Target tracking for the stream download to deliver data blocks to */
|
|
|
|
/* Each active preload only needs one target to output to at a time,
|
|
|
|
* since we only download one segment at a time, and MAP requests are distinct from PART requests,
|
|
|
|
* so 1 preload = 1 download request by the stream */
|
2022-10-13 19:25:28 +00:00
|
|
|
guint64 target_cur_offset; /* offset of the next delivered target data */
|
|
|
|
DownloadRequest *target_request;
|
2022-10-12 18:19:57 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static GstHLSDemuxPreloadRequest *
|
|
|
|
gst_hls_demux_preload_request_new (GstHLSDemuxPreloader * preloader,
|
|
|
|
GstM3U8PreloadHint * hint)
|
|
|
|
{
|
|
|
|
GstHLSDemuxPreloadRequest *req = g_new0 (GstHLSDemuxPreloadRequest, 1);
|
|
|
|
req->preloader = preloader;
|
|
|
|
req->hint = gst_m3u8_preload_hint_ref (hint);
|
2022-10-14 12:32:56 +00:00
|
|
|
req->request_latency = GST_CLOCK_TIME_NONE;
|
|
|
|
req->download_first_data_time = GST_CLOCK_TIME_NONE;
|
|
|
|
req->download_first_data_offset = GST_BUFFER_OFFSET_NONE;
|
2022-10-12 18:19:57 +00:00
|
|
|
|
|
|
|
return req;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_hls_demux_preload_request_free (GstHLSDemuxPreloadRequest * req)
|
|
|
|
{
|
|
|
|
gst_m3u8_preload_hint_unref (req->hint);
|
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
if (req->download_request != NULL) {
|
2022-10-13 19:41:21 +00:00
|
|
|
/* The download request must have been cancelled by the preload helper,
|
|
|
|
* but cancellation is async, so we can't verify */
|
2022-10-13 19:25:28 +00:00
|
|
|
download_request_unref (req->download_request);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req->target_request != NULL) {
|
|
|
|
download_request_unref (req->target_request);
|
|
|
|
}
|
|
|
|
|
2022-10-12 18:19:57 +00:00
|
|
|
g_free (req);
|
|
|
|
};
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_hls_demux_preloader_submit (GstHLSDemuxPreloader * preloader,
|
|
|
|
GstHLSDemuxPreloadRequest * preload_req, const gchar * referrer_uri);
|
2022-10-13 19:25:28 +00:00
|
|
|
static void gst_hls_demux_preloader_release_request (GstHLSDemuxPreloader *
|
|
|
|
preloader, GstHLSDemuxPreloadRequest * preload_req,
|
|
|
|
gboolean cancel_download);
|
2022-10-12 18:19:57 +00:00
|
|
|
|
|
|
|
GstHLSDemuxPreloader *
|
|
|
|
gst_hls_demux_preloader_new (DownloadHelper * download_helper)
|
|
|
|
{
|
|
|
|
GstHLSDemuxPreloader *preloader = g_new0 (GstHLSDemuxPreloader, 1);
|
|
|
|
|
|
|
|
preloader->download_helper = download_helper;
|
|
|
|
preloader->active_preloads = g_ptr_array_new ();
|
|
|
|
|
|
|
|
return preloader;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gst_hls_demux_preloader_free (GstHLSDemuxPreloader * preloader)
|
|
|
|
{
|
|
|
|
gst_hls_demux_preloader_cancel (preloader, M3U8_PRELOAD_HINT_ALL);
|
|
|
|
g_ptr_array_free (preloader->active_preloads, TRUE);
|
|
|
|
g_free (preloader);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gst_hls_demux_preloader_load (GstHLSDemuxPreloader * preloader,
|
|
|
|
GstM3U8PreloadHint * hint, const gchar * referrer_uri)
|
|
|
|
{
|
|
|
|
/* Check if we have an active preload already for this hint */
|
|
|
|
guint idx;
|
|
|
|
for (idx = 0; idx < preloader->active_preloads->len; idx++) {
|
|
|
|
GstHLSDemuxPreloadRequest *req =
|
|
|
|
g_ptr_array_index (preloader->active_preloads, idx);
|
|
|
|
if (hint->hint_type == req->hint->hint_type) {
|
|
|
|
/* We already have an active hint of this type. If this new one is different, cancel
|
|
|
|
* the active preload before starting this one */
|
|
|
|
if (gst_m3u8_preload_hint_equal (hint, req->hint)) {
|
|
|
|
GST_LOG ("Ignoring pre-existing preload of type %d uri: %s, range:%"
|
|
|
|
G_GINT64_FORMAT " size %" G_GINT64_FORMAT, hint->hint_type,
|
|
|
|
hint->uri, hint->offset, hint->size);
|
|
|
|
return; /* Nothing to do */
|
|
|
|
}
|
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
gst_hls_demux_preloader_release_request (preloader, req, TRUE);
|
2022-10-12 18:19:57 +00:00
|
|
|
g_ptr_array_remove_index_fast (preloader->active_preloads, idx);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we get here, then there's no preload of this type. Create one */
|
|
|
|
GstHLSDemuxPreloadRequest *req =
|
|
|
|
gst_hls_demux_preload_request_new (preloader, hint);
|
|
|
|
/* Submit the request */
|
|
|
|
|
|
|
|
if (gst_hls_demux_preloader_submit (preloader, req, referrer_uri)) {
|
|
|
|
g_ptr_array_add (preloader->active_preloads, req);
|
|
|
|
} else {
|
|
|
|
/* Discard failed request */
|
2022-10-13 19:25:28 +00:00
|
|
|
gst_hls_demux_preloader_release_request (preloader, req, TRUE);
|
2022-10-12 18:19:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gst_hls_demux_preloader_cancel (GstHLSDemuxPreloader * preloader,
|
|
|
|
GstM3U8PreloadHintType hint_types)
|
|
|
|
{
|
|
|
|
/* Go through the active downloads and remove/cancel any with the matching type */
|
|
|
|
guint idx;
|
|
|
|
for (idx = 0; idx < preloader->active_preloads->len;) {
|
|
|
|
GstHLSDemuxPreloadRequest *req =
|
|
|
|
g_ptr_array_index (preloader->active_preloads, idx);
|
|
|
|
if (hint_types & req->hint->hint_type) {
|
2022-10-13 19:25:28 +00:00
|
|
|
gst_hls_demux_preloader_release_request (preloader, req, TRUE);
|
2022-10-12 18:19:57 +00:00
|
|
|
g_ptr_array_remove_index_fast (preloader->active_preloads, idx);
|
|
|
|
continue; /* Don't increment idx++, as we just removed an item */
|
|
|
|
}
|
|
|
|
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
/* This function transfers any available data to the target request, and possibly
|
|
|
|
* completes it and removes it from the preload */
|
|
|
|
static void
|
|
|
|
gst_hls_demux_preloader_despatch (GstHLSDemuxPreloadRequest * preload_req,
|
|
|
|
gboolean input_is_finished)
|
|
|
|
{
|
|
|
|
GstHLSDemuxPreloader *preloader = preload_req->preloader;
|
2022-10-14 12:32:56 +00:00
|
|
|
DownloadRequest *download_req = preload_req->download_request;
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
if (input_is_finished)
|
|
|
|
preload_req->download_is_finished = TRUE;
|
|
|
|
else
|
|
|
|
input_is_finished = preload_req->download_is_finished;
|
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
download_request_lock (download_req);
|
|
|
|
|
|
|
|
/* Update timestamp tracking */
|
|
|
|
if (preload_req->request_latency == GST_CLOCK_TIME_NONE) {
|
|
|
|
if (GST_CLOCK_TIME_IS_VALID (download_req->download_request_time) &&
|
|
|
|
GST_CLOCK_TIME_IS_VALID (download_req->download_start_time)) {
|
|
|
|
|
|
|
|
preload_req->request_latency =
|
|
|
|
download_req->download_start_time -
|
|
|
|
download_req->download_request_time;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preload_req->download_first_data_time == GST_CLOCK_TIME_NONE &&
|
|
|
|
download_request_get_bytes_available (download_req) > 0) {
|
|
|
|
/* Got the first data of this download burst */
|
|
|
|
preload_req->download_first_data_time = download_req->download_start_time;
|
|
|
|
preload_req->download_first_data_offset =
|
|
|
|
download_request_get_cur_offset (download_req);
|
|
|
|
}
|
|
|
|
|
|
|
|
download_request_unlock (download_req);
|
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
/* If there is a target request, see if any of our data should be
|
|
|
|
* transferred to it, and if it should be despatched as complete */
|
|
|
|
if (preload_req->target_request != NULL) {
|
|
|
|
gboolean output_is_finished = input_is_finished;
|
|
|
|
gboolean despatch_progress = FALSE;
|
2022-10-14 12:32:56 +00:00
|
|
|
DownloadRequest *target_req = preload_req->target_request;
|
2022-10-13 19:25:28 +00:00
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
download_request_lock (target_req);
|
|
|
|
download_request_lock (download_req);
|
2022-10-13 19:25:28 +00:00
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
DownloadRequestState target_state = download_req->state;
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
/* Transfer the http status code */
|
2022-10-14 12:32:56 +00:00
|
|
|
target_req->status_code = download_req->status_code;
|
2022-10-13 19:25:28 +00:00
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
GstBuffer *target_buf = download_request_take_buffer_range (download_req,
|
2022-10-13 19:25:28 +00:00
|
|
|
preload_req->target_cur_offset,
|
2022-10-14 12:32:56 +00:00
|
|
|
target_req->range_end);
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
if (target_buf != NULL) {
|
|
|
|
/* Deliver data to the target, and update our tracked output position */
|
|
|
|
preload_req->target_cur_offset =
|
|
|
|
GST_BUFFER_OFFSET (target_buf) + gst_buffer_get_size (target_buf);
|
|
|
|
|
|
|
|
GST_LOG ("Adding %" G_GSIZE_FORMAT " bytes at offset %" G_GUINT64_FORMAT
|
|
|
|
" to target download request uri %s range %" G_GINT64_FORMAT " - %"
|
|
|
|
G_GINT64_FORMAT, gst_buffer_get_size (target_buf),
|
2022-10-14 12:32:56 +00:00
|
|
|
GST_BUFFER_OFFSET (target_buf), target_req->uri,
|
|
|
|
target_req->range_start, target_req->range_end);
|
2022-10-13 19:25:28 +00:00
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
download_request_add_buffer (target_req, target_buf);
|
2022-10-13 19:25:28 +00:00
|
|
|
despatch_progress = TRUE; /* Added a buffer, despatch progress callback */
|
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
/* Transfer timing from the input download as best we can, so the receiver can
|
|
|
|
* calculate bitrates. If all preload requests filled one target download,
|
|
|
|
* we could just transfer the timestamps, but to handle the case of an
|
|
|
|
* ongoing chunked connection needs fancier accounting based on the
|
|
|
|
* arrival times of each data burst */
|
|
|
|
if (target_req->download_start_time == GST_CLOCK_TIME_NONE) {
|
|
|
|
if (preload_req->download_first_data_time >
|
|
|
|
preload_req->request_latency) {
|
|
|
|
target_req->download_request_time =
|
|
|
|
preload_req->download_first_data_time -
|
|
|
|
preload_req->request_latency;
|
|
|
|
} else {
|
|
|
|
target_req->download_request_time = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
target_req->download_start_time = preload_req->download_first_data_time;
|
|
|
|
target_req->download_newest_data_time =
|
|
|
|
download_req->download_newest_data_time;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target_req->range_end != -1
|
|
|
|
&& preload_req->target_cur_offset > target_req->range_end) {
|
2022-10-13 19:25:28 +00:00
|
|
|
/* We've delivered all data to satisfy the requested byte range - the target request is complete */
|
|
|
|
if (target_state == DOWNLOAD_REQUEST_STATE_LOADING) {
|
|
|
|
target_state = DOWNLOAD_REQUEST_STATE_COMPLETE;
|
|
|
|
GST_LOG ("target download request uri %s range %" G_GINT64_FORMAT
|
|
|
|
" - %" G_GINT64_FORMAT " is fully satisfied. Completing",
|
2022-10-14 12:32:56 +00:00
|
|
|
target_req->uri, target_req->range_start, target_req->range_end);
|
2022-10-13 19:25:28 +00:00
|
|
|
}
|
2022-10-14 12:32:56 +00:00
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
output_is_finished = TRUE;
|
2022-10-14 12:32:56 +00:00
|
|
|
|
|
|
|
/* If there's unconsumed data left in the input download, then update
|
|
|
|
* our variable that tracks the first data arrival time in in a prorata
|
|
|
|
* fashion (because there's more partial segment data already downloaded
|
|
|
|
* and we need to preserve a reasonable bitrate estimate. If there's no
|
|
|
|
* data, but the connection is continuing, then it's returned to a blocking
|
|
|
|
* read state that'll send more data in the future when
|
|
|
|
* a new live segment becomes available, so reset our variable as if that
|
|
|
|
* download was starting again */
|
|
|
|
guint64 data_avail =
|
|
|
|
download_request_get_bytes_available (download_req);
|
|
|
|
if (data_avail > 0) {
|
|
|
|
/* burst first data offset must have been set by now */
|
|
|
|
g_assert (preload_req->download_first_data_offset !=
|
|
|
|
GST_BUFFER_OFFSET_NONE);
|
|
|
|
|
|
|
|
/* Calculate how long it took to download the data we have output/discarded
|
|
|
|
* based on the average bitrate so far.
|
|
|
|
* time_to_download = total_download_time * consumed_bytes / total_download_bytes */
|
|
|
|
guint64 new_cur_offset =
|
|
|
|
download_request_get_cur_offset (download_req);
|
|
|
|
GstClockTime data_time_offset =
|
|
|
|
gst_util_uint64_scale (download_req->download_newest_data_time -
|
|
|
|
preload_req->download_first_data_time,
|
|
|
|
new_cur_offset - preload_req->download_first_data_offset,
|
|
|
|
new_cur_offset + data_avail -
|
|
|
|
preload_req->download_first_data_offset);
|
|
|
|
|
|
|
|
preload_req->download_first_data_time += data_time_offset;
|
|
|
|
preload_req->download_first_data_offset = new_cur_offset;
|
|
|
|
|
|
|
|
GST_LOG ("Advancing request timing tracking by %" GST_TIMEP_FORMAT
|
|
|
|
" to time %" GST_TIMEP_FORMAT " @ offset %" G_GUINT64_FORMAT,
|
|
|
|
&data_time_offset,
|
|
|
|
&preload_req->download_first_data_time,
|
|
|
|
preload_req->download_first_data_offset);
|
|
|
|
|
|
|
|
/* Say that this target download finished when the first
|
|
|
|
* byte of the remaining data arrived */
|
|
|
|
target_req->download_end_time = preload_req->download_first_data_time;
|
|
|
|
} else {
|
|
|
|
/* Reset the download start time */
|
|
|
|
preload_req->download_first_data_time = GST_CLOCK_TIME_NONE;
|
|
|
|
preload_req->download_first_data_offset = GST_BUFFER_OFFSET_NONE;
|
|
|
|
|
|
|
|
/* Say that this request finished when the most recent data arrived */
|
|
|
|
target_req->download_end_time =
|
|
|
|
download_req->download_newest_data_time;
|
|
|
|
}
|
2022-10-13 19:25:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
if (input_is_finished
|
|
|
|
&& target_req->download_end_time == GST_CLOCK_TIME_NONE) {
|
|
|
|
/* No download end time was set yet - use the input download end time */
|
|
|
|
target_req->download_end_time = download_req->download_end_time;
|
|
|
|
}
|
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
/* Update the target request's state, which may have been adjusted from the
|
|
|
|
* input request's state */
|
2022-10-14 12:32:56 +00:00
|
|
|
target_req->state = target_state;
|
2022-10-13 19:25:28 +00:00
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
if (target_req->headers == NULL && download_req->headers != NULL) {
|
|
|
|
target_req->headers = gst_structure_copy (download_req->headers);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target_req->redirect_uri == NULL && download_req->redirect_uri != NULL) {
|
|
|
|
target_req->redirect_uri = g_strdup (download_req->redirect_uri);
|
|
|
|
target_req->redirect_permanent = download_req->redirect_permanent;
|
|
|
|
}
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
/* We're done with the input download request . */
|
2022-10-14 12:32:56 +00:00
|
|
|
download_request_unlock (download_req);
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
if (output_is_finished) {
|
|
|
|
GST_DEBUG ("Finishing target preload request uri: %s, start: %"
|
2022-10-14 12:32:56 +00:00
|
|
|
G_GINT64_FORMAT " end: %" G_GINT64_FORMAT, target_req->uri,
|
|
|
|
target_req->range_start, target_req->range_end);
|
2022-10-13 19:25:28 +00:00
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
download_request_despatch_completion (target_req);
|
|
|
|
download_request_unlock (target_req);
|
2022-10-13 19:25:28 +00:00
|
|
|
|
2022-10-14 12:32:56 +00:00
|
|
|
download_request_unref (target_req);
|
2022-10-13 19:25:28 +00:00
|
|
|
preload_req->target_request = NULL;
|
|
|
|
} else if (despatch_progress) {
|
2022-10-14 12:32:56 +00:00
|
|
|
download_request_despatch_progress (target_req);
|
2022-10-13 19:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Unlock if the target request didn't get released above */
|
|
|
|
if (preload_req->target_request != NULL) {
|
|
|
|
download_request_unlock (preload_req->target_request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input_is_finished) {
|
2022-10-14 12:32:56 +00:00
|
|
|
if (download_req == NULL
|
|
|
|
|| download_request_get_bytes_available (download_req)
|
2022-10-13 19:25:28 +00:00
|
|
|
== 0) {
|
|
|
|
GstM3U8PreloadHint *hint = preload_req->hint;
|
|
|
|
GST_DEBUG ("Removing finished+drained preload type %d uri: %s, start: %"
|
|
|
|
G_GINT64_FORMAT " size: %" G_GINT64_FORMAT, hint->hint_type,
|
|
|
|
hint->uri, hint->offset, hint->size);
|
|
|
|
|
|
|
|
/* The incoming request is complete and the data is drained. Remove this preload request from the list */
|
|
|
|
g_ptr_array_remove_fast (preloader->active_preloads, preload_req);
|
|
|
|
gst_hls_demux_preloader_release_request (preloader, preload_req, FALSE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-12 18:19:57 +00:00
|
|
|
static void
|
|
|
|
on_download_cancellation (DownloadRequest * request, DownloadRequestState state,
|
|
|
|
GstHLSDemuxPreloadRequest * preload_req)
|
|
|
|
{
|
2022-10-13 19:25:28 +00:00
|
|
|
gst_hls_demux_preloader_despatch (preload_req, TRUE);
|
2022-10-12 18:19:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
on_download_error (DownloadRequest * request, DownloadRequestState state,
|
|
|
|
GstHLSDemuxPreloadRequest * preload_req)
|
|
|
|
{
|
|
|
|
GstM3U8PreloadHint *hint = preload_req->hint;
|
|
|
|
GST_DEBUG ("preload type %d uri: %s download error", hint->hint_type,
|
|
|
|
hint->uri);
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
/* FIXME: Should we attempt to re-request a preload? Should we check if
|
|
|
|
* any part was transferred to the target request already? Should we
|
|
|
|
* attempt to request a byte range with a new start position if we
|
|
|
|
* already despatched data to other requests?
|
|
|
|
*/
|
|
|
|
gst_hls_demux_preloader_despatch (preload_req, TRUE);
|
2022-10-12 18:19:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
on_download_progress (DownloadRequest * request, DownloadRequestState state,
|
|
|
|
GstHLSDemuxPreloadRequest * preload_req)
|
|
|
|
{
|
|
|
|
GstM3U8PreloadHint *hint = preload_req->hint;
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
GST_DEBUG ("preload type %d uri: %s download progress. position %"
|
|
|
|
G_GUINT64_FORMAT " of %" G_GUINT64_FORMAT " bytes", hint->hint_type,
|
|
|
|
hint->uri,
|
|
|
|
preload_req->download_cur_offset +
|
|
|
|
download_request_get_bytes_available (request), request->content_length);
|
|
|
|
preload_req->download_content_length = request->content_length;
|
|
|
|
|
|
|
|
gst_hls_demux_preloader_despatch (preload_req, FALSE);
|
2022-10-12 18:19:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
on_download_complete (DownloadRequest * request, DownloadRequestState state,
|
|
|
|
GstHLSDemuxPreloadRequest * preload_req)
|
|
|
|
{
|
|
|
|
GstM3U8PreloadHint *hint = preload_req->hint;
|
2022-10-13 19:25:28 +00:00
|
|
|
GST_DEBUG ("preload type %d uri: %s download complete. position %"
|
|
|
|
G_GUINT64_FORMAT " of %" G_GUINT64_FORMAT " bytes", hint->hint_type,
|
|
|
|
hint->uri,
|
|
|
|
preload_req->download_cur_offset +
|
|
|
|
download_request_get_bytes_available (request), request->content_length);
|
|
|
|
preload_req->download_content_length = request->content_length;
|
|
|
|
|
|
|
|
gst_hls_demux_preloader_despatch (preload_req, TRUE);
|
2022-10-12 18:19:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_hls_demux_preloader_submit (GstHLSDemuxPreloader * preloader,
|
|
|
|
GstHLSDemuxPreloadRequest * preload_req, const gchar * referrer_uri)
|
|
|
|
{
|
|
|
|
g_assert (preload_req->download_request == NULL);
|
|
|
|
|
|
|
|
DownloadRequest *download_req = download_request_new ();
|
|
|
|
GstM3U8PreloadHint *hint = preload_req->hint;
|
|
|
|
|
|
|
|
/* Configure our download request */
|
|
|
|
gint64 end = RFC8673_LAST_BYTE_POS;
|
|
|
|
if (hint->size > 0) {
|
|
|
|
end = hint->offset + hint->size - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
download_request_set_uri (download_req, hint->uri, hint->offset, end);
|
|
|
|
download_request_set_callbacks (download_req,
|
|
|
|
(DownloadRequestEventCallback) on_download_complete,
|
|
|
|
(DownloadRequestEventCallback) on_download_error,
|
|
|
|
(DownloadRequestEventCallback) on_download_cancellation,
|
|
|
|
(DownloadRequestEventCallback) on_download_progress, preload_req);
|
|
|
|
|
|
|
|
GST_DEBUG ("Submitting preload type %d uri: %s, range:%" G_GINT64_FORMAT
|
|
|
|
" - %" G_GINT64_FORMAT, hint->hint_type, hint->uri, hint->offset, end);
|
|
|
|
|
|
|
|
if (!downloadhelper_submit_request (preloader->download_helper,
|
|
|
|
referrer_uri, DOWNLOAD_FLAG_NONE, download_req, NULL)) {
|
|
|
|
/* Abandon the request */
|
|
|
|
download_request_unref (download_req);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
/* Store the current read offset */
|
|
|
|
preload_req->download_cur_offset = hint->offset;
|
2022-10-12 18:19:57 +00:00
|
|
|
preload_req->download_request = download_req;
|
2022-10-13 19:25:28 +00:00
|
|
|
preload_req->download_is_finished = FALSE;
|
2022-10-12 18:19:57 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2022-10-13 19:25:28 +00:00
|
|
|
gst_hls_demux_preloader_release_request (GstHLSDemuxPreloader * preloader,
|
|
|
|
GstHLSDemuxPreloadRequest * preload_req, gboolean cancel_download)
|
2022-10-12 18:19:57 +00:00
|
|
|
{
|
|
|
|
if (preload_req->download_request) {
|
2022-10-13 19:25:28 +00:00
|
|
|
if (cancel_download) {
|
|
|
|
GstM3U8PreloadHint *hint = preload_req->hint;
|
|
|
|
|
|
|
|
GST_DEBUG ("Cancelling preload type %d uri: %s, range start:%"
|
|
|
|
G_GINT64_FORMAT " size %" G_GINT64_FORMAT, hint->hint_type, hint->uri,
|
|
|
|
hint->offset, hint->size);
|
|
|
|
|
2022-10-13 19:41:21 +00:00
|
|
|
/* We don't want any callbacks to happen after we cancel here */
|
|
|
|
download_request_set_callbacks (preload_req->download_request,
|
|
|
|
NULL, NULL, NULL, NULL, NULL);
|
2022-10-13 19:25:28 +00:00
|
|
|
downloadhelper_cancel_request (preloader->download_helper,
|
|
|
|
preload_req->download_request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gst_hls_demux_preload_request_free (preload_req);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* See if we can satisfy a download request from a preload, and fulfil it if so.
|
|
|
|
* There are several cases:
|
|
|
|
* * The URI and range exactly match one of our preloads -> OK
|
|
|
|
* * The URI matches, and the requested range is a subset of the preload -> OK
|
|
|
|
* * The URI matches, but the requested range is outside what's available in the preload
|
|
|
|
* and can't be provided.
|
|
|
|
*
|
|
|
|
* Within those options, there are sub-possibilities:
|
|
|
|
* * The preload request is ongoing. It might have enough data already to completely provide
|
|
|
|
* the requested range.
|
|
|
|
* * The preload request is ongoing, but has already moved past the requested range (no longer available)
|
|
|
|
* * The preload request is ongoing, will feed data to the target req as it arrives
|
|
|
|
* * The preload request is complete already, so can either provide the requested range or not, but
|
|
|
|
* also needs to mark the target_req as completed once it has passed the required data.
|
|
|
|
*/
|
|
|
|
gboolean
|
|
|
|
gst_hls_demux_preloader_provide_request (GstHLSDemuxPreloader * preloader,
|
|
|
|
DownloadRequest * target_req)
|
|
|
|
{
|
|
|
|
guint idx;
|
|
|
|
for (idx = 0; idx < preloader->active_preloads->len; idx++) {
|
|
|
|
GstHLSDemuxPreloadRequest *preload_req =
|
|
|
|
g_ptr_array_index (preloader->active_preloads, idx);
|
2022-10-12 18:19:57 +00:00
|
|
|
GstM3U8PreloadHint *hint = preload_req->hint;
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
if (!g_str_equal (hint->uri, target_req->uri))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
GST_LOG ("Possible matching preload type %d uri: %s, range start:%"
|
|
|
|
G_GINT64_FORMAT " size %" G_GINT64_FORMAT " (download position %"
|
|
|
|
G_GUINT64_FORMAT ") for req with range %" G_GINT64_FORMAT " to %"
|
|
|
|
G_GINT64_FORMAT, hint->hint_type, hint->uri, hint->offset, hint->size,
|
|
|
|
preload_req->download_cur_offset, target_req->range_start,
|
|
|
|
target_req->range_end);
|
|
|
|
|
|
|
|
if (target_req->range_start > preload_req->download_cur_offset) {
|
|
|
|
/* This preload request is for a byte range beyond the desired
|
|
|
|
* position (or something already consumed the target data) */
|
|
|
|
GST_LOG ("Range start didn't match");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target_req->range_end != -1) {
|
|
|
|
/* The target request does not want the entire rest of the preload
|
|
|
|
* stream, so check that the end is satisfiable */
|
|
|
|
gint64 content_length = preload_req->download_content_length;
|
|
|
|
if (content_length == 0) {
|
|
|
|
/* We don't have information from the preload download's response headers yet,
|
|
|
|
* so check against the requested length and error out later if the server
|
|
|
|
* doesn't provide all the desired response */
|
|
|
|
if (hint->size != -1)
|
|
|
|
content_length = hint->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (content_length != 0) {
|
|
|
|
/* We have some idea of the content length. Check if it will provide the requested
|
|
|
|
* range */
|
|
|
|
if (target_req->range_end > hint->offset + content_length - 1) {
|
|
|
|
GST_LOG ("Range end %" G_GINT64_FORMAT " is beyond the end (%"
|
|
|
|
G_GINT64_FORMAT ") of this preload", target_req->range_end,
|
|
|
|
hint->offset + content_length - 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GST_DEBUG ("Found a matching preload type %d uri: %s, range start:%"
|
2022-10-12 18:19:57 +00:00
|
|
|
G_GINT64_FORMAT " size %" G_GINT64_FORMAT, hint->hint_type, hint->uri,
|
|
|
|
hint->offset, hint->size);
|
|
|
|
|
2022-10-13 19:25:28 +00:00
|
|
|
if (preload_req->target_request != NULL) {
|
|
|
|
DownloadRequest *old_request = preload_req->target_request;
|
|
|
|
|
|
|
|
/* Detach the existing target request */
|
|
|
|
if (old_request != target_req) {
|
|
|
|
download_request_lock (old_request);
|
|
|
|
old_request->state = DOWNLOAD_REQUEST_STATE_UNSENT;
|
|
|
|
download_request_despatch_completion (old_request);
|
|
|
|
download_request_unlock (old_request);
|
|
|
|
}
|
|
|
|
|
|
|
|
download_request_unref (old_request);
|
|
|
|
preload_req->target_request = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Attach the new target request and despatch any available data */
|
|
|
|
preload_req->target_cur_offset = target_req->range_start;
|
|
|
|
preload_req->target_request = download_request_ref (target_req);
|
|
|
|
|
|
|
|
download_request_lock (target_req);
|
|
|
|
target_req->state = DOWNLOAD_REQUEST_STATE_UNSENT;
|
|
|
|
download_request_begin_download (target_req);
|
|
|
|
download_request_unlock (target_req);
|
|
|
|
|
|
|
|
gst_hls_demux_preloader_despatch (preload_req, FALSE);
|
|
|
|
return TRUE;
|
2022-10-12 18:19:57 +00:00
|
|
|
}
|
2022-10-13 19:25:28 +00:00
|
|
|
|
|
|
|
return FALSE;
|
2022-10-12 18:19:57 +00:00
|
|
|
}
|