summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2017-01-30 19:38:43 +0100
committerwm4 <wm4@nowhere>2017-02-04 22:34:38 +0100
commit61202bb3640740d2cb98cf13922dcdf67970d5ef (patch)
tree2318d52589ada16859f19761924a26aa7bd09812
parent97680bf6041a0512c929c927024b38235a35ce55 (diff)
downloadmpv-61202bb3640740d2cb98cf13922dcdf67970d5ef.tar.bz2
mpv-61202bb3640740d2cb98cf13922dcdf67970d5ef.tar.xz
ytdl_hook, edl: implement pseudo-DASH support
We use the metadata provided by youtube-dl to sort-of implement fragmented DASH streaming. This is all a bit hacky, but hopefully a makeshift solution until libavformat has proper mechanisms. (Although in danger of being one of those temporary hacks that become permanent.)
-rw-r--r--DOCS/edl-mpv.rst43
-rw-r--r--demux/demux.h2
-rw-r--r--demux/demux_edl.c138
-rw-r--r--demux/demux_lavf.c45
-rw-r--r--demux/demux_timeline.c98
-rw-r--r--demux/timeline.c4
-rw-r--r--demux/timeline.h4
-rw-r--r--player/lua/ytdl_hook.lua12
8 files changed, 276 insertions, 70 deletions
diff --git a/DOCS/edl-mpv.rst b/DOCS/edl-mpv.rst
index b0f7238307..341d5ffa50 100644
--- a/DOCS/edl-mpv.rst
+++ b/DOCS/edl-mpv.rst
@@ -51,7 +51,8 @@ The rest of the lines belong to one of these classes:
1) An empty or commented line. A comment starts with ``#``, which must be the
first character in the line. The rest of the line (up until the next line
break) is ignored. An empty line has 0 bytes between two line feed bytes.
-2) A segment entry in all other cases.
+2) A header entry if the line starts with ``!``.
+3) A segment entry in all other cases.
Each segment entry consists of a list of named or unnamed parameters.
Parameters are separated with ``,``. Named parameters consist of a name,
@@ -63,8 +64,8 @@ Syntax::
segment_entry ::= <param> ( <param> ',' )*
param ::= [ <name> '=' ] ( <value> | '%' <number> '%' <valuebytes> )
-The ``name`` string can consist of any characters, except ``=%,;\n``. The
-``value`` string can consist of any characters except of ``,;\n``.
+The ``name`` string can consist of any characters, except ``=%,;\n!``. The
+``value`` string can consist of any characters except of ``,;\n!``.
The construct starting with ``%`` allows defining any value with arbitrary
contents inline, where ``number`` is an integer giving the number of bytes in
@@ -94,6 +95,42 @@ to ``20``, ``param3`` to ``value,escaped``, ``param4`` to ``value2``.
Instead of line breaks, the character ``;`` can be used. Line feed bytes and
``;`` are treated equally.
+Header entries start with ``!`` as first character after a line break. Header
+entries affect all other file entries in the EDL file. Their format is highly
+implementation specific. They should generally follow the file header, and come
+before any file entries.
+
+MP4 DASH
+========
+
+This is a header that helps implementing DASH, although it only provides a low
+level mechanism.
+
+If this header is set, the given url designates an mp4 init fragment. It's
+downloaded, and every URL in the EDL is prefixed with the init fragment on the
+byte stream level. This is mostly for use by mpv's internal ytdl support. The
+ytdl script will call youtube-dl, which in turn actually processes DASH
+manifests. It may work only for this very specific purpose and fail to be
+useful in other scenarios. It can be removed ot changed in incompatible ways
+at any times.
+
+Example::
+
+ !mp4_dash,init=url
+
+The ``url`` is encoded as parameter value as defined in the general EDL syntax.
+It's expected to point to an "initialization fragment", which will be prefixed
+to every entry in the EDL on the byte stream level.
+
+The current implementation will
+
+- ignore stream start times
+- use durations as hint for seeking only
+- not adjust source timestamps
+- open and close segments (i.e. fragments) as needed
+- not add segment boundaries as chapter points
+- require full compatibility between all segments (same codec etc.)
+
Timestamp format
================
diff --git a/demux/demux.h b/demux/demux.h
index 44bd2b9766..8a1da4e400 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -163,6 +163,8 @@ struct demuxer_params {
struct timeline *timeline;
bool disable_timeline;
bool initial_readahead;
+ bstr init_fragment;
+ bool skip_lavf_probing;
// -- demux_open_url() only
int stream_flags;
bool disable_cache;
diff --git a/demux/demux_edl.c b/demux/demux_edl.c
index 65a18a1a41..fad73f7644 100644
--- a/demux/demux_edl.c
+++ b/demux/demux_edl.c
@@ -44,6 +44,8 @@ struct tl_part {
};
struct tl_parts {
+ bool dash;
+ char *init_fragment_url;
struct tl_part *parts;
int num_parts;
};
@@ -65,6 +67,8 @@ static bool parse_time(bstr str, double *out_time)
return true;
}
+#define MAX_PARAMS 10
+
/* Returns a list of parts, or NULL on parse error.
* Syntax (without file header or URI prefix):
* url ::= <entry> ( (';' | '\n') <entry> )*
@@ -79,7 +83,10 @@ static struct tl_parts *parse_edl(bstr str)
bstr_split_tok(str, "\n", &(bstr){0}, &str);
if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
continue;
+ bool is_header = bstr_eatstart0(&str, "!");
struct tl_part p = { .length = -1 };
+ bstr param_names[MAX_PARAMS];
+ bstr param_vals[MAX_PARAMS];
int nparam = 0;
while (1) {
bstr name, val;
@@ -117,10 +124,25 @@ static struct tl_parts *parse_edl(bstr str)
if (bstr_equals0(val, "chapters"))
p.chapter_ts = true;
}
+ if (nparam >= MAX_PARAMS)
+ goto error;
+ param_names[nparam] = name;
+ param_vals[nparam] = val;
nparam++;
if (!bstr_eatstart0(&str, ","))
break;
}
+ if (is_header) {
+ if (tl->num_parts)
+ goto error; // can't have header once an entry was defined
+ bstr type = param_vals[0]; // value, because no "="
+ if (bstr_equals0(type, "mp4_dash")) {
+ tl->dash = true;
+ if (bstr_equals0(param_names[1], "init"))
+ tl->init_fragment_url = bstrto0(tl, param_vals[1]);
+ }
+ continue;
+ }
if (!p.filename)
goto error;
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
@@ -140,7 +162,10 @@ static struct demuxer *open_source(struct timeline *tl, char *filename)
if (strcmp(d->stream->url, filename) == 0)
return d;
}
- struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global);
+ struct demuxer_params params = {
+ .init_fragment = tl->init_fragment,
+ };
+ struct demuxer *d = demux_open_url(filename, &params, tl->cancel, tl->global);
if (d) {
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
} else {
@@ -203,63 +228,104 @@ static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
static void build_timeline(struct timeline *tl, struct tl_parts *parts)
{
+ tl->track_layout = NULL;
+ tl->dash = parts->dash;
+
+ if (parts->init_fragment_url && parts->init_fragment_url[0]) {
+ MP_VERBOSE(tl, "Opening init fragment...\n");
+ stream_t *s = stream_create(parts->init_fragment_url, STREAM_READ,
+ tl->cancel, tl->global);
+ if (s)
+ tl->init_fragment = stream_read_complete(s, tl, 1000000);
+ free_stream(s);
+ if (!tl->init_fragment.len) {
+ MP_ERR(tl, "Could not read init fragment.\n");
+ goto error;
+ }
+ s = open_memory_stream(tl->init_fragment.start, tl->init_fragment.len);
+ tl->track_layout = demux_open(s, NULL, tl->global);
+ if (!tl->track_layout) {
+ free_stream(s);
+ MP_ERR(tl, "Could not demux init fragment.\n");
+ goto error;
+ }
+ }
+
tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1);
double starttime = 0;
for (int n = 0; n < parts->num_parts; n++) {
struct tl_part *part = &parts->parts[n];
- struct demuxer *source = open_source(tl, part->filename);
- if (!source)
- goto error;
+ struct demuxer *source = NULL;
- resolve_timestamps(part, source);
+ if (tl->dash) {
+ part->offset = starttime;
+ if (part->length <= 0)
+ MP_WARN(tl, "Segment %d has unknown duration.\n", n);
+ if (part->offset_set)
+ MP_WARN(tl, "Offsets are ignored.\n");
+ tl->demuxer->is_network = true;
+ } else {
+ MP_VERBOSE(tl, "Opening segment %d...\n", n);
- double end_time = source_get_length(source);
- if (end_time >= 0)
- end_time += source->start_time;
+ source = open_source(tl, part->filename);
+ if (!source)
+ goto error;
- // Unknown length => use rest of the file. If duration is unknown, make
- // something up.
- if (part->length < 0) {
- if (end_time < 0) {
- MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
- part->filename);
- end_time = 1;
- }
- part->length = end_time - part->offset;
- } else if (end_time >= 0) {
- double end_part = part->offset + part->length;
- if (end_part > end_time) {
- MP_WARN(tl, "EDL: entry %d uses %f "
- "seconds, but file has only %f seconds.\n",
- n, end_part, end_time);
+ resolve_timestamps(part, source);
+
+ double end_time = source_get_length(source);
+ if (end_time >= 0)
+ end_time += source->start_time;
+
+ // Unknown length => use rest of the file. If duration is unknown, make
+ // something up.
+ if (part->length < 0) {
+ if (end_time < 0) {
+ MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
+ part->filename);
+ end_time = 1;
+ }
+ part->length = end_time - part->offset;
+ } else if (end_time >= 0) {
+ double end_part = part->offset + part->length;
+ if (end_part > end_time) {
+ MP_WARN(tl, "EDL: entry %d uses %f "
+ "seconds, but file has only %f seconds.\n",
+ n, end_part, end_time);
+ }
}
- }
- // Add a chapter between each file.
- struct demux_chapter ch = {
- .pts = starttime,
- .metadata = talloc_zero(tl, struct mp_tags),
- };
- mp_tags_set_str(ch.metadata, "title", part->filename);
- MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
+ // Add a chapter between each file.
+ struct demux_chapter ch = {
+ .pts = starttime,
+ .metadata = talloc_zero(tl, struct mp_tags),
+ };
+ mp_tags_set_str(ch.metadata, "title", part->filename);
+ MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
- // Also copy the source file's chapters for the relevant parts
- copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset,
- part->length, starttime);
+ // Also copy the source file's chapters for the relevant parts
+ copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset,
+ part->length, starttime);
+ }
tl->parts[n] = (struct timeline_part) {
.start = starttime,
.source_start = part->offset,
.source = source,
+ .url = talloc_strdup(tl, part->filename),
};
starttime += part->length;
- tl->demuxer->is_network |= source->is_network;
+ if (source) {
+ tl->demuxer->is_network |= source->is_network;
+
+ if (!tl->track_layout)
+ tl->track_layout = source;
+ }
}
tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime};
tl->num_parts = parts->num_parts;
- tl->track_layout = tl->parts[0].source;
return;
error:
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index 50391a29da..1cfbdb16cd 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -183,6 +183,8 @@ typedef struct lavf_priv {
AVInputFormat *avif;
int avif_flags;
AVFormatContext *avfc;
+ bstr init_fragment;
+ int64_t stream_pos;
AVIOContext *pb;
struct sh_stream **streams; // NULL for unknown streams
int num_streams;
@@ -218,7 +220,14 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
struct stream *stream = priv->stream;
int ret;
- ret = stream_read(stream, buf, size);
+ if (priv->stream_pos < priv->init_fragment.len) {
+ ret = MPMIN(size, priv->init_fragment.len - priv->stream_pos);
+ memcpy(buf, priv->init_fragment.start + priv->stream_pos, ret);
+ priv->stream_pos += ret;
+ } else {
+ ret = stream_read(stream, buf, size);
+ priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
+ }
MP_TRACE(demuxer, "%d=mp_read(%p, %p, %d), pos: %"PRId64", eof:%d\n",
ret, stream, buf, size, stream_tell(stream), stream->eof);
@@ -230,32 +239,44 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
struct demuxer *demuxer = opaque;
lavf_priv_t *priv = demuxer->priv;
struct stream *stream = priv->stream;
- int64_t current_pos;
+
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %s)\n", stream, pos,
whence == SEEK_END ? "end" :
whence == SEEK_CUR ? "cur" :
whence == SEEK_SET ? "set" : "size");
if (whence == SEEK_END || whence == AVSEEK_SIZE) {
- int64_t end = stream_get_size(stream);
+ int64_t end = stream_get_size(stream) + priv->init_fragment.len;
if (end < 0)
return -1;
if (whence == AVSEEK_SIZE)
return end;
pos += end;
} else if (whence == SEEK_CUR) {
- pos += stream_tell(stream);
+ pos += priv->stream_pos;
} else if (whence != SEEK_SET) {
return -1;
}
if (pos < 0)
return -1;
- current_pos = stream_tell(stream);
- if (stream_seek(stream, pos) == 0) {
+
+ int64_t stream_target = pos - priv->init_fragment.len;
+ bool seek_before = stream_target < 0;
+ if (seek_before)
+ stream_target = 0; // within init segment - seek real stream to 0
+
+ int64_t current_pos = stream_tell(stream);
+ if (stream_seek(stream, stream_target) == 0) {
stream_seek(stream, current_pos);
return -1;
}
+ if (seek_before) {
+ priv->stream_pos = pos;
+ } else {
+ priv->stream_pos = priv->init_fragment.len + stream_tell(stream);
+ }
+
return pos;
}
@@ -771,6 +792,9 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
if (lavf_check_file(demuxer, check) < 0)
return -1;
+ if (demuxer->params)
+ priv->init_fragment = bstrdup(priv, demuxer->params->init_fragment);
+
avfc = avformat_alloc_context();
if (!avfc)
return -1;
@@ -864,9 +888,12 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
av_dict_free(&dopts);
priv->avfc = avfc;
- if (avformat_find_stream_info(avfc, NULL) < 0) {
- MP_ERR(demuxer, "av_find_stream_info() failed\n");
- return -1;
+
+ if (!demuxer->params || !demuxer->params->skip_lavf_probing) {
+ if (avformat_find_stream_info(avfc, NULL) < 0) {
+ MP_ERR(demuxer, "av_find_stream_info() failed\n");
+ return -1;
+ }
}
MP_VERBOSE(demuxer, "avformat_find_stream_info() finished after %"PRId64
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
index c640965809..4b3eb93d0d 100644
--- a/demux/demux_timeline.c
+++ b/demux/demux_timeline.c
@@ -24,11 +24,14 @@
#include "demux.h"
#include "timeline.h"
#include "stheader.h"
+#include "stream/stream.h"
struct segment {
int index;
double start, end;
double d_start;
+ char *url;
+ bool lazy;
struct demuxer *d;
// stream_map[sh_stream.index] = index into priv.streams, where sh_stream
// is a stream from the source d. It's used to map the streams of the
@@ -51,6 +54,7 @@ struct priv {
struct timeline *tl;
double duration;
+ bool dash;
struct segment **segments;
int num_segments;
@@ -79,6 +83,9 @@ static void associate_streams(struct demuxer *demuxer, struct segment *seg)
{
struct priv *p = demuxer->priv;
+ if (!seg->d || seg->stream_map)
+ return;
+
int counts[STREAM_TYPE_COUNT] = {0};
int num_streams = demux_get_num_stream(seg->d);
@@ -118,6 +125,9 @@ static void reselect_streams(struct demuxer *demuxer)
for (int n = 0; n < p->num_segments; n++) {
struct segment *seg = p->segments[n];
for (int i = 0; i < seg->num_stream_map; i++) {
+ if (!seg->d)
+ continue;
+
struct sh_stream *sh = demux_get_stream(seg->d, i);
bool selected = false;
if (seg->stream_map[i] >= 0)
@@ -130,8 +140,42 @@ static void reselect_streams(struct demuxer *demuxer)
}
}
+static void close_lazy_segments(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ // unload previous segment
+ for (int n = 0; n < p->num_segments; n++) {
+ struct segment *seg = p->segments[n];
+ if (seg != p->current && seg->d && seg->lazy) {
+ free_demuxer_and_stream(seg->d);
+ seg->d = NULL;
+ }
+ }
+}
+
+static void reopen_lazy_segments(struct demuxer *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ if (p->current->d)
+ return;
+
+ close_lazy_segments(demuxer);
+
+ struct demuxer_params params = {
+ .init_fragment = p->tl->init_fragment,
+ .skip_lavf_probing = true,
+ };
+ p->current->d = demux_open_url(p->current->url, &params,
+ demuxer->stream->cancel, demuxer->global);
+ if (!p->current->d)
+ MP_ERR(demuxer, "failed to load segment\n");
+ associate_streams(demuxer, p->current);
+}
+
static void switch_segment(struct demuxer *demuxer, struct segment *new,
- double start_pts, int flags)
+ double start_pts, int flags, bool init)
{
struct priv *p = demuxer->priv;
@@ -141,9 +185,14 @@ static void switch_segment(struct demuxer *demuxer, struct segment *new,
MP_VERBOSE(demuxer, "switch to segment %d\n", new->index);
p->current = new;
+ reopen_lazy_segments(demuxer);
+ if (!new->d)
+ return;
reselect_streams(demuxer);
- demux_set_ts_offset(new->d, new->start - new->d_start);
- demux_seek(new->d, start_pts, flags);
+ if (!p->dash)
+ demux_set_ts_offset(new->d, new->start - new->d_start);
+ if (!p->dash || !init)
+ demux_seek(new->d, start_pts, flags);
for (int n = 0; n < p->num_streams; n++) {
struct virtual_stream *vs = &p->streams[n];
@@ -170,7 +219,7 @@ static void d_seek(struct demuxer *demuxer, double seek_pts, int flags)
}
}
- switch_segment(demuxer, new, pts, flags);
+ switch_segment(demuxer, new, pts, flags, false);
}
static int d_fill_buffer(struct demuxer *demuxer)
@@ -178,9 +227,11 @@ static int d_fill_buffer(struct demuxer *demuxer)
struct priv *p = demuxer->priv;
if (!p->current)
- switch_segment(demuxer, p->segments[0], 0, 0);
+ switch_segment(demuxer, p->segments[0], 0, 0, true);
struct segment *seg = p->current;
+ if (!seg || !seg->d)
+ return 0;
struct demux_packet *pkt = demux_read_any_packet(seg->d);
if (!pkt || pkt->pts >= seg->end)
@@ -217,20 +268,21 @@ static int d_fill_buffer(struct demuxer *demuxer)
}
if (!next)
return 0;
- switch_segment(demuxer, next, next->start, 0);
+ switch_segment(demuxer, next, next->start, 0, true);
return 1; // reader will retry
}
if (pkt->stream < 0 || pkt->stream > seg->num_stream_map)
goto drop;
- if (!pkt->codec)
- pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
-
- if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
- pkt->start = seg->start;
- if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
- pkt->end = seg->end;
+ if (!p->dash) {
+ if (!pkt->codec)
+ pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
+ if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
+ pkt->start = seg->start;
+ if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
+ pkt->end = seg->end;
+ }
pkt->stream = seg->stream_map[pkt->stream];
if (pkt->stream < 0)
@@ -255,7 +307,8 @@ static int d_fill_buffer(struct demuxer *demuxer)
}
}
- pkt->new_segment |= vs->new_segment;
+ if (!p->dash)
+ pkt->new_segment |= vs->new_segment;
vs->new_segment = false;
demux_add_packet(vs->sh, pkt);
@@ -273,9 +326,9 @@ static void print_timeline(struct demuxer *demuxer)
MP_VERBOSE(demuxer, "Timeline segments:\n");
for (int n = 0; n < p->num_segments; n++) {
struct segment *seg = p->segments[n];
- int src_num = -1;
- for (int i = 0; i < p->tl->num_sources; i++) {
- if (p->tl->sources[i] == seg->d) {
+ int src_num = n;
+ for (int i = 0; i < n - 1; i++) {
+ if (seg->d && p->segments[i]->d == seg->d) {
src_num = i;
break;
}
@@ -283,9 +336,12 @@ static void print_timeline(struct demuxer *demuxer)
MP_VERBOSE(demuxer, " %2d: %12f [%12f] (", n, seg->start, seg->d_start);
for (int i = 0; i < seg->num_stream_map; i++)
MP_VERBOSE(demuxer, "%s%d", i ? " " : "", seg->stream_map[i]);
- MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->d->filename);
+ MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->url);
}
MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
+
+ if (p->dash)
+ MP_VERBOSE(demuxer, "Durations and offsets are non-authoritative.\n");
}
static int d_open(struct demuxer *demuxer, enum demux_check check)
@@ -334,6 +390,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
struct segment *seg = talloc_ptrtype(p, seg);
*seg = (struct segment){
.d = part->source,
+ .url = part->source ? part->source->filename : part->url,
+ .lazy = !part->source,
.d_start = part->source_start,
.start = part->start,
.end = next->start,
@@ -345,6 +403,8 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
MP_TARRAY_APPEND(p, p->segments, p->num_segments, seg);
}
+ p->dash = p->tl->dash;
+
print_timeline(demuxer);
demuxer->seekable = true;
@@ -363,6 +423,8 @@ static void d_close(struct demuxer *demuxer)
{
struct priv *p = demuxer->priv;
struct demuxer *master = p->tl->demuxer;
+ p->current = NULL;
+ close_lazy_segments(demuxer);
timeline_destroy(p->tl);
free_demuxer(master);
}
diff --git a/demux/timeline.c b/demux/timeline.c
index 73f3ab79a2..700a6dfd05 100644
--- a/demux/timeline.c
+++ b/demux/timeline.c
@@ -33,8 +33,10 @@ void timeline_destroy(struct timeline *tl)
return;
for (int n = 0; n < tl->num_sources; n++) {
struct demuxer *d = tl->sources[n];
- if (d != tl->demuxer)
+ if (d != tl->demuxer && d != tl->track_layout)
free_demuxer_and_stream(d);
}
+ if (tl->track_layout && tl->track_layout != tl->demuxer)
+ free_demuxer_and_stream(tl->track_layout);
talloc_free(tl);
}
diff --git a/demux/timeline.h b/demux/timeline.h
index edc6a2f7ae..21a6602ba8 100644
--- a/demux/timeline.h
+++ b/demux/timeline.h
@@ -4,6 +4,7 @@
struct timeline_part {
double start;
double source_start;
+ char *url;
struct demuxer *source;
};
@@ -15,6 +16,9 @@ struct timeline {
// main source
struct demuxer *demuxer;
+ bstr init_fragment;
+ bool dash;
+
// All referenced files. The source file must be at sources[0].
struct demuxer **sources;
int num_sources;
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua
index 7349e579e3..fba5100822 100644
--- a/player/lua/ytdl_hook.lua
+++ b/player/lua/ytdl_hook.lua
@@ -90,11 +90,17 @@ end
local function edl_track_joined(fragments)
local edl = "edl://"
- for i = 1, #fragments do
+ local offset = 1
+ if fragments[1] and not fragments[1].duration then
+ -- if no duration, probably initialization segment
+ edl = edl .. "!mp4_dash,init=" .. edl_escape(fragments[1].url)
+ offset = 2
+ end
+ for i = offset, #fragments do
local fragment = fragments[i]
- edl = edl .. edl_escape(fragment.url)
if fragment.duration then
- edl = edl .. ",length=" .. fragment.duration
+ edl = edl .. edl_escape(fragment.url)
+ edl = edl..",length="..fragment.duration
end
edl = edl .. ";"
end