summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/mpv.rst13
-rw-r--r--stream/stream_file.c55
2 files changed, 61 insertions, 7 deletions
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,
};