diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst index ec68d9a9da..5ce5d43e25 100644 --- a/DOCS/man/mpv.rst +++ b/DOCS/man/mpv.rst @@ -803,6 +803,19 @@ PROTOCOLS ``PATH`` itself should start with a third ``/`` to make the path an absolute path. +``appending://PATH`` + + Play a local file, but assume it's being appended to. This is useful for + example for files that are currently being downloaded to disk. This will + block playback, and stop playback only if no new data was appended after + a timeout of about 2 seconds. + + Using this is still a bit of a bad idea, because there is no way to detect + if a file is actually being appended, or if it's still written. If you're + trying to play the output of some program, consider using a pipe + (``something | mpv -``). If it really has to be a file on disk, use tail to + make it wait forever, e.g. ``tail -f -c +0 file.mkv | mpv -``. + ``fd://123`` Read data from the given file descriptor (for example 123). This is similar diff --git a/stream/stream_file.c b/stream/stream_file.c index d12d0d20b1..66c2a1a4c1 100644 --- a/stream/stream_file.c +++ b/stream/stream_file.c @@ -61,11 +61,27 @@ struct priv { int fd; bool close; bool use_poll; + bool regular_file; + bool appending; + int64_t orig_size; }; +// Total timeout = RETRY_TIMEOUT * MAX_RETRIES +#define RETRY_TIMEOUT 0.2 +#define MAX_RETRIES 10 + +static int64_t get_size(stream_t *s) +{ + struct priv *p = s->priv; + off_t size = lseek(p->fd, 0, SEEK_END); + lseek(p->fd, s->pos, SEEK_SET); + return size == (off_t)-1 ? -1 : size; +} + static int fill_buffer(stream_t *s, char *buffer, int max_len) { struct priv *p = s->priv; + #ifndef __MINGW32__ if (p->use_poll) { int c = s->cancel ? mp_cancel_get_fd(s->cancel) : -1; @@ -78,8 +94,28 @@ static int fill_buffer(stream_t *s, char *buffer, int max_len) return -1; } #endif - int r = read(p->fd, buffer, max_len); - return (r <= 0) ? -1 : r; + + for (int retries = 0; retries < MAX_RETRIES; retries++) { + int r = read(p->fd, buffer, max_len); + if (r > 0) + return r; + + // Try to detect and handle files being appended during playback. + int64_t size = get_size(s); + if (p->regular_file && size > p->orig_size && !p->appending) { + MP_WARN(s, "File is apparently being appended to, will keep " + "retrying with timeouts.\n"); + p->appending = true; + } + + if (!p->appending || p->use_poll) + break; + + if (mp_cancel_wait(s->cancel, RETRY_TIMEOUT)) + break; + } + + return 0; } static int write_buffer(stream_t *s, char *buffer, int len) @@ -105,12 +141,10 @@ static int seek(stream_t *s, int64_t newpos) static int control(stream_t *s, int cmd, void *arg) { - struct priv *p = s->priv; switch (cmd) { case STREAM_CTRL_GET_SIZE: { - off_t size = lseek(p->fd, 0, SEEK_END); - lseek(p->fd, s->pos, SEEK_SET); - if (size != (off_t)-1) { + int64_t size = get_size(s); + if (size >= 0) { *(int64_t *)arg = size; return 1; } @@ -265,6 +299,9 @@ static int open_f(stream_t *stream) p->fd = 1; } } else { + if (bstr_startswith0(bstr0(stream->url), "appending://")) + p->appending = true; + mode_t openmode = S_IRUSR | S_IWUSR; #ifndef __MINGW32__ openmode |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; @@ -289,6 +326,7 @@ static int open_f(stream_t *stream) #ifndef __MINGW32__ if (S_ISREG(st.st_mode)) { p->use_poll = false; + p->regular_file = true; // O_NONBLOCK has weird semantics on file locks; remove it. int val = fcntl(p->fd, F_GETFL) & ~(unsigned)O_NONBLOCK; fcntl(p->fd, F_SETFL, val); @@ -319,13 +357,16 @@ static int open_f(stream_t *stream) if (check_stream_network(p->fd)) stream->streaming = true; + p->orig_size = get_size(stream); + return STREAM_OK; } const stream_info_t stream_info_file = { .name = "file", .open = open_f, - .protocols = (const char*const[]){ "file", "", "fd", "fdclose", NULL }, + .protocols = (const char*const[]){ "file", "", "fd", "fdclose", + "appending", NULL }, .can_write = true, .is_safe = true, };