From 5e55669a0b91e7cffa6a040ec66144246b9eb5f0 Mon Sep 17 00:00:00 2001
From: "F. Duncanh" Usage
ctrl-C fg ctrl-C
to terminate the image viewer, bring
uxplay
into the foreground, and terminate it too.
-reset n sets a limit of n consecutive -timeout failures of the client to respond to ntp requests from the -server (these are sent every 3 seconds to check if the client is still -present, and synchronize with it). After n failures, the client -will be presumed to be offline, and the connection will be reset to -allow a new connection. The default value of n is 5; the value -n = 0 means “no limit” on timeouts.
+timeout failures of the client to send feedback requests (these +“heartbeat signal” are sent by the client once per second to check if +the server is still present. After n missing signals, the +client will be presumed to be offline, and the connection will be reset +to allow a new connection. The default value of n is 15 +seconds; the value n = 0 means “no limit”.-nofreeze closes the video window after a reset due -to ntp timeout (default is to leave window open to allow a smoother -reconection to the same client). This option may be useful in fullscreen -mode.
+to client going offline (default is to leave window open to allow a +smoother reconection to the same client). This option may be useful in +fullscreen mode.-nc maintains previous UxPlay < 1.45 behavior
that does not close the video window when the the
client sends the “Stop Mirroring” signal. This option is currently
diff --git a/README.md b/README.md
index ab3f8723f..7bdd00705 100644
--- a/README.md
+++ b/README.md
@@ -1180,13 +1180,13 @@ terminate the image viewer, bring `uxplay` into the foreground, and
terminate it too.
**-reset n** sets a limit of *n* consecutive timeout failures of the
-client to respond to ntp requests from the server (these are sent every
-3 seconds to check if the client is still present, and synchronize with
-it). After *n* failures, the client will be presumed to be offline, and
+client to send feedback requests (these "heartbeat signal" are sent by the client
+once per second to check if the server is still present.
+After *n* missing signals, the client will be presumed to be offline, and
the connection will be reset to allow a new connection. The default
-value of *n* is 5; the value *n* = 0 means "no limit" on timeouts.
+value of *n* is 15 seconds; the value *n* = 0 means "no limit".
-**-nofreeze** closes the video window after a reset due to ntp timeout
+**-nofreeze** closes the video window after a reset due to client going offline
(default is to leave window open to allow a smoother reconection to the
same client). This option may be useful in fullscreen mode.
diff --git a/README.txt b/README.txt
index 01ed68775..43297e474 100644
--- a/README.txt
+++ b/README.txt
@@ -1181,15 +1181,15 @@ terminate the image viewer, bring `uxplay` into the foreground, and
terminate it too.
**-reset n** sets a limit of *n* consecutive timeout failures of the
-client to respond to ntp requests from the server (these are sent every
-3 seconds to check if the client is still present, and synchronize with
-it). After *n* failures, the client will be presumed to be offline, and
-the connection will be reset to allow a new connection. The default
-value of *n* is 5; the value *n* = 0 means "no limit" on timeouts.
-
-**-nofreeze** closes the video window after a reset due to ntp timeout
-(default is to leave window open to allow a smoother reconection to the
-same client). This option may be useful in fullscreen mode.
+client to send feedback requests (these "heartbeat signal" are sent by
+the client once per second to check if the server is still present.
+After *n* missing signals, the client will be presumed to be offline,
+and the connection will be reset to allow a new connection. The default
+value of *n* is 15 seconds; the value *n* = 0 means "no limit".
+
+**-nofreeze** closes the video window after a reset due to client going
+offline (default is to leave window open to allow a smoother reconection
+to the same client). This option may be useful in fullscreen mode.
**-nc** maintains previous UxPlay \< 1.45 behavior that does **not
close** the video window when the the client sends the "Stop Mirroring"
diff --git a/lib/raop.c b/lib/raop.c
index c275dc1dd..41b2d2d84 100644
--- a/lib/raop.c
+++ b/lib/raop.c
@@ -64,7 +64,6 @@ struct raop_s {
uint8_t clientFPSdata;
int audio_delay_micros;
- int max_ntp_timeouts;
/* for temporary storage of pin during pair-pin start */
unsigned short pin;
@@ -554,7 +553,6 @@ raop_init(raop_callbacks_t *callbacks) {
/* initialize switch for display of client's streaming data records */
raop->clientFPSdata = 0;
- raop->max_ntp_timeouts = 0;
raop->audio_delay_micros = 250000;
raop->hls_support = false;
@@ -662,9 +660,6 @@ int raop_set_plist(raop_t *raop, const char *plist_item, const int value) {
} else if (strcmp(plist_item, "clientFPSdata") == 0) {
raop->clientFPSdata = (value ? 1 : 0);
if ((int) raop->clientFPSdata != value) retval = 1;
- } else if (strcmp(plist_item, "max_ntp_timeouts") == 0) {
- raop->max_ntp_timeouts = (value > 0 ? value : 0);
- if (raop->max_ntp_timeouts != value) retval = 1;
} else if (strcmp(plist_item, "audio_delay_micros") == 0) {
if (value >= 0 && value <= 10 * SECOND_IN_USECS) {
raop->audio_delay_micros = value;
diff --git a/lib/raop.h b/lib/raop.h
index e0a206205..97e7eb9e7 100644
--- a/lib/raop.h
+++ b/lib/raop.h
@@ -67,11 +67,12 @@ struct raop_callbacks_s {
void (*video_process)(void *cls, raop_ntp_t *ntp, video_decode_struct *data);
void (*video_pause)(void *cls);
void (*video_resume)(void *cls);
+ void (*conn_feedback) (void *cls);
/* Optional but recommended callback functions */
void (*conn_init)(void *cls);
void (*conn_destroy)(void *cls);
- void (*conn_reset) (void *cls, int timeouts, bool reset_video);
+ void (*conn_reset) (void *cls);
void (*conn_teardown)(void *cls, bool *teardown_96, bool *teardown_110 );
void (*audio_flush)(void *cls);
void (*video_flush)(void *cls);
diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h
index 3b2c83880..0bc980ea8 100644
--- a/lib/raop_handlers.h
+++ b/lib/raop_handlers.h
@@ -742,7 +742,7 @@ raop_handler_setup(raop_conn_t *conn,
}
conn->raop_ntp = raop_ntp_init(conn->raop->logger, &conn->raop->callbacks, remote,
conn->remotelen, (unsigned short) timing_rport, &time_protocol);
- raop_ntp_start(conn->raop_ntp, &timing_lport, conn->raop->max_ntp_timeouts);
+ raop_ntp_start(conn->raop_ntp, &timing_lport);
conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp,
remote, conn->remotelen, aeskey, aesiv);
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks,
@@ -983,6 +983,8 @@ raop_handler_feedback(raop_conn_t *conn,
char **response_data, int *response_datalen)
{
logger_log(conn->raop->logger, LOGGER_DEBUG, "raop_handler_feedback");
+ /* register receipt of client's "heartbeat" signal */
+ conn->raop->callbacks.conn_feedback(conn->raop->callbacks.cls);
}
static void
diff --git a/lib/raop_ntp.c b/lib/raop_ntp.c
index 1a932e41f..026f70b7d 100644
--- a/lib/raop_ntp.c
+++ b/lib/raop_ntp.c
@@ -58,8 +58,6 @@ struct raop_ntp_s {
logger_t *logger;
raop_callbacks_t callbacks;
- int max_ntp_timeouts;
-
thread_handle_t thread;
mutex_handle_t run_mutex;
@@ -94,6 +92,8 @@ struct raop_ntp_s {
int tsock;
timing_protocol_t time_protocol;
+ bool client_time_received;
+
};
@@ -153,6 +153,7 @@ raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const c
raop_ntp->logger = logger;
memcpy(&raop_ntp->callbacks, callbacks, sizeof(raop_callbacks_t));
raop_ntp->timing_rport = timing_rport;
+ raop_ntp->client_time_received = false;
if (raop_ntp_parse_remote(raop_ntp, remote, remote_addr_len) < 0) {
free(raop_ntp);
@@ -274,8 +275,6 @@ raop_ntp_thread(void *arg)
};
raop_ntp_data_t data_sorted[RAOP_NTP_DATA_COUNT];
const unsigned two_pow_n[RAOP_NTP_DATA_COUNT] = {2, 4, 8, 16, 32, 64, 128, 256};
- int timeout_counter = 0;
- bool conn_reset = false;
bool logger_debug = (logger_get_level(raop_ntp->logger) >= LOGGER_DEBUG);
while (1) {
@@ -308,20 +307,15 @@ raop_ntp_thread(void *arg)
// Read response
response_len = recvfrom(raop_ntp->tsock, (char *)response, sizeof(response), 0, NULL, NULL);
if (response_len < 0) {
- timeout_counter++;
char time[30];
- int level = (timeout_counter == 1 ? LOGGER_DEBUG : LOGGER_ERR);
ntp_timestamp_to_time(send_time, time, sizeof(time));
- logger_log(raop_ntp->logger, level, "raop_ntp receive timeout %d (limit %d) (request sent %s)",
- timeout_counter, raop_ntp->max_ntp_timeouts, time);
- if (timeout_counter == raop_ntp->max_ntp_timeouts) {
- conn_reset = true; /* client is no longer responding */
- break;
- }
+ logger_log(raop_ntp->logger, LOGGER_DEBUG , "raop_ntp receive timeout (request sent %s)", time);
} else {
+ if (!raop_ntp->client_time_received) {
+ raop_ntp->client_time_received = true;
+ }
//local time of the server when the NTP response packet returns
int64_t t3 = (int64_t) raop_ntp_get_local_time(raop_ntp);
- timeout_counter = 0;
// Local time of the server when the NTP request packet leaves the server
int64_t t0 = (int64_t) byteutils_get_ntp_timestamp(response, 8);
@@ -391,15 +385,11 @@ raop_ntp_thread(void *arg)
MUTEX_UNLOCK(raop_ntp->run_mutex);
logger_log(raop_ntp->logger, LOGGER_DEBUG, "raop_ntp exiting thread");
- if (conn_reset && raop_ntp->callbacks.conn_reset) {
- const bool video_reset = false; /* leave "frozen video" in place */
- raop_ntp->callbacks.conn_reset(raop_ntp->callbacks.cls, timeout_counter, video_reset);
- }
return 0;
}
void
-raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport, int max_ntp_timeouts)
+raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport)
{
logger_log(raop_ntp->logger, LOGGER_DEBUG, "raop_ntp starting time");
int use_ipv6 = 0;
@@ -407,7 +397,6 @@ raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport, int max_ntp_t
assert(raop_ntp);
assert(timing_lport);
- raop_ntp->max_ntp_timeouts = max_ntp_timeouts;
raop_ntp->timing_lport = *timing_lport;
MUTEX_LOCK(raop_ntp->run_mutex);
diff --git a/lib/raop_ntp.h b/lib/raop_ntp.h
index bf2f5bb24..f829f4e42 100644
--- a/lib/raop_ntp.h
+++ b/lib/raop_ntp.h
@@ -27,7 +27,7 @@ typedef struct raop_ntp_s raop_ntp_t;
typedef enum timing_protocol_e { NTP, TP_NONE, TP_OTHER, TP_UNSPECIFIED } timing_protocol_t;
-void raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport, int max_ntp_timeouts);
+void raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport);
void raop_ntp_stop(raop_ntp_t *raop_ntp);
diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c
index bc88472ef..5b66194bc 100644
--- a/lib/raop_rtp_mirror.c
+++ b/lib/raop_rtp_mirror.c
@@ -804,9 +804,8 @@ raop_rtp_mirror_thread(void *arg)
MUTEX_UNLOCK(raop_rtp_mirror->run_mutex);
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror exiting TCP thread");
- if (conn_reset && raop_rtp_mirror->callbacks.conn_reset) {
- const bool video_reset = false; /* leave "frozen video" showing */
- raop_rtp_mirror->callbacks.conn_reset(raop_rtp_mirror->callbacks.cls, 0, video_reset);
+ if (conn_reset&& raop_rtp_mirror->callbacks.conn_reset) {
+ raop_rtp_mirror->callbacks.conn_reset(raop_rtp_mirror->callbacks.cls);
}
if (unsupported_codec) {
diff --git a/uxplay.1 b/uxplay.1
index ee8d3f6c0..f760cca71 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -106,7 +106,7 @@ UxPlay 1.71: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-ca\fI fn \fR In Airplay Audio (ALAC) mode, write cover-art to file fn.
.TP
-\fB\-reset\fR n Reset after 3n seconds client silence (default 5, 0=never).
+\fB\-reset\fR n Reset after n seconds client silence (default n=15, 0=never).
.TP
\fB\-nofreeze\fR Do NOT leave frozen screen in place after reset.
.TP
diff --git a/uxplay.cpp b/uxplay.cpp
index b96053d36..ea7a34cdc 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -70,7 +70,7 @@
#define DEFAULT_DEBUG_LOG false
#define LOWEST_ALLOWED_PORT 1024
#define HIGHEST_PORT 65535
-#define NTP_TIMEOUT_LIMIT 5
+#define MISSED_FEEDBACK_LIMIT 15
#define BT709_FIX "capssetter caps=\"video/x-h264, colorimetry=bt709\""
#define SRGB_FIX " ! video/x-raw,colorimetry=sRGB,format=RGB ! "
#ifdef FULL_RANGE_RGB_FIX
@@ -104,7 +104,6 @@ static std::string video_parser = "h264parse";
static std::string video_decoder = "decodebin";
static std::string video_converter = "videoconvert";
static bool show_client_FPS_data = false;
-static unsigned int max_ntp_timeouts = NTP_TIMEOUT_LIMIT;
static FILE *video_dumpfile = NULL;
static std::string video_dumpfile_name = "videodump";
static int video_dump_limit = 0;
@@ -156,6 +155,8 @@ static std::string url = "";
static guint gst_x11_window_id = 0;
static guint gst_hls_position_id = 0;
static bool preserve_connections = false;
+static guint missed_feedback_limit = MISSED_FEEDBACK_LIMIT;
+static guint missed_feedback = 0;
/* logging */
@@ -365,6 +366,28 @@ static void dump_video_to_file(unsigned char *data, int datalen) {
}
}
+static gboolean feedback_callback(gpointer loop) {
+ if (open_connections) {
+ if (missed_feedback_limit && missed_feedback > missed_feedback_limit) {
+ LOGI("***ERROR lost connection with client (network problem?)");
+ LOGI("%u missed client feedback signals exceeds limit of %u", missed_feedback, missed_feedback_limit);
+ LOGI(" Sometimes the network connection may recover after a longer delay:\n"
+ " the default limit n = %d seconds, can be changed with the \"-reset n\" option", MISSED_FEEDBACK_LIMIT);
+ if (!nofreeze) {
+ close_window = false; /* leave "frozen" window open if reset_video is false */
+ }
+ raop_stop(raop);
+ reset_loop = true;
+ } else if (missed_feedback > 2) {
+ LOGE("%u missed client feedback signals (expected once per second); client may be offline", missed_feedback);
+ }
+ missed_feedback++;
+ } else {
+ missed_feedback = 0;
+ }
+ return TRUE;
+}
+
static gboolean reset_callback(gpointer loop) {
if (reset_loop) {
g_main_loop_quit((GMainLoop *) loop);
@@ -435,6 +458,8 @@ static void main_loop() {
gst_bus_watch_id[i] = (guint) video_renderer_listen((void *)loop, i);
}
}
+ missed_feedback = 0;
+ guint feedback_watch_id = g_timeout_add_seconds(1, (GSourceFunc) feedback_callback, (gpointer) loop);
guint reset_watch_id = g_timeout_add(100, (GSourceFunc) reset_callback, (gpointer) loop);
guint video_reset_watch_id = g_timeout_add(100, (GSourceFunc) video_reset_callback, (gpointer) loop);
guint sigterm_watch_id = g_unix_signal_add(SIGTERM, (GSourceFunc) sigterm_callback, (gpointer) loop);
@@ -449,6 +474,7 @@ static void main_loop() {
if (sigterm_watch_id > 0) g_source_remove(sigterm_watch_id);
if (reset_watch_id > 0) g_source_remove(reset_watch_id);
if (video_reset_watch_id > 0) g_source_remove(video_reset_watch_id);
+ if (feedback_watch_id > 0) g_source_remove(feedback_watch_id);
g_main_loop_unref(loop);
}
@@ -657,7 +683,7 @@ static void print_info (char *name) {
printf("-as 0 (or -a) Turn audio off, streamed video only\n");
printf("-al x Audio latency in seconds (default 0.25) reported to client.\n");
printf("-ca