Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ jobs:
brew update
fi
if brew list ffmpeg &> /dev/null; then brew unlink ffmpeg; fi
brew install -q autoconf automake pkgconf libtool python freetype fribidi little-cms2 \
brew install -q autoconf automake curl pkgconf libtool python freetype fribidi little-cms2 \
luajit libass ffmpeg-full meson rust uchardet mujs libplacebo molten-vk vulkan-loader vulkan-headers \
libarchive libbluray libcaca libcdio-paranoia libdvdnav rubberband zimg
brew link ffmpeg-full
Expand Down Expand Up @@ -439,6 +439,7 @@ jobs:
apk update
apk add \
binutils \
curl-dev \
ffmpeg-dev \
gcc \
git \
Expand Down Expand Up @@ -493,6 +494,7 @@ jobs:
run: |
sudo pkg_add -U \
cmake \
curl \
ffmpeg \
git \
libarchive \
Expand Down Expand Up @@ -541,6 +543,7 @@ jobs:
sudo pkg install -y \
alsa-lib \
cmake \
curl \
evdev-proto \
ffmpeg \
git \
Expand Down Expand Up @@ -625,6 +628,7 @@ jobs:
ca-certificates:p
cc:p
cppwinrt:p
curl:p
ffmpeg:p
lcms2:p
libarchive:p
Expand Down
1 change: 1 addition & 0 deletions DOCS/interface-changes/http-backend.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add libcurl-based stream backend (http, https, ftp, ftps), with new options `--curl-http-version`, `--curl-max-redirects`, `--curl-max-retries`, `--curl-connect-timeout`, `--curl-buffer-size` and `--curl-max-request-size`
61 changes: 61 additions & 0 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5718,6 +5718,67 @@ Network
The bitrate as used is sent by the server, and there's no guarantee it's
actually meaningful.

Network backend (libcurl)
-------------------------

When mpv is built with libcurl support, ``http://``, ``https://``, ``ftp://``
and ``ftps://`` URLs are served by an internal libcurl-based stream backend
instead of FFmpeg. The backend fully supports all features of libcurl, making it
more robust and compatible with a wide range of servers and CDNs, and faster too.

For HTTP transfers, the backend transparently negotiates HTTP/1.1, HTTP/2
multiplexing or HTTP/3 (QUIC) when the server offers them, with HSTS enabled
and TCP keep-alive turned on. Content compression (gzip, deflate, zstd,
brotli) is always advertised in the request. If the server applies it, the
transfer is treated as non-seekable. Servers normally do not compress
already-compressed media payloads. Otherwise, it's great improvement for text
playlist data transfers.

The backend honors the network options listed above (``--user-agent``,
``--http-proxy``, ``--http-header-fields``, ``--referrer``, ``--cookies*``,
``--tls-*``).

If libcurl is not available at build time, mpv uses FFmpeg's networking
implementation instead.

To inspect libcurl's debug output (requests, response headers,
TLS/connection diagnostics), set ``--msg-level=curl=trace``.

``--curl-http-version=<auto|1.0|1.1|2|2tls|2-prior-knowledge|3|3only>``
Select the maximum HTTP protocol version libcurl is allowed to negotiate.
If libcurl was built without HTTP/3 support, it will fallback to ``auto``.
(default: ``auto``, i.e. let libcurl pick)

``--curl-max-redirects=<0-100>``
Maximum number of HTTP redirects to follow before reporting an error
(default: 16).

``--curl-max-retries=<0-100>``
Number of times a single seekable transfer may be transparently
re-attempted after a recoverable error (timeout, connection drop,
HTTP/2 stream reset, ...) before the stream gives up (default: 5).
Non-seekable transfers cannot be retried and ignore this option.

``--curl-connect-timeout=<seconds>``
TCP/TLS connect timeout in seconds (default: 30, range 0-600). 0 lets
libcurl use its built-in default. The overall transfer timeout is
controlled by ``--network-timeout``.

``--curl-buffer-size=<bytes>``
Size of the per-stream producer-side ring buffer that decouples the
network thread from the consumer (default: 4 MiB, minimum: 32 KiB).
Lower values may reduce in-flight data and reduce latency.

``--curl-max-request-size=<bytes>``
For seekable streams, split the transfer into Range requests of at most
this size (default: 0, i.e. one open-ended request for the whole stream).
A non-zero value can help with very long-running connections that some
CDNs or proxies recycle aggressively, and is also a common workaround for
per-connection bandwidth throttling employed by some CDNs (notably some
video hosting services), where each individual Range request is served at
full speed but a single long-lived connection is rate-limited. Ignored for
non-seekable streams.

DVB
---

Expand Down
17 changes: 14 additions & 3 deletions ci/build-mingw64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -324,14 +324,25 @@ _subrandr () {
}
_subrandr_mark=lib/libsubrandr.dll.a

_curl () {
local ver=8.20.0
gettar "https://curl.se/download/curl-${ver}.tar.xz"
builddir curl-${ver}
cmake .. "${cmake_args[@]}" \
-DCURL_{USE_SCHANNEL,ZLIB}=ON -DCURL_DISABLE_LDAP=ON -DCURL_USE_LIBPSL=OFF
makeplusinstall
popd
}
_curl_mark=lib/libcurl.dll.a

for x in iconv zlib-ng shaderc spirv-cross amf-headers nv-headers dav1d lcms2; do
build_if_missing $x
done
if [[ "$TARGET" != "i686-"* ]]; then
build_if_missing vulkan-headers
build_if_missing vulkan-loader
fi
for x in ffmpeg libplacebo freetype fribidi harfbuzz libass luajit; do
for x in ffmpeg libplacebo freetype fribidi harfbuzz libass luajit curl; do
build_if_missing $x
done
if [[ "$TARGET" != "i686-"* ]]; then
Expand All @@ -358,7 +369,7 @@ mpv_args=(
-Dmujs:werror=false
-Dmujs:default_library=static
-Dlua=luajit
-D{amf,shaderc,spirv-cross,d3d11,javascript}=enabled
-D{amf,shaderc,spirv-cross,d3d11,javascript,libcurl}=enabled
)
meson setup $build "${mpv_args[@]}"
meson compile -C $build
Expand All @@ -384,7 +395,7 @@ if [ "$2" = pack ]; then
av*.dll sw*.dll postproc-[0-9]*.dll
# everything else
subrandr-[0-9]*.dll lib{ass,freetype,fribidi,harfbuzz,iconv,placebo}-[0-9]*.dll
lib{shaderc_shared,spirv-cross-c-shared,dav1d,lcms2,zlib1}.dll
lib{curl,shaderc_shared,spirv-cross-c-shared,dav1d,lcms2,zlib1}.dll
)
[[ -f vulkan-1.dll ]] && dlls+=(vulkan-1.dll)
mv -v "${dlls[@]}" ..
Expand Down
1 change: 1 addition & 0 deletions ci/build-win32.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ meson setup build `
-Dlibplacebo:tests=false `
-Dlibplacebo:vulkan=enabled `
-Dlibplacebo:d3d11=enabled `
-Dlibpsl:tests=false `
-Dxxhash:inline-all=true `
-Dxxhash:cli=false `
-Dluajit:amalgam=true `
Expand Down
1 change: 1 addition & 0 deletions common/global.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct mpv_global {
char *configdir;
struct stats_base *stats;
struct demux_packet_pool *packet_pool;
struct curl_ctx *curl;
};

#endif
164 changes: 164 additions & 0 deletions demux/avio_crypto.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* 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 <string.h>

#include <libavformat/avio.h>
#include <libavutil/aes.h>
#include <libavutil/error.h>
#include <libavutil/mem.h>

#include "avio_crypto.h"

#include "common/common.h"
#include "misc/bstr.h"
#include "mpv_talloc.h"

#define BLOCKSIZE 16
// In-place decrypt scratch. Holds unconsumed plaintext followed by at most one
// held-back ciphertext block. Any value >= 2*BLOCKSIZE works, this is just a
// reasonable batch size.
#define IO_BUFFER_SIZE (4 * 1024)
#define AVIO_BUFFER_SIZE (64 * 1024)

struct crypto_priv {
AVIOContext *inner;
struct AVAES *aes;
uint8_t iv[BLOCKSIZE];

// [0 .. plain_pos) consumed plaintext (free space)
// [plain_pos .. plain_end) plaintext to deliver
// [plain_end .. ct_end) held-back ciphertext (< BLOCKSIZE)
uint8_t buf[IO_BUFFER_SIZE];
int plain_pos;
int plain_end;
int ct_end;
bool inner_eof;
};

static void priv_destructor(void *ptr)
{
struct crypto_priv *p = ptr;
av_free(p->aes);
}

static int crypto_read(void *opaque, uint8_t *buf, int size)
{
struct crypto_priv *p = opaque;

if (size <= 0)
return 0;

while (p->plain_pos == p->plain_end) {
// Compact held-back ciphertext to the start of the buffer.
int held = p->ct_end - p->plain_end;
if (held > 0 && p->plain_end > 0)
memmove(p->buf, p->buf + p->plain_end, held);
p->plain_pos = p->plain_end = 0;
p->ct_end = held;

// Refill until we have two blocks pending (so the final one can stay
// held back for PKCS#7 stripping) or hit EOF.
while (!p->inner_eof && p->ct_end < 2 * BLOCKSIZE) {
int n = avio_read(p->inner, p->buf + p->ct_end,
sizeof(p->buf) - p->ct_end);
if (n <= 0) {
p->inner_eof = true;
break;
}
p->ct_end += n;
}

int blocks = p->ct_end / BLOCKSIZE;
if (!p->inner_eof)
blocks--;
if (blocks <= 0)
return AVERROR_EOF;

av_aes_crypt(p->aes, p->buf, p->buf, blocks, p->iv, 1);
p->plain_end = blocks * BLOCKSIZE;

// Final batch: drop PKCS#7 padding bytes from the tail.
if (p->inner_eof && p->plain_end == p->ct_end) {
int padding = p->buf[p->plain_end - 1];
if (padding < 1 || padding > BLOCKSIZE)
return AVERROR_INVALIDDATA;
p->plain_end -= padding;
}
}

int copy = MPMIN(p->plain_end - p->plain_pos, size);
memcpy(buf, p->buf + p->plain_pos, copy);
p->plain_pos += copy;
return copy;
}

int mp_avio_crypto_open(AVIOContext **out_pb, AVIOContext *inner,
bstr key, bstr iv)
{
if (!out_pb || !inner || key.len != BLOCKSIZE || iv.len != BLOCKSIZE)
return AVERROR(EINVAL);

int r;
void *avio_buf = NULL;
struct crypto_priv *p = talloc_zero(NULL, struct crypto_priv);
talloc_set_destructor(p, priv_destructor);

p->inner = inner;
p->aes = av_aes_alloc();
if (!p->aes) {
r = AVERROR(ENOMEM);
goto fail;
}
if (av_aes_init(p->aes, key.start, BLOCKSIZE * 8, 1) < 0) {
r = AVERROR(EINVAL);
goto fail;
}
memcpy(p->iv, iv.start, BLOCKSIZE);

avio_buf = av_malloc(AVIO_BUFFER_SIZE);
if (!avio_buf) {
r = AVERROR(ENOMEM);
goto fail;
}

AVIOContext *pb = avio_alloc_context(avio_buf, AVIO_BUFFER_SIZE, 0, p,
crypto_read, NULL, NULL);
if (!pb) {
r = AVERROR(ENOMEM);
goto fail;
}
pb->seekable = 0;

*out_pb = pb;
return 0;

fail:
av_free(avio_buf);
talloc_free(p);
return r;
}

void mp_avio_crypto_close(AVIOContext **pb)
{
if (!pb || !*pb)
return;
struct crypto_priv *p = (*pb)->opaque;
av_freep(&(*pb)->buffer);
avio_context_free(pb);
talloc_free(p);
}
32 changes: 32 additions & 0 deletions demux/avio_crypto.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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/>.
*/

#pragma once

#include <libavformat/avio.h>

struct bstr;

// AES-128-CBC + PKCS#7 decryption layer over `inner`. `key` and `iv` are 16
// bytes each; other lengths return AVERROR(EINVAL). Neither buffer is retained
// past this call.
//
// The returned wrapper is read-only and non-seekable, and does not own
// `inner`.
int mp_avio_crypto_open(AVIOContext **out_pb, AVIOContext *inner,
struct bstr key, struct bstr iv);
void mp_avio_crypto_close(AVIOContext **pb);
Loading
Loading