obs-ffmpeg: Add auto reconnect to remote media sources

(Note: This commit also modifies deps/media-playback)

Co-authored-by: Eric Lindvall <eric@5stops.com>
Co-authored-by: Ryan Foster <RytoEX@gmail.com>
Co-authored-by: Jim <obs.jim@gmail.com>
This commit is contained in:
yoho 2020-06-21 15:46:36 -04:00 committed by jp9000
parent 0fff23d8e1
commit 0a4b1d622c
4 changed files with 102 additions and 17 deletions

View file

@ -587,6 +587,7 @@ static bool init_avformat(mp_media_t *m)
m->fmt->flags |= AVFMT_FLAG_NOBUFFER;
}
if (!m->is_local_file) {
av_dict_set(&opts, "stimeout", "30000000", 0);
m->fmt->interrupt_callback.callback = interrupt_callback;
m->fmt->interrupt_callback.opaque = m;
}
@ -596,7 +597,9 @@ static bool init_avformat(mp_media_t *m)
av_dict_free(&opts);
if (ret < 0) {
blog(LOG_WARNING, "MP: Failed to open media: '%s'", m->path);
if (!m->reconnecting)
blog(LOG_WARNING, "MP: Failed to open media: '%s'",
m->path);
return false;
}
@ -606,6 +609,7 @@ static bool init_avformat(mp_media_t *m)
return false;
}
m->reconnecting = false;
m->has_video = mp_decode_init(m, AVMEDIA_TYPE_VIDEO, m->hw);
m->has_audio = mp_decode_init(m, AVMEDIA_TYPE_AUDIO, m->hw);
@ -820,7 +824,7 @@ void mp_media_free(mp_media_t *media)
pthread_mutex_init_value(&media->mutex);
}
void mp_media_play(mp_media_t *m, bool loop)
void mp_media_play(mp_media_t *m, bool loop, bool reconnecting)
{
pthread_mutex_lock(&m->mutex);
@ -829,6 +833,7 @@ void mp_media_play(mp_media_t *m, bool loop)
m->looping = loop;
m->active = true;
m->reconnecting = reconnecting;
pthread_mutex_unlock(&m->mutex);

View file

@ -64,6 +64,7 @@ struct mp_media {
struct mp_decode v;
struct mp_decode a;
bool is_local_file;
bool reconnecting;
bool has_video;
bool has_audio;
bool is_file;
@ -117,12 +118,13 @@ struct mp_media_info {
enum video_range_type force_range;
bool hardware_decoding;
bool is_local_file;
bool reconnecting;
};
extern bool mp_media_init(mp_media_t *media, const struct mp_media_info *info);
extern void mp_media_free(mp_media_t *media);
extern void mp_media_play(mp_media_t *media, bool loop);
extern void mp_media_play(mp_media_t *media, bool loop, bool reconnecting);
extern void mp_media_stop(mp_media_t *media);
extern void mp_media_play_pause(mp_media_t *media, bool pause);
extern int64_t mp_get_current_time(mp_media_t *m);

View file

@ -66,3 +66,5 @@ NVENC.OutdatedDriver="Your current video card driver does not support this NVENC
NVENC.UnsupportedDevice="NVENC Error: Unsupported device. Check your video card supports NVENC and that the drivers are up to date."
NVENC.TooManySessions="NVENC Error: Too many concurrent sessions. Try closing other recording software which might be using NVENC such as NVIDIA Shadowplay or Windows 10 Game DVR."
NVENC.CheckDrivers="Please check your video drivers are up to date."
ReconnectDelayTime="Reconnect Delay"

View file

@ -58,6 +58,12 @@ struct ffmpeg_source {
bool close_when_inactive;
bool seekable;
pthread_t reconnect_thread;
bool stop_reconnect;
bool reconnect_thread_valid;
volatile bool reconnecting;
int reconnect_delay_sec;
enum obs_media_state state;
obs_hotkey_pair_id play_pause_hotkey;
obs_hotkey_id stop_hotkey;
@ -85,6 +91,8 @@ static bool is_local_file_modified(obs_properties_t *props,
obs_properties_get(props, "close_when_inactive");
obs_property_t *seekable = obs_properties_get(props, "seekable");
obs_property_t *speed = obs_properties_get(props, "speed_percent");
obs_property_t *reconnect_delay_sec =
obs_properties_get(props, "reconnect_delay_sec");
obs_property_set_visible(input, !enabled);
obs_property_set_visible(input_format, !enabled);
obs_property_set_visible(buffering, !enabled);
@ -93,6 +101,7 @@ static bool is_local_file_modified(obs_properties_t *props,
obs_property_set_visible(looping, enabled);
obs_property_set_visible(speed, enabled);
obs_property_set_visible(seekable, !enabled);
obs_property_set_visible(reconnect_delay_sec, !enabled);
return true;
}
@ -103,6 +112,7 @@ static void ffmpeg_source_defaults(obs_data_t *settings)
obs_data_set_default_bool(settings, "looping", false);
obs_data_set_default_bool(settings, "clear_on_media_end", true);
obs_data_set_default_bool(settings, "restart_on_activate", true);
obs_data_set_default_int(settings, "reconnect_delay_sec", 10);
obs_data_set_default_int(settings, "buffering_mb", 2);
obs_data_set_default_int(settings, "speed_percent", 100);
}
@ -174,6 +184,11 @@ static obs_properties_t *ffmpeg_source_getproperties(void *data)
obs_module_text("InputFormat"),
OBS_TEXT_DEFAULT);
prop = obs_properties_add_int_slider(
props, "reconnect_delay_sec",
obs_module_text("ReconnectDelayTime"), 1, 60, 1);
obs_property_int_set_suffix(prop, " S");
#ifndef __APPLE__
obs_properties_add_bool(props, "hw_decode",
obs_module_text("HardwareDecode"));
@ -245,12 +260,18 @@ static void preload_frame(void *opaque, struct obs_source_frame *f)
if (s->is_clear_on_media_end || s->is_looping)
obs_source_preload_video(s->source, f);
if (!s->is_local_file && os_atomic_set_bool(&s->reconnecting, false))
FF_BLOG(LOG_INFO, "Reconnected.");
}
static void get_audio(void *opaque, struct obs_source_audio *a)
{
struct ffmpeg_source *s = opaque;
obs_source_output_audio(s->source, a);
if (!s->is_local_file && os_atomic_set_bool(&s->reconnecting, false))
FF_BLOG(LOG_INFO, "Reconnected.");
}
static void media_stopped(void *opaque)
@ -282,12 +303,50 @@ static void ffmpeg_source_open(struct ffmpeg_source *s)
.speed = s->speed_percent,
.force_range = s->range,
.hardware_decoding = s->is_hw_decoding,
.is_local_file = s->is_local_file || s->seekable};
.is_local_file = s->is_local_file || s->seekable,
.reconnecting = s->reconnecting,
};
s->media_valid = mp_media_init(&s->media, &info);
}
}
static void ffmpeg_source_start(struct ffmpeg_source *s)
{
if (!s->media_valid)
ffmpeg_source_open(s);
if (!s->media_valid)
return;
mp_media_play(&s->media, s->is_looping, s->reconnecting);
if (s->is_local_file)
obs_source_show_preloaded_video(s->source);
set_media_state(s, OBS_MEDIA_STATE_PLAYING);
obs_source_media_started(s->source);
}
static void *ffmpeg_source_reconnect(void *data)
{
struct ffmpeg_source *s = data;
os_sleep_ms(s->reconnect_delay_sec * 1000);
if (s->stop_reconnect || s->media_valid)
goto finish;
bool active = obs_source_active(s->source);
if (!s->close_when_inactive || active)
ffmpeg_source_open(s);
if (!s->restart_on_activate || active)
ffmpeg_source_start(s);
finish:
s->reconnect_thread_valid = false;
return NULL;
}
static void ffmpeg_source_tick(void *data, float seconds)
{
UNUSED_PARAMETER(seconds);
@ -298,21 +357,22 @@ static void ffmpeg_source_tick(void *data, float seconds)
mp_media_free(&s->media);
s->media_valid = false;
}
s->destroy_media = false;
}
}
static void ffmpeg_source_start(struct ffmpeg_source *s)
{
if (!s->media_valid)
ffmpeg_source_open(s);
if (s->media_valid) {
mp_media_play(&s->media, s->is_looping);
if (s->is_local_file)
obs_source_show_preloaded_video(s->source);
set_media_state(s, OBS_MEDIA_STATE_PLAYING);
obs_source_media_started(s->source);
if (!s->is_local_file) {
if (!os_atomic_set_bool(&s->reconnecting, true)) {
FF_BLOG(LOG_WARNING, "Disconnected. "
"Reconnecting...");
}
if (pthread_create(&s->reconnect_thread, NULL,
ffmpeg_source_reconnect, s) != 0) {
FF_BLOG(LOG_WARNING, "Could not create "
"reconnect thread");
return;
}
s->reconnect_thread_valid = true;
}
}
}
@ -338,8 +398,19 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings)
input = (char *)obs_data_get_string(settings, "input");
input_format =
(char *)obs_data_get_string(settings, "input_format");
s->reconnect_delay_sec =
(int)obs_data_get_int(settings, "reconnect_delay_sec");
s->reconnect_delay_sec = s->reconnect_delay_sec == 0
? 10
: s->reconnect_delay_sec;
s->is_looping = false;
s->close_when_inactive = true;
if (s->reconnect_thread_valid) {
s->stop_reconnect = true;
pthread_join(s->reconnect_thread, NULL);
s->stop_reconnect = false;
}
}
s->input = input ? bstrdup(input) : NULL;
@ -539,6 +610,11 @@ static void ffmpeg_source_destroy(void *data)
if (s->hotkey)
obs_hotkey_unregister(s->hotkey);
if (!s->is_local_file) {
s->stop_reconnect = true;
if (s->reconnect_thread_valid)
pthread_join(s->reconnect_thread, NULL);
}
if (s->media_valid)
mp_media_free(&s->media);