summaryrefslogtreecommitdiffstats
path: root/stream/stream.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
commit51de1d8436100f725f3576aefa24a2bd2057bc28 (patch)
treec6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /stream/stream.c
parentInitial commit. (diff)
downloadmpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz
mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--stream/stream.c900
1 files changed, 900 insertions, 0 deletions
diff --git a/stream/stream.c b/stream/stream.c
new file mode 100644
index 0000000..dd67825
--- /dev/null
+++ b/stream/stream.c
@@ -0,0 +1,900 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <strings.h>
+#include <assert.h>
+
+#include "osdep/io.h"
+
+#include "mpv_talloc.h"
+
+#include "config.h"
+
+#include "common/common.h"
+#include "common/global.h"
+#include "demux/demux.h"
+#include "misc/bstr.h"
+#include "misc/thread_tools.h"
+#include "common/msg.h"
+#include "options/m_config.h"
+#include "options/options.h"
+#include "options/path.h"
+#include "osdep/timer.h"
+#include "stream.h"
+
+#include "options/m_option.h"
+#include "options/m_config.h"
+
+extern const stream_info_t stream_info_cdda;
+extern const stream_info_t stream_info_dvb;
+extern const stream_info_t stream_info_null;
+extern const stream_info_t stream_info_memory;
+extern const stream_info_t stream_info_mf;
+extern const stream_info_t stream_info_ffmpeg;
+extern const stream_info_t stream_info_ffmpeg_unsafe;
+extern const stream_info_t stream_info_avdevice;
+extern const stream_info_t stream_info_file;
+extern const stream_info_t stream_info_slice;
+extern const stream_info_t stream_info_fd;
+extern const stream_info_t stream_info_ifo_dvdnav;
+extern const stream_info_t stream_info_dvdnav;
+extern const stream_info_t stream_info_bdmv_dir;
+extern const stream_info_t stream_info_bluray;
+extern const stream_info_t stream_info_bdnav;
+extern const stream_info_t stream_info_edl;
+extern const stream_info_t stream_info_libarchive;
+extern const stream_info_t stream_info_cb;
+
+static const stream_info_t *const stream_list[] = {
+#if HAVE_CDDA
+ &stream_info_cdda,
+#endif
+ &stream_info_ffmpeg,
+ &stream_info_ffmpeg_unsafe,
+ &stream_info_avdevice,
+#if HAVE_DVBIN
+ &stream_info_dvb,
+#endif
+#if HAVE_DVDNAV
+ &stream_info_ifo_dvdnav,
+ &stream_info_dvdnav,
+#endif
+#if HAVE_LIBBLURAY
+ &stream_info_bdmv_dir,
+ &stream_info_bluray,
+ &stream_info_bdnav,
+#endif
+#if HAVE_LIBARCHIVE
+ &stream_info_libarchive,
+#endif
+ &stream_info_memory,
+ &stream_info_null,
+ &stream_info_mf,
+ &stream_info_edl,
+ &stream_info_file,
+ &stream_info_slice,
+ &stream_info_fd,
+ &stream_info_cb,
+};
+
+// Because of guarantees documented on STREAM_BUFFER_SIZE.
+// Half the buffer is used as forward buffer, the other for seek-back.
+#define STREAM_MIN_BUFFER_SIZE (STREAM_BUFFER_SIZE * 2)
+// Sort of arbitrary; keep *2 of it comfortably within integer limits.
+// Must be power of 2.
+#define STREAM_MAX_BUFFER_SIZE (512 * 1024 * 1024)
+
+struct stream_opts {
+ int64_t buffer_size;
+ bool load_unsafe_playlists;
+};
+
+#define OPT_BASE_STRUCT struct stream_opts
+
+const struct m_sub_options stream_conf = {
+ .opts = (const struct m_option[]){
+ {"stream-buffer-size", OPT_BYTE_SIZE(buffer_size),
+ M_RANGE(STREAM_MIN_BUFFER_SIZE, STREAM_MAX_BUFFER_SIZE)},
+ {"load-unsafe-playlists", OPT_BOOL(load_unsafe_playlists)},
+ {0}
+ },
+ .size = sizeof(struct stream_opts),
+ .defaults = &(const struct stream_opts){
+ .buffer_size = 128 * 1024,
+ },
+};
+
+// return -1 if not hex char
+static int hex2dec(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'A' && c <= 'F')
+ return 10 + c - 'A';
+ if (c >= 'a' && c <= 'f')
+ return 10 + c - 'a';
+ return -1;
+}
+
+// Replace escape sequences in an URL (or a part of an URL)
+void mp_url_unescape_inplace(char *url)
+{
+ for (int len = strlen(url), i = 0, o = 0; i <= len;) {
+ if ((url[i] != '%') || (i > len - 3)) { // %NN can't start after len-3
+ url[o++] = url[i++];
+ continue;
+ }
+
+ int msd = hex2dec(url[i + 1]),
+ lsd = hex2dec(url[i + 2]);
+
+ if (msd >= 0 && lsd >= 0) {
+ url[o++] = 16 * msd + lsd;
+ i += 3;
+ } else {
+ url[o++] = url[i++];
+ url[o++] = url[i++];
+ url[o++] = url[i++];
+ }
+ }
+}
+
+static const char hex_digits[] = "0123456789ABCDEF";
+
+
+static const char url_default_ok[] = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "-._~";
+
+// Escape according to http://tools.ietf.org/html/rfc3986#section-2.1
+// Only unreserved characters are not escaped.
+// The argument ok (if not NULL) is as follows:
+// ok[0] != '~': additional characters that are not escaped
+// ok[0] == '~': do not escape anything but these characters
+// (can't override the unreserved characters, which are
+// never escaped)
+char *mp_url_escape(void *talloc_ctx, const char *url, const char *ok)
+{
+ char *rv = talloc_size(talloc_ctx, strlen(url) * 3 + 1);
+ char *out = rv;
+ bool negate = ok && ok[0] == '~';
+
+ for (char c; (c = *url); url++) {
+ bool as_is = negate ? !strchr(ok + 1, c)
+ : (strchr(url_default_ok, c) || (ok && strchr(ok, c)));
+ if (as_is) {
+ *out++ = c;
+ } else {
+ unsigned char v = c;
+ *out++ = '%';
+ *out++ = hex_digits[v / 16];
+ *out++ = hex_digits[v % 16];
+ }
+ }
+
+ *out = 0;
+ return rv;
+}
+
+static const char *match_proto(const char *url, const char *proto)
+{
+ int l = strlen(proto);
+ if (l > 0) {
+ if (strncasecmp(url, proto, l) == 0 && strncmp("://", url + l, 3) == 0)
+ return url + l + 3;
+ } else if (!mp_is_url(bstr0(url))) {
+ return url; // pure filenames
+ }
+ return NULL;
+}
+
+// src and new are both STREAM_ORIGIN_* values. This checks whether a stream
+// with flags "new" can be opened from the "src". On success, return
+// new origin, on incompatibility return 0.
+static int check_origin(int src, int new)
+{
+ switch (src) {
+ case STREAM_ORIGIN_DIRECT:
+ case STREAM_ORIGIN_UNSAFE:
+ // Allow anything, but constrain it to the new origin.
+ return new;
+ case STREAM_ORIGIN_FS:
+ // From unix FS, allow all but unsafe.
+ if (new == STREAM_ORIGIN_FS || new == STREAM_ORIGIN_NET)
+ return new;
+ break;
+ case STREAM_ORIGIN_NET:
+ // Allow only other network links.
+ if (new == STREAM_ORIGIN_NET)
+ return new;
+ break;
+ }
+ return 0;
+}
+
+// Read len bytes from the start position, and wrap around as needed. Limit the
+// actually read data to the size of the buffer. Return amount of copied bytes.
+// len: max bytes to copy to dst
+// pos: index into s->buffer[], e.g. s->buf_start is byte 0
+// returns: bytes copied to dst (limited by len and available buffered data)
+static int ring_copy(struct stream *s, void *dst, int len, int pos)
+{
+ assert(len >= 0);
+
+ if (pos < s->buf_start || pos > s->buf_end)
+ return 0;
+
+ int copied = 0;
+ len = MPMIN(len, s->buf_end - pos);
+
+ if (len && pos <= s->buffer_mask) {
+ int copy = MPMIN(len, s->buffer_mask + 1 - pos);
+ memcpy(dst, &s->buffer[pos], copy);
+ copied += copy;
+ len -= copy;
+ pos += copy;
+ }
+
+ if (len) {
+ memcpy((char *)dst + copied, &s->buffer[pos & s->buffer_mask], len);
+ copied += len;
+ }
+
+ return copied;
+}
+
+// Resize the current stream buffer. Uses a larger size if needed to keep data.
+// Does nothing if the size is adequate. Calling this with 0 ensures it uses the
+// default buffer size if possible.
+// The caller must check whether enough data was really allocated.
+// keep: keep at least [buf_end-keep, buf_end] (used for assert()s only)
+// new: new total size of buffer
+// returns: false if buffer allocation failed, true if reallocated or size ok
+static bool stream_resize_buffer(struct stream *s, int keep, int new)
+{
+ assert(keep >= s->buf_end - s->buf_cur);
+ assert(keep <= new);
+
+ new = MPMAX(new, s->requested_buffer_size);
+ new = MPMIN(new, STREAM_MAX_BUFFER_SIZE);
+ new = mp_round_next_power_of_2(new);
+
+ assert(keep <= new); // can't fail (if old buffer size was valid)
+
+ if (new == s->buffer_mask + 1)
+ return true;
+
+ int old_pos = s->buf_cur - s->buf_start;
+ int old_used_len = s->buf_end - s->buf_start;
+ int skip = old_used_len > new ? old_used_len - new : 0;
+
+ MP_DBG(s, "resize stream to %d bytes, drop %d bytes\n", new, skip);
+
+ void *nbuf = ta_alloc_size(s, new);
+ if (!nbuf)
+ return false; // oom; tolerate it, caller needs to check if required
+
+ int new_len = 0;
+ if (s->buffer)
+ new_len = ring_copy(s, nbuf, new, s->buf_start + skip);
+ assert(new_len == old_used_len - skip);
+ assert(old_pos >= skip); // "keep" too low
+ assert(old_pos - skip <= new_len);
+ s->buf_start = 0;
+ s->buf_cur = old_pos - skip;
+ s->buf_end = new_len;
+
+ ta_free(s->buffer);
+
+ s->buffer = nbuf;
+ s->buffer_mask = new - 1;
+
+ return true;
+}
+
+static int stream_create_instance(const stream_info_t *sinfo,
+ struct stream_open_args *args,
+ struct stream **ret)
+{
+ const char *url = args->url;
+ int flags = args->flags;
+
+ *ret = NULL;
+
+ const char *path = url;
+
+ if (flags & STREAM_LOCAL_FS_ONLY) {
+ if (!sinfo->local_fs)
+ return STREAM_NO_MATCH;
+ } else {
+ for (int n = 0; sinfo->protocols && sinfo->protocols[n]; n++) {
+ path = match_proto(url, sinfo->protocols[n]);
+ if (path)
+ break;
+ }
+
+ if (!path)
+ return STREAM_NO_MATCH;
+ }
+
+ stream_t *s = talloc_zero(NULL, stream_t);
+ s->global = args->global;
+ struct stream_opts *opts = mp_get_config_group(s, s->global, &stream_conf);
+ if (flags & STREAM_SILENT) {
+ s->log = mp_null_log;
+ } else {
+ s->log = mp_log_new(s, s->global->log, sinfo->name);
+ }
+ s->info = sinfo;
+ s->cancel = args->cancel;
+ s->url = talloc_strdup(s, url);
+ s->path = talloc_strdup(s, path);
+ s->mode = flags & (STREAM_READ | STREAM_WRITE);
+ s->requested_buffer_size = opts->buffer_size;
+
+ if (flags & STREAM_LESS_NOISE)
+ mp_msg_set_max_level(s->log, MSGL_WARN);
+
+ struct demux_opts *demux_opts = mp_get_config_group(s, s->global, &demux_conf);
+ s->access_references = demux_opts->access_references;
+ talloc_free(demux_opts);
+
+ MP_VERBOSE(s, "Opening %s\n", url);
+
+ if (strlen(url) > INT_MAX / 8) {
+ MP_ERR(s, "URL too large.\n");
+ talloc_free(s);
+ return STREAM_ERROR;
+ }
+
+ if ((s->mode & STREAM_WRITE) && !sinfo->can_write) {
+ MP_DBG(s, "No write access implemented.\n");
+ talloc_free(s);
+ return STREAM_NO_MATCH;
+ }
+
+ s->stream_origin = flags & STREAM_ORIGIN_MASK; // pass through by default
+ if (opts->load_unsafe_playlists) {
+ s->stream_origin = STREAM_ORIGIN_DIRECT;
+ } else if (sinfo->stream_origin) {
+ s->stream_origin = check_origin(s->stream_origin, sinfo->stream_origin);
+ }
+
+ if (!s->stream_origin) {
+ talloc_free(s);
+ return STREAM_UNSAFE;
+ }
+
+ int r = STREAM_UNSUPPORTED;
+ if (sinfo->open2) {
+ r = sinfo->open2(s, args);
+ } else if (!args->special_arg) {
+ r = (sinfo->open)(s);
+ }
+ if (r != STREAM_OK) {
+ talloc_free(s);
+ return r;
+ }
+
+ if (!stream_resize_buffer(s, 0, 0)) {
+ free_stream(s);
+ return STREAM_ERROR;
+ }
+
+ assert(s->seekable == !!s->seek);
+
+ if (s->mime_type)
+ MP_VERBOSE(s, "Mime-type: '%s'\n", s->mime_type);
+
+ MP_DBG(s, "Stream opened successfully.\n");
+
+ *ret = s;
+ return STREAM_OK;
+}
+
+int stream_create_with_args(struct stream_open_args *args, struct stream **ret)
+
+{
+ assert(args->url);
+
+ int r = STREAM_NO_MATCH;
+ *ret = NULL;
+
+ // Open stream proper
+ if (args->sinfo) {
+ r = stream_create_instance(args->sinfo, args, ret);
+ } else {
+ for (int i = 0; i < MP_ARRAY_SIZE(stream_list); i++) {
+ r = stream_create_instance(stream_list[i], args, ret);
+ if (r == STREAM_OK)
+ break;
+ if (r == STREAM_NO_MATCH || r == STREAM_UNSUPPORTED)
+ continue;
+ if (r == STREAM_UNSAFE)
+ continue;
+ break;
+ }
+ }
+
+ if (!*ret && !(args->flags & STREAM_SILENT) && !mp_cancel_test(args->cancel))
+ {
+ struct mp_log *log = mp_log_new(NULL, args->global->log, "!stream");
+
+ if (r == STREAM_UNSAFE) {
+ mp_err(log, "\nRefusing to load potentially unsafe URL from a playlist.\n"
+ "Use the --load-unsafe-playlists option to load it anyway.\n\n");
+ } else if (r == STREAM_NO_MATCH || r == STREAM_UNSUPPORTED) {
+ mp_err(log, "No protocol handler found to open URL %s\n", args->url);
+ mp_err(log, "The protocol is either unsupported, or was disabled "
+ "at compile-time.\n");
+ } else {
+ mp_err(log, "Failed to open %s.\n", args->url);
+ }
+
+ talloc_free(log);
+ }
+
+ return r;
+}
+
+struct stream *stream_create(const char *url, int flags,
+ struct mp_cancel *c, struct mpv_global *global)
+{
+ struct stream_open_args args = {
+ .global = global,
+ .cancel = c,
+ .flags = flags,
+ .url = url,
+ };
+ struct stream *s;
+ stream_create_with_args(&args, &s);
+ return s;
+}
+
+stream_t *open_output_stream(const char *filename, struct mpv_global *global)
+{
+ return stream_create(filename, STREAM_ORIGIN_DIRECT | STREAM_WRITE,
+ NULL, global);
+}
+
+// Read function bypassing the local stream buffer. This will not write into
+// s->buffer, but into buf[0..len] instead.
+// Returns 0 on error or EOF, and length of bytes read on success.
+// Partial reads are possible, even if EOF is not reached.
+static int stream_read_unbuffered(stream_t *s, void *buf, int len)
+{
+ assert(len >= 0);
+ if (len <= 0)
+ return 0;
+
+ int res = 0;
+ // we will retry even if we already reached EOF previously.
+ if (s->fill_buffer && !mp_cancel_test(s->cancel))
+ res = s->fill_buffer(s, buf, len);
+ if (res <= 0) {
+ s->eof = 1;
+ return 0;
+ }
+ assert(res <= len);
+ // When reading succeeded we are obviously not at eof.
+ s->eof = 0;
+ s->pos += res;
+ s->total_unbuffered_read_bytes += res;
+ return res;
+}
+
+// Ask for having at most "forward" bytes ready to read in the buffer.
+// To read everything, you may have to call this in a loop.
+// forward: desired amount of bytes in buffer after s->cur_pos
+// returns: progress (false on EOF or on OOM or if enough data was available)
+static bool stream_read_more(struct stream *s, int forward)
+{
+ assert(forward >= 0);
+
+ int forward_avail = s->buf_end - s->buf_cur;
+ if (forward_avail >= forward)
+ return false;
+
+ // Avoid that many small reads will lead to many low-level read calls.
+ forward = MPMAX(forward, s->requested_buffer_size / 2);
+ assert(forward_avail < forward);
+
+ // Keep guaranteed seek-back.
+ int buf_old = MPMIN(s->buf_cur - s->buf_start, s->requested_buffer_size / 2);
+
+ if (!stream_resize_buffer(s, buf_old + forward_avail, buf_old + forward))
+ return false;
+
+ int buf_alloc = s->buffer_mask + 1;
+
+ assert(s->buf_start <= s->buf_cur);
+ assert(s->buf_cur <= s->buf_end);
+ assert(s->buf_cur < buf_alloc * 2);
+ assert(s->buf_end < buf_alloc * 2);
+ assert(s->buf_start < buf_alloc);
+
+ // Note: read as much as possible, even if forward is much smaller. Do
+ // this because the stream buffer is supposed to set an approx. minimum
+ // read size on it.
+ int read = buf_alloc - (buf_old + forward_avail); // free buffer past end
+
+ int pos = s->buf_end & s->buffer_mask;
+ read = MPMIN(read, buf_alloc - pos);
+
+ // Note: if wrap-around happens, we need to make two calls. This may
+ // affect latency (e.g. waiting for new data on a socket), so do only
+ // 1 read call always.
+ read = stream_read_unbuffered(s, &s->buffer[pos], read);
+
+ s->buf_end += read;
+
+ // May have overwritten old data.
+ if (s->buf_end - s->buf_start >= buf_alloc) {
+ assert(s->buf_end >= buf_alloc);
+
+ s->buf_start = s->buf_end - buf_alloc;
+
+ assert(s->buf_start <= s->buf_cur);
+ assert(s->buf_cur <= s->buf_end);
+
+ if (s->buf_start >= buf_alloc) {
+ s->buf_start -= buf_alloc;
+ s->buf_cur -= buf_alloc;
+ s->buf_end -= buf_alloc;
+ }
+ }
+
+ // Must not have overwritten guaranteed old data.
+ assert(s->buf_cur - s->buf_start >= buf_old);
+
+ if (s->buf_cur < s->buf_end)
+ s->eof = 0;
+
+ return !!read;
+}
+
+// Read between 1..buf_size bytes of data, return how much data has been read.
+// Return 0 on EOF, error, or if buf_size was 0.
+int stream_read_partial(stream_t *s, void *buf, int buf_size)
+{
+ assert(s->buf_cur <= s->buf_end);
+ assert(buf_size >= 0);
+ if (s->buf_cur == s->buf_end && buf_size > 0) {
+ if (buf_size > (s->buffer_mask + 1) / 2) {
+ // Direct read if the buffer is too small anyway.
+ stream_drop_buffers(s);
+ return stream_read_unbuffered(s, buf, buf_size);
+ }
+ stream_read_more(s, 1);
+ }
+ int res = ring_copy(s, buf, buf_size, s->buf_cur);
+ s->buf_cur += res;
+ return res;
+}
+
+// Slow version of stream_read_char(); called by it if the buffer is empty.
+int stream_read_char_fallback(stream_t *s)
+{
+ uint8_t c;
+ return stream_read_partial(s, &c, 1) ? c : -256;
+}
+
+int stream_read(stream_t *s, void *mem, int total)
+{
+ int len = total;
+ while (len > 0) {
+ int read = stream_read_partial(s, mem, len);
+ if (read <= 0)
+ break; // EOF
+ mem = (char *)mem + read;
+ len -= read;
+ }
+ total -= len;
+ return total;
+}
+
+// Read ahead so that at least forward_size bytes are readable ahead. Returns
+// the actual forward amount available (restricted by EOF or buffer limits).
+int stream_peek(stream_t *s, int forward_size)
+{
+ while (stream_read_more(s, forward_size)) {}
+ return s->buf_end - s->buf_cur;
+}
+
+// Like stream_read(), but do not advance the current position. This may resize
+// the buffer to satisfy the read request.
+int stream_read_peek(stream_t *s, void *buf, int buf_size)
+{
+ stream_peek(s, buf_size);
+ return ring_copy(s, buf, buf_size, s->buf_cur);
+}
+
+int stream_write_buffer(stream_t *s, void *buf, int len)
+{
+ if (!s->write_buffer)
+ return -1;
+ int orig_len = len;
+ while (len) {
+ int w = s->write_buffer(s, buf, len);
+ if (w <= 0)
+ return -1;
+ s->pos += w;
+ buf = (char *)buf + w;
+ len -= w;
+ }
+ return orig_len;
+}
+
+// Drop len bytes form input, possibly reading more until all is skipped. If
+// EOF or an error was encountered before all could be skipped, return false,
+// otherwise return true.
+static bool stream_skip_read(struct stream *s, int64_t len)
+{
+ while (len > 0) {
+ unsigned int left = s->buf_end - s->buf_cur;
+ if (!left) {
+ if (!stream_read_more(s, 1))
+ return false;
+ continue;
+ }
+ unsigned skip = MPMIN(len, left);
+ s->buf_cur += skip;
+ len -= skip;
+ }
+ return true;
+}
+
+// Drop the internal buffer. Note that this will advance the stream position
+// (as seen by stream_tell()), because the real stream position is ahead of the
+// logical stream position by the amount of buffered but not yet read data.
+void stream_drop_buffers(stream_t *s)
+{
+ s->pos = stream_tell(s);
+ s->buf_start = s->buf_cur = s->buf_end = 0;
+ s->eof = 0;
+ stream_resize_buffer(s, 0, 0);
+}
+
+// Seek function bypassing the local stream buffer.
+static bool stream_seek_unbuffered(stream_t *s, int64_t newpos)
+{
+ if (newpos != s->pos) {
+ MP_VERBOSE(s, "stream level seek from %" PRId64 " to %" PRId64 "\n",
+ s->pos, newpos);
+
+ s->total_stream_seeks++;
+
+ if (newpos > s->pos && !s->seekable) {
+ MP_ERR(s, "Cannot seek forward in this stream\n");
+ return false;
+ }
+ if (newpos < s->pos && !s->seekable) {
+ MP_ERR(s, "Cannot seek backward in linear streams!\n");
+ return false;
+ }
+ if (s->seek(s, newpos) <= 0) {
+ int level = mp_cancel_test(s->cancel) ? MSGL_V : MSGL_ERR;
+ MP_MSG(s, level, "Seek failed (to %lld, size %lld)\n",
+ (long long)newpos, (long long)stream_get_size(s));
+ return false;
+ }
+ stream_drop_buffers(s);
+ s->pos = newpos;
+ }
+ return true;
+}
+
+bool stream_seek(stream_t *s, int64_t pos)
+{
+ MP_TRACE(s, "seek request from %" PRId64 " to %" PRId64 "\n",
+ stream_tell(s), pos);
+
+ s->eof = 0; // eof should be set only on read; seeking always clears it
+
+ if (pos < 0) {
+ MP_ERR(s, "Invalid seek to negative position %lld!\n", (long long)pos);
+ pos = 0;
+ }
+
+ if (pos <= s->pos) {
+ int64_t x = pos - (s->pos - (int)s->buf_end);
+ if (x >= (int)s->buf_start) {
+ s->buf_cur = x;
+ assert(s->buf_cur >= s->buf_start);
+ assert(s->buf_cur <= s->buf_end);
+ return true;
+ }
+ }
+
+ if (s->mode == STREAM_WRITE)
+ return s->seekable && s->seek(s, pos);
+
+ // Skip data instead of performing a seek in some cases.
+ if (pos >= s->pos &&
+ ((!s->seekable && s->fast_skip) ||
+ pos - s->pos <= s->requested_buffer_size))
+ {
+ return stream_skip_read(s, pos - stream_tell(s));
+ }
+
+ return stream_seek_unbuffered(s, pos);
+}
+
+// Like stream_seek(), but strictly prefer skipping data instead of failing, if
+// it's a forward-seek.
+bool stream_seek_skip(stream_t *s, int64_t pos)
+{
+ uint64_t cur_pos = stream_tell(s);
+
+ if (cur_pos == pos)
+ return true;
+
+ return !s->seekable && pos > cur_pos
+ ? stream_skip_read(s, pos - cur_pos)
+ : stream_seek(s, pos);
+}
+
+int stream_control(stream_t *s, int cmd, void *arg)
+{
+ return s->control ? s->control(s, cmd, arg) : STREAM_UNSUPPORTED;
+}
+
+// Return the current size of the stream, or a negative value if unknown.
+int64_t stream_get_size(stream_t *s)
+{
+ return s->get_size ? s->get_size(s) : -1;
+}
+
+void free_stream(stream_t *s)
+{
+ if (!s)
+ return;
+
+ if (s->close)
+ s->close(s);
+ talloc_free(s);
+}
+
+static const char *const bom[3] = {"\xEF\xBB\xBF", "\xFF\xFE", "\xFE\xFF"};
+
+// Return utf16 argument for stream_read_line
+int stream_skip_bom(struct stream *s)
+{
+ char buf[4];
+ int len = stream_read_peek(s, buf, sizeof(buf));
+ bstr data = {buf, len};
+ for (int n = 0; n < 3; n++) {
+ if (bstr_startswith0(data, bom[n])) {
+ stream_seek_skip(s, stream_tell(s) + strlen(bom[n]));
+ return n;
+ }
+ }
+ return -1; // default to 8 bit codepages
+}
+
+// Read the rest of the stream into memory (current pos to EOF), and return it.
+// talloc_ctx: used as talloc parent for the returned allocation
+// max_size: must be set to >0. If the file is larger than that, it is treated
+// as error. This is a minor robustness measure.
+// returns: stream contents, or .start/.len set to NULL on error
+// If the file was empty, but no error happened, .start will be non-NULL and
+// .len will be 0.
+// For convenience, the returned buffer is padded with a 0 byte. The padding
+// is not included in the returned length.
+struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
+ int max_size)
+{
+ if (max_size > 1000000000)
+ abort();
+
+ int bufsize;
+ int total_read = 0;
+ int padding = 1;
+ char *buf = NULL;
+ int64_t size = stream_get_size(s) - stream_tell(s);
+ if (size > max_size)
+ return (struct bstr){NULL, 0};
+ if (size > 0)
+ bufsize = size + padding;
+ else
+ bufsize = 1000;
+ while (1) {
+ buf = talloc_realloc_size(talloc_ctx, buf, bufsize);
+ int readsize = stream_read(s, buf + total_read, bufsize - total_read);
+ total_read += readsize;
+ if (total_read < bufsize)
+ break;
+ if (bufsize > max_size) {
+ talloc_free(buf);
+ return (struct bstr){NULL, 0};
+ }
+ bufsize = MPMIN(bufsize + (bufsize >> 1), max_size + padding);
+ }
+ buf = talloc_realloc_size(talloc_ctx, buf, total_read + padding);
+ memset(&buf[total_read], 0, padding);
+ return (struct bstr){buf, total_read};
+}
+
+struct bstr stream_read_file(const char *filename, void *talloc_ctx,
+ struct mpv_global *global, int max_size)
+{
+ struct bstr res = {0};
+ int flags = STREAM_ORIGIN_DIRECT | STREAM_READ | STREAM_LOCAL_FS_ONLY |
+ STREAM_LESS_NOISE;
+ stream_t *s = stream_create(filename, flags, NULL, global);
+ if (s) {
+ res = stream_read_complete(s, talloc_ctx, max_size);
+ free_stream(s);
+ }
+ return res;
+}
+
+char **stream_get_proto_list(void)
+{
+ char **list = NULL;
+ int num = 0;
+ for (int i = 0; i < MP_ARRAY_SIZE(stream_list); i++) {
+ const stream_info_t *stream_info = stream_list[i];
+
+ if (!stream_info->protocols)
+ continue;
+
+ for (int j = 0; stream_info->protocols[j]; j++) {
+ if (*stream_info->protocols[j] == '\0')
+ continue;
+
+ MP_TARRAY_APPEND(NULL, list, num,
+ talloc_strdup(NULL, stream_info->protocols[j]));
+ }
+ }
+ MP_TARRAY_APPEND(NULL, list, num, NULL);
+ return list;
+}
+
+void stream_print_proto_list(struct mp_log *log)
+{
+ int count = 0;
+
+ mp_info(log, "Protocols:\n\n");
+ char **list = stream_get_proto_list();
+ for (int i = 0; list[i]; i++) {
+ mp_info(log, " %s://\n", list[i]);
+ count++;
+ talloc_free(list[i]);
+ }
+ talloc_free(list);
+ mp_info(log, "\nTotal: %d protocols\n", count);
+}
+
+bool stream_has_proto(const char *proto)
+{
+ for (int i = 0; i < MP_ARRAY_SIZE(stream_list); i++) {
+ const stream_info_t *stream_info = stream_list[i];
+
+ for (int j = 0; stream_info->protocols && stream_info->protocols[j]; j++) {
+ if (strcmp(stream_info->protocols[j], proto) == 0)
+ return true;
+ }
+ }
+
+ return false;
+}