diff --git a/deps/media-playback/media-playback/media.c b/deps/media-playback/media-playback/media.c index 0513618d7..11bd8bf5d 100644 --- a/deps/media-playback/media-playback/media.c +++ b/deps/media-playback/media-playback/media.c @@ -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); diff --git a/deps/media-playback/media-playback/media.h b/deps/media-playback/media-playback/media.h index ece7c4bd2..f45bce15e 100644 --- a/deps/media-playback/media-playback/media.h +++ b/deps/media-playback/media-playback/media.h @@ -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); diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index d140a1093..0e5e46083 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -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" diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-source.c b/plugins/obs-ffmpeg/obs-ffmpeg-source.c index 35925dbab..e28d8b8f2 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-source.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-source.c @@ -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);