Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958/* * 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 <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_mpv;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_edl;extern const stream_info_t stream_info_libarchive;extern const stream_info_t stream_info_cb;extern const stream_info_t stream_info_curl;extern const stream_info_t stream_info_env;
static const stream_info_t *const stream_list[] = { &stream_info_mpv,#if HAVE_CDDA &stream_info_cdda,#endif &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,#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,#if HAVE_LIBCURL &stream_info_curl,#endif &stream_info_ffmpeg, &stream_info_ffmpeg_unsafe, &stream_info_env,};
// 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 charstatic 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++]; } }}
char *mp_url_unescape(void *talloc_ctx, const char *url){ char *unescaped = talloc_strdup(talloc_ctx, url); mp_url_unescape_inplace(unescaped); return unescaped;}
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){ mp_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 mp_assert()s only)// new: new total size of buffer// returns: false if buffer allocation failed, true if reallocated or size okstatic bool stream_resize_buffer(struct stream *s, int keep, int new){ mp_assert(keep >= s->buf_end - s->buf_cur); mp_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);
mp_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); mp_assert(new_len == old_used_len - skip); mp_assert(old_pos >= skip); // "keep" too low mp_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 { char **get_protocols = sinfo->get_protocols ? sinfo->get_protocols() : NULL; char **protocols = get_protocols ? get_protocols : (char **)sinfo->protocols;
for (int n = 0; protocols && protocols[n]; n++) { path = match_proto(url, protocols[n]); if (path) break; }
talloc_free(get_protocols);
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; s->allow_partial_read = flags & STREAM_ALLOW_PARTIAL_READ;
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; }
mp_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){ mp_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){ struct stream *s = stream_create(filename, STREAM_ORIGIN_DIRECT | STREAM_WRITE, NULL, global); if (s && s->is_directory) { free_stream(s); s = NULL; } return s;}
// 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){ mp_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; } mp_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){ mp_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); mp_assert(forward_avail < forward);
// Keep guaranteed seek-back. int buf_old = MPMIN(s->buf_cur - s->buf_start, s->requested_buffer_size / 2);
// Never shrink the buffer here. That's stream_drop_buffers()'s job. Otherwise // data fetched by earlier larger reads (e.g. demuxer probing) would be // discarded, forcing redundant re-reads on backward seeks. int new_size = MPMAX(buf_old + forward, s->buffer_mask + 1);
if (!stream_resize_buffer(s, buf_old + forward_avail, new_size)) return false;
int buf_alloc = s->buffer_mask + 1;
mp_assert(s->buf_start <= s->buf_cur); mp_assert(s->buf_cur <= s->buf_end); mp_assert(s->buf_cur < buf_alloc * 2); mp_assert(s->buf_end < buf_alloc * 2); mp_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) { mp_assert(s->buf_end >= buf_alloc);
s->buf_start = s->buf_end - buf_alloc;
mp_assert(s->buf_start <= s->buf_cur); mp_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. mp_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){ mp_assert(s->buf_cur <= s->buf_end); mp_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 : STREAM_EOF;}
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; mp_assert(s->buf_cur >= s->buf_start); mp_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_lineint 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. If the stream is// created with STREAM_ALLOW_PARTIAL_READ flag, partial result up to// max_size is returned instead.// 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 < 0 || max_size > STREAM_MAX_READ_SIZE) abort(); if (s->is_directory) return (struct bstr){NULL, 0};
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 && !s->allow_partial_read) return (struct bstr){NULL, 0}; if (size > 0) bufsize = size + padding; else bufsize = 1000; if (s->allow_partial_read) bufsize = MPMIN(bufsize, max_size + padding); 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 >= max_size && s->allow_partial_read) { total_read = max_size; break; } 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){ return stream_read_file2(filename, talloc_ctx, STREAM_READ_FILE_FLAGS_DEFAULT, global, max_size);}
struct bstr stream_read_file2(const char *filename, void *talloc_ctx, int flags, struct mpv_global *global, int max_size){ struct bstr res = {0}; stream_t *s = stream_create(filename, flags, NULL, global); if (s) { if (s->is_directory) mp_err(s->log, "Failed to open %s (not a file).\n", filename); else res = stream_read_complete(s, talloc_ctx, max_size); } free_stream(s); return res;}
char **stream_get_proto_list(bool safe_only){ 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 (safe_only && (stream_info->stream_origin & STREAM_ORIGIN_UNSAFE)) continue;
char **get_protocols = stream_info->get_protocols ? stream_info->get_protocols() : NULL; char **protocols = get_protocols ? get_protocols : (char **)stream_info->protocols;
for (int j = 0; protocols && protocols[j]; j++) { if (*protocols[j] == '\0') continue;
MP_TARRAY_APPEND(NULL, list, num, talloc_strdup(list, protocols[j])); }
talloc_free(get_protocols); } 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(false); for (int i = 0; list[i]; i++) { mp_info(log, " %s://\n", list[i]); count++; } 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];
bool match = false; char **get_protocols = stream_info->get_protocols ? stream_info->get_protocols() : NULL; char **protocols = get_protocols ? get_protocols : (char **)stream_info->protocols;
for (int j = 0; protocols && protocols[j]; j++) { if (strcmp(protocols[j], proto) == 0) { match = true; break; } }
talloc_free(get_protocols); if (match) return match; }
return false;}