@@ -17,7 +17,62 @@ Index: ffmpeg-6.0.1/libavformat/hls.c
17
17
===================================================================
18
18
--- ffmpeg-6.0.1.orig/libavformat/hls.c
19
19
+++ ffmpeg-6.0.1/libavformat/hls.c
20
- @@ -673,6 +673,8 @@ static int open_url(AVFormatContext *s,
20
+ --- a/libavformat/hls.c
21
+ +++ b/libavformat/hls.c
22
+ @@ -311,6 +311,36 @@ static void free_rendition_list(HLSContext *c)
23
+ c->n_renditions = 0;
24
+ }
25
+
26
+ + // When the M3U8 contains any absolute URIs - i.e init segment, AES key, or just normal segments,
27
+ + // the jsfetch: protocol needs to be prepended.
28
+ + static int jsfetch_wrap_url(const char *url, char **out_url)
29
+ + {
30
+ + char *url_fixed = NULL;
31
+ +
32
+ + if (av_strstart(url, "http://", NULL) || av_strstart(url, "https://", NULL)) {
33
+ + url_fixed = av_malloc(strlen("jsfetch:") + strlen(url) + 1);
34
+ + if (!url_fixed)
35
+ + return AVERROR(ENOMEM);
36
+ + strcpy(url_fixed, "jsfetch:");
37
+ + strcat(url_fixed, url);
38
+ + } else if (av_strstart(url, "crypto+http://", NULL) || av_strstart(url, "crypto+https://", NULL)) {
39
+ + const char *url_tail = url + strlen("crypto+");
40
+ + url_fixed = av_malloc(strlen("crypto+jsfetch:") + strlen(url_tail) + 1);
41
+ + if (!url_fixed)
42
+ + return AVERROR(ENOMEM);
43
+ + strcpy(url_fixed, "crypto+jsfetch:");
44
+ + strcat(url_fixed, url_tail);
45
+ + } else {
46
+ + // No transformation needed; just duplicate original URL
47
+ + url_fixed = av_strdup(url);
48
+ + if (!url_fixed)
49
+ + return AVERROR(ENOMEM);
50
+ + }
51
+ +
52
+ + *out_url = url_fixed;
53
+ + return 0;
54
+ + }
55
+ +
56
+ static struct playlist *new_playlist(HLSContext *c, const char *url,
57
+ const char *base)
58
+ {
59
+ @@ -438,7 +468,14 @@ static struct segment *new_init_section(struct playlist *pls,
60
+ return NULL;
61
+ }
62
+ }
63
+ - sec->url = av_strdup(ptr);
64
+ + char *wrapped_url = NULL;
65
+ + int ret = jsfetch_wrap_url(ptr, &wrapped_url);
66
+ + if (ret < 0) {
67
+ + sec->url = av_strdup(ptr);
68
+ + fprintf(stderr, "Failed to wrap URL: %s\n", av_err2str(ret));
69
+ + } else {
70
+ + sec->url = av_strdup(wrapped_url);
71
+ + }
72
+ if (!sec->url) {
73
+ av_free(sec);
74
+ return NULL;
75
+ @@ -680,6 +717,8 @@ static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url,
21
76
is_http = 1;
22
77
} else if (av_strstart(proto_name, "data", NULL)) {
23
78
;
@@ -26,11 +81,68 @@ Index: ffmpeg-6.0.1/libavformat/hls.c
26
81
} else
27
82
return AVERROR_INVALIDDATA;
28
83
29
- Index: ffmpeg-6.0.1/libavformat/jsfetch.c
84
+ @@ -981,7 +1020,14 @@ static int parse_playlist(HLSContext *c, const char *url,
85
+ av_free(seg);
86
+ goto fail;
87
+ }
88
+ - seg->url = av_strdup(tmp_str);
89
+ + char *wrapped_url = NULL;
90
+ + int ret = jsfetch_wrap_url(tmp_str, &wrapped_url);
91
+ + if (ret < 0) {
92
+ + seg->url = av_strdup(tmp_str);
93
+ + fprintf(stderr, "Failed to wrap URL: %s\n", av_err2str(ret));
94
+ + } else {
95
+ + seg->url = av_strdup(wrapped_url);
96
+ + }
97
+ if (!seg->url) {
98
+ av_free(seg->key);
99
+ av_free(seg);
100
+ @@ -1294,6 +1340,16 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg,
101
+ * (if this is in fact a HTTP request) */
102
+ av_dict_set_int(&opts, "offset", seg->url_offset, 0);
103
+ av_dict_set_int(&opts, "end_offset", seg->url_offset + seg->size, 0);
104
+ +
105
+ + // Pre-populate the byte range header if needed. Passed to jsfetch in AVDictionary.
106
+ + char range_header[128] = {0};
107
+ + if (seg->url_offset >= 0 && seg->size > 0) {
108
+ + snprintf(range_header, sizeof(range_header), "bytes=%" PRId64 "-%" PRId64, seg->url_offset, seg->url_offset + seg->size - 1);
109
+ + av_dict_set(&opts, "range_header", range_header, 0);
110
+ + } else if (seg->url_offset >= 0) {
111
+ + snprintf(range_header, sizeof(range_header), "bytes=%" PRId64 "-", seg->url_offset);
112
+ + av_dict_set(&opts, "range_header", range_header, 0);
113
+ + }
114
+ }
115
+
116
+ av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', offset %"PRId64", playlist %d\n",
117
+ @@ -1348,14 +1404,15 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg,
118
+ * as would be expected. Wrong offset received from the server will not be
119
+ * noticed without the call, though.
120
+ */
121
+ - if (ret == 0 && !is_http && seg->url_offset) {
122
+ - int64_t seekret = avio_seek(*in, seg->url_offset, SEEK_SET);
123
+ - if (seekret < 0) {
124
+ - av_log(pls->parent, AV_LOG_ERROR, "Unable to seek to offset %"PRId64" of HLS segment '%s'\n", seg->url_offset, seg->url);
125
+ - ret = seekret;
126
+ - ff_format_io_close(pls->parent, in);
127
+ - }
128
+ - }
129
+ + // Breaks seeking on M3U8s containing byte ranges. Not needed for "http" anyways.
130
+ + // if (ret == 0 && !is_http && seg->url_offset) {
131
+ + // int64_t seekret = avio_seek(*in, seg->url_offset, SEEK_SET);
132
+ + // if (seekret < 0) {
133
+ + // av_log(pls->parent, AV_LOG_ERROR, "Unable to seek to offset %"PRId64" of HLS segment '%s'\n", seg->url_offset, seg->url);
134
+ + // ret = seekret;
135
+ + // ff_format_io_close(pls->parent, in);
136
+ + // }
137
+ + // }
138
+
139
+ cleanup:
140
+ av_dict_free(&opts);
141
+ Index: ffmpeg-6.0.1/libavformat/jsfetch.c
30
142
===================================================================
31
143
--- /dev/null
32
144
+++ ffmpeg-6.0.1/libavformat/jsfetch.c
33
- @@ -0,0 +1,188 @@
145
+ @@ -0,0 +1,194 @@
34
146
+ /*
35
147
+ * JavaScript fetch metaprotocol for ffmpeg client
36
148
+ * Copyright (c) 2023 Yahweasel and contributors
@@ -70,7 +182,6 @@ Index: ffmpeg-6.0.1/libavformat/jsfetch.c
70
182
+ static const AVOption options[] = {
71
183
+ { NULL }
72
184
+ };
73
- +
74
185
+ #if CONFIG_JSFETCH_PROTOCOL
75
186
+ static const AVClass jsfetch_context_class = {
76
187
+ .class_name = "jsfetch",
@@ -82,14 +193,17 @@ Index: ffmpeg-6.0.1/libavformat/jsfetch.c
82
193
+ /**
83
194
+ * Open a fetch connection (JavaScript side).
84
195
+ */
85
- + EM_JS(int, jsfetch_open_js, (const char *url), {
196
+ + EM_JS(int, jsfetch_open_js, (const char *url, char* range_header, bool has_range ), {
86
197
+ return Asyncify.handleAsync(function() {
87
198
+ return Promise.all([]).then(function() {
88
199
+ url = UTF8ToString(url);
89
- + if (url.slice(0, 8) === "jsfetch:")
90
- + return fetch(url.slice(8));
91
- + else
92
- + return fetch(url);
200
+ + var headers = {};
201
+ + if (has_range) {
202
+ + var range = range_header ? UTF8ToString(range_header) : undefined;
203
+ + headers.Range = range;
204
+ + }
205
+ + var fetchUrl = url.startsWith("jsfetch:") ? url.slice(8) : url;
206
+ + return fetch(fetchUrl, { headers });
93
207
+ }).then(function(response) {
94
208
+ if (!Module.libavjsJSFetch)
95
209
+ Module.libavjsJSFetch = {ctr: 1, fetches: {}};
@@ -124,7 +238,11 @@ Index: ffmpeg-6.0.1/libavformat/jsfetch.c
124
238
+ {
125
239
+ JSFetchContext *ctx = h->priv_data;
126
240
+ h->is_streamed = 1;
127
- + ctx->idx = jsfetch_open_js(url);
241
+ +
242
+ + AVDictionaryEntry *entry = av_dict_get(*options, "range_header", NULL, 0);
243
+ + const char *range_ptr = entry ? entry->value : NULL;
244
+ + bool has_range = range_ptr != NULL;
245
+ + ctx->idx = jsfetch_open_js(url, range_ptr, has_range);
128
246
+ return (ctx->idx > 0) ? 0 : ctx->idx;
129
247
+ }
130
248
+
@@ -216,7 +334,7 @@ Index: ffmpeg-6.0.1/libavformat/jsfetch.c
216
334
+ .priv_data_size = sizeof(JSFetchContext),
217
335
+ .priv_data_class = &jsfetch_context_class,
218
336
+ .flags = URL_PROTOCOL_FLAG_NETWORK,
219
- + .default_whitelist = "jsfetch,http,https"
337
+ + .default_whitelist = "jsfetch,http,https,crypto "
220
338
+ };
221
339
+ #endif
222
340
Index: ffmpeg-6.0.1/libavformat/protocols.c
@@ -234,3 +352,74 @@ Index: ffmpeg-6.0.1/libavformat/protocols.c
234
352
#include "libavformat/protocol_list.c"
235
353
236
354
const AVClass *ff_urlcontext_child_class_iterate(void **iter)
355
+
356
+ Index: ffmpeg-6.0.1/libavformat/mov.c
357
+ ===================================================================
358
+ From https://trac.ffmpeg.org/ticket/7359
359
+ https://github.com/FFmpeg/FFmpeg/commit/380a518c439d4e5e3cf17b97e4a06259e8048f99
360
+ Note: this patch isn't in 7.1.1.
361
+ --- ffmpeg-6.0.1.orig/libavformat/mov.c
362
+ +++ ffmpeg-6.0.1/libavformat/mov.c
363
+ @@ -10416,15 +10416,15 @@ static int mov_switch_root(AVFormatContext *s, int64_t target, int index)
364
+
365
+ if (index >= 0 && index < mov->frag_index.nb_items)
366
+ target = mov->frag_index.item[index].moof_offset;
367
+ - if (avio_seek(s->pb, target, SEEK_SET) != target) {
368
+ + if (target >= 0 && avio_seek(s->pb, target, SEEK_SET) != target) {
369
+ av_log(mov->fc, AV_LOG_ERROR, "root atom offset 0x%"PRIx64": partial file\n", target);
370
+ return AVERROR_INVALIDDATA;
371
+ }
372
+
373
+ mov->next_root_atom = 0;
374
+ - if (index < 0 || index >= mov->frag_index.nb_items)
375
+ + if ((index < 0 && target >= 0) || index >= mov->frag_index.nb_items)
376
+ index = search_frag_moof_offset(&mov->frag_index, target);
377
+ - if (index < mov->frag_index.nb_items &&
378
+ + if (index >= 0 && index < mov->frag_index.nb_items &&
379
+ mov->frag_index.item[index].moof_offset == target) {
380
+ if (index + 1 < mov->frag_index.nb_items)
381
+ mov->next_root_atom = mov->frag_index.item[index + 1].moof_offset;
382
+ @@ -10554,10 +10554,43 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt)
383
+ MOVStreamContext *sc;
384
+ AVIndexEntry *sample;
385
+ AVStream *st = NULL;
386
+ + FFStream *avsti = NULL;
387
+ int64_t current_index;
388
+ int ret;
389
+ + int i;
390
+ mov->fc = s;
391
+ retry:
392
+ + if (s->pb->pos == 0) {
393
+ +
394
+ + // Discard current fragment index
395
+ + if (mov->frag_index.allocated_size > 0) {
396
+ + av_freep(&mov->frag_index.item);
397
+ + mov->frag_index.nb_items = 0;
398
+ + mov->frag_index.allocated_size = 0;
399
+ + mov->frag_index.current = -1;
400
+ + mov->frag_index.complete = 0;
401
+ + }
402
+ +
403
+ + for (i = 0; i < s->nb_streams; i++) {
404
+ + AVStream *avst = s->streams[i];
405
+ + MOVStreamContext *msc = avst->priv_data;
406
+ +
407
+ + // Clear current sample
408
+ + mov_current_sample_set(msc, 0);
409
+ + msc->ctts_index = 0;
410
+ +
411
+ + // Discard current index entries
412
+ + avsti = ffstream(avst);
413
+ + if (avsti->index_entries_allocated_size > 0) {
414
+ + av_freep(&avsti->index_entries);
415
+ + avsti->index_entries_allocated_size = 0;
416
+ + avsti->nb_index_entries = 0;
417
+ + }
418
+ + }
419
+ +
420
+ + if ((ret = mov_switch_root(s, -1, -1)) < 0)
421
+ + return ret;
422
+ + }
423
+ sample = mov_find_next_sample(s, &st);
424
+ if (!sample || (mov->next_root_atom && sample->pos > mov->next_root_atom)) {
425
+ if (!mov->next_root_atom)
0 commit comments