summaryrefslogtreecommitdiffstats
path: root/stream/stream_concat.c
diff options
context:
space:
mode:
Diffstat (limited to 'stream/stream_concat.c')
-rw-r--r--stream/stream_concat.c179
1 files changed, 179 insertions, 0 deletions
diff --git a/stream/stream_concat.c b/stream/stream_concat.c
new file mode 100644
index 0000000..d06bd4a
--- /dev/null
+++ b/stream/stream_concat.c
@@ -0,0 +1,179 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libavutil/common.h>
+
+#include "common/common.h"
+#include "stream.h"
+
+struct priv {
+ struct stream **streams;
+ int num_streams;
+
+ int64_t size;
+
+ int cur; // streams[cur] is the stream for current stream.pos
+};
+
+static int fill_buffer(struct stream *s, void *buffer, int len)
+{
+ struct priv *p = s->priv;
+
+ while (1) {
+ int res = stream_read_partial(p->streams[p->cur], buffer, len);
+ if (res || p->cur == p->num_streams - 1)
+ return res;
+
+ p->cur += 1;
+ if (s->seekable)
+ stream_seek(p->streams[p->cur], 0);
+ }
+}
+
+static int seek(struct stream *s, int64_t newpos)
+{
+ struct priv *p = s->priv;
+
+ int64_t next_pos = 0;
+ int64_t base_pos = 0;
+
+ // Look for the last stream whose start position is <= newpos.
+ // Note that the last stream's size is essentially ignored. The last
+ // stream is allowed to have an unknown size.
+ for (int n = 0; n < p->num_streams; n++) {
+ if (next_pos > newpos)
+ break;
+
+ base_pos = next_pos;
+ p->cur = n;
+
+ int64_t size = stream_get_size(p->streams[n]);
+ if (size < 0)
+ break;
+
+ next_pos = base_pos + size;
+ }
+
+ int ok = stream_seek(p->streams[p->cur], newpos - base_pos);
+ s->pos = base_pos + stream_tell(p->streams[p->cur]);
+ return ok;
+}
+
+static int64_t get_size(struct stream *s)
+{
+ struct priv *p = s->priv;
+ return p->size;
+}
+
+static void s_close(struct stream *s)
+{
+ struct priv *p = s->priv;
+
+ for (int n = 0; n < p->num_streams; n++)
+ free_stream(p->streams[n]);
+}
+
+// Return the "worst" origin value of the two. cur can be 0 to mean unset.
+static int combine_origin(int cur, int new)
+{
+ if (cur == STREAM_ORIGIN_UNSAFE || new == STREAM_ORIGIN_UNSAFE)
+ return STREAM_ORIGIN_UNSAFE;
+ if (cur == STREAM_ORIGIN_NET || new == STREAM_ORIGIN_NET)
+ return STREAM_ORIGIN_NET;
+ if (cur == STREAM_ORIGIN_FS || new == STREAM_ORIGIN_FS)
+ return STREAM_ORIGIN_FS;
+ return new; // including cur==0
+}
+
+static int open2(struct stream *stream, const struct stream_open_args *args)
+{
+ struct priv *p = talloc_zero(stream, struct priv);
+ stream->priv = p;
+
+ stream->fill_buffer = fill_buffer;
+ stream->get_size = get_size;
+ stream->close = s_close;
+
+ stream->seekable = true;
+
+ struct priv *list = args->special_arg;
+ if (!list || !list->num_streams) {
+ MP_FATAL(stream, "No sub-streams.\n");
+ return STREAM_ERROR;
+ }
+
+ stream->stream_origin = 0;
+
+ for (int n = 0; n < list->num_streams; n++) {
+ struct stream *sub = list->streams[n];
+
+ int64_t size = stream_get_size(sub);
+ if (n != list->num_streams - 1 && size < 0) {
+ MP_WARN(stream, "Sub stream %d has unknown size.\n", n);
+ stream->seekable = false;
+ p->size = -1;
+ } else if (size >= 0 && p->size >= 0) {
+ p->size += size;
+ }
+
+ if (!sub->seekable)
+ stream->seekable = false;
+
+ stream->stream_origin =
+ combine_origin(stream->stream_origin, sub->stream_origin);
+
+ MP_TARRAY_APPEND(p, p->streams, p->num_streams, sub);
+ }
+
+ if (stream->seekable)
+ stream->seek = seek;
+
+ return STREAM_OK;
+}
+
+static const stream_info_t stream_info_concat = {
+ .name = "concat",
+ .open2 = open2,
+ .protocols = (const char*const[]){ "concat", NULL },
+};
+
+// Create a stream with a concatenated view on streams[]. Copies the streams
+// array. Takes over ownership of every stream passed to it (it will free them
+// if the concat stream is closed).
+// If an error happens, NULL is returned, and the streams are not freed.
+struct stream *stream_concat_open(struct mpv_global *global, struct mp_cancel *c,
+ struct stream **streams, int num_streams)
+{
+ // (struct priv is blatantly abused to pass in the stream list)
+ struct priv arg = {
+ .streams = streams,
+ .num_streams = num_streams,
+ };
+
+ struct stream_open_args sargs = {
+ .global = global,
+ .cancel = c,
+ .url = "concat://",
+ .flags = STREAM_READ | STREAM_SILENT | STREAM_ORIGIN_DIRECT,
+ .sinfo = &stream_info_concat,
+ .special_arg = &arg,
+ };
+
+ struct stream *s = NULL;
+ stream_create_with_args(&sargs, &s);
+ return s;
+}