stream: fix reconnecting on broken network connections

This didn't work properly for HTTP with libavformat. The builtin HTTP
implementation reconnects automatically on its own, while libavformat
doesn't. Fix this by adding explicit reconnection support to
stream_lavf.c, which simply destroys and recreates the AVIO context.

It mostly works, though sometimes it mysteriously fails, spamming crap
all over the terminal and feeding broken data to the decoders. This is
probably due to itneractions with the cache. Also, reconnecting to
unseekable HTTP streams will make it read the entire stream until the
previous playback position is reached again.

It's not known whether this change makes behavior with "strange"
protocols like RTP better or worse.
This commit is contained in:
wm4 2013-01-24 18:45:24 +01:00
parent 47cec75291
commit 8a7b8c3dd6
3 changed files with 50 additions and 21 deletions

View File

@ -286,21 +286,30 @@ static int stream_reconnect(stream_t *s)
{
#define MAX_RECONNECT_RETRIES 5
#define RECONNECT_SLEEP_MS 1000
int retry = 0;
if (!s->streaming)
return 0;
int64_t pos = s->pos;
// Seeking is used as a hack to make network streams
// reopen the connection, ideally they would implement
// e.g. a STREAM_CTRL_RECONNECT to do this
do {
if (retry >= MAX_RECONNECT_RETRIES)
return 0;
for (int retry = 0; retry < MAX_RECONNECT_RETRIES; retry++) {
mp_msg(MSGT_STREAM, MSGL_WARN,
"Connection lost! Attempting to reconnect...\n");
if (retry)
usec_sleep(RECONNECT_SLEEP_MS * 1000);
retry++;
s->eof = 1;
stream_reset(s);
} while (stream_seek_internal(s, pos) >= 0 || s->pos != pos); // seek failed
return 1;
// Some streams (internal http.c) don't support STREAM_CTRL_RECONNECT,
// but do it when trying to seek.
if (s->control) {
if (s->control(s, STREAM_CTRL_RECONNECT, NULL) == STREAM_ERROR)
continue;
}
if (stream_seek_internal(s, pos) < 0 && s->pos == pos)
return 1;
}
return 0;
}
int stream_read_internal(stream_t *s, void *buf, int len)

View File

@ -101,6 +101,7 @@
#define STREAM_CTRL_GET_CACHE_SIZE 15
#define STREAM_CTRL_GET_CACHE_FILL 16
#define STREAM_CTRL_GET_CACHE_IDLE 17
#define STREAM_CTRL_RECONNECT 18
struct stream_lang_req {
int type; // STREAM_AUDIO, STREAM_SUB

View File

@ -30,9 +30,13 @@
#include "network.h"
#include "cookies.h"
static int open_f(stream_t *stream, int mode, void *opts, int *file_format);
static int fill_buffer(stream_t *s, char *buffer, int max_len)
{
AVIOContext *avio = s->priv;
if (!avio)
return -1;
int r = avio_read(avio, buffer, max_len);
return (r <= 0) ? -1 : r;
}
@ -40,6 +44,8 @@ static int fill_buffer(stream_t *s, char *buffer, int max_len)
static int write_buffer(stream_t *s, char *buffer, int len)
{
AVIOContext *avio = s->priv;
if (!avio)
return -1;
avio_write(avio, buffer, len);
avio_flush(avio);
if (avio->error)
@ -50,6 +56,8 @@ static int write_buffer(stream_t *s, char *buffer, int len)
static int seek(stream_t *s, int64_t newpos)
{
AVIOContext *avio = s->priv;
if (!avio)
return -1;
s->pos = newpos;
if (avio_seek(avio, s->pos, SEEK_SET) < 0) {
s->eof = 1;
@ -58,9 +66,23 @@ static int seek(stream_t *s, int64_t newpos)
return 1;
}
static void close_f(stream_t *stream)
{
AVIOContext *avio = stream->priv;
/* NOTE: As of 2011 write streams must be manually flushed before close.
* Currently write_buffer() always flushes them after writing.
* avio_close() could return an error, but we have no way to return that
* with the current stream API.
*/
if (avio)
avio_close(avio);
}
static int control(stream_t *s, int cmd, void *arg)
{
AVIOContext *avio = s->priv;
if (!avio && cmd != STREAM_CTRL_RECONNECT)
return -1;
int64_t size, ts;
double pts;
switch(cmd) {
@ -78,21 +100,18 @@ static int control(stream_t *s, int cmd, void *arg)
if (ts >= 0)
return 1;
break;
case STREAM_CTRL_RECONNECT: {
if (avio && avio->write_flag)
break; // don't bother with this
// avio doesn't seem to support this - emulate it by reopening
close_f(s);
s->priv = NULL;
return open_f(s, STREAM_READ, NULL, &(int) {0});
}
}
return STREAM_UNSUPPORTED;
}
static void close_f(stream_t *stream)
{
AVIOContext *avio = stream->priv;
/* NOTE: As of 2011 write streams must be manually flushed before close.
* Currently write_buffer() always flushes them after writing.
* avio_close() could return an error, but we have no way to return that
* with the current stream API.
*/
avio_close(avio);
}
static const char * const prefix[] = { "lavf://", "ffmpeg://" };
static int open_f(stream_t *stream, int mode, void *opts, int *file_format)