Allow sps to deal with duplicate RTSP messages like RECORD and SETUP. Add some diagnostics to the player.
This commit is contained in:
@@ -1659,8 +1659,6 @@ static void *player_thread_func(void *arg) {
|
||||
player_volume(config.airplay_volume, conn);
|
||||
int64_t frames_to_drop = 0;
|
||||
// debug(1, "Play begin");
|
||||
if (play_number % 100 == 0)
|
||||
debug(3, "Play frame %d.", play_number);
|
||||
while (!conn->player_thread_please_stop) {
|
||||
abuf_t *inframe = buffer_get_frame(conn);
|
||||
if (inframe) {
|
||||
@@ -1668,9 +1666,11 @@ static void *player_thread_func(void *arg) {
|
||||
inbuflength = inframe->length;
|
||||
if (inbuf) {
|
||||
play_number++;
|
||||
if (play_number % 100 == 0)
|
||||
debug(3, "Play frame %d.", play_number);
|
||||
conn->play_number_after_flush++;
|
||||
if (inframe->timestamp == 0) {
|
||||
// debug(1,"Player has a supplied silent frame.");
|
||||
debug(3, "Player has a supplied silent frame.");
|
||||
conn->last_seqno_read = (SUCCESSOR(conn->last_seqno_read) &
|
||||
0xffff); // manage the packet out of sequence minder
|
||||
config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
|
||||
@@ -1685,7 +1685,9 @@ static void *player_thread_func(void *arg) {
|
||||
*/
|
||||
config.output->play(silence, conn->max_frames_per_packet * conn->output_sample_ratio);
|
||||
} else if (frames_to_drop) {
|
||||
// debug(2,"%lld frames to drop.",frames_to_drop);
|
||||
if (frames_to_drop > 3 * config.output_rate)
|
||||
warn("Very large number of frames to drop: %" PRId64 ".");
|
||||
debug(3, "%" PRId64 " frames to drop.", frames_to_drop);
|
||||
frames_to_drop -= inframe->length;
|
||||
if (frames_to_drop < 0)
|
||||
frames_to_drop = 0;
|
||||
@@ -1885,6 +1887,9 @@ static void *player_thread_func(void *arg) {
|
||||
if ((config.no_sync == 0) && (inframe->timestamp != 0) &&
|
||||
(!conn->player_thread_please_stop) && (config.resyncthreshold > 0.0) &&
|
||||
(abs_sync_error > config.resyncthreshold * config.output_rate)) {
|
||||
if (abs_sync_error > 3 * config.output_rate) {
|
||||
warn("The sync error is very large: %" PRId64 " frames.");
|
||||
}
|
||||
sync_error_out_of_bounds++;
|
||||
} else {
|
||||
sync_error_out_of_bounds = 0;
|
||||
|
||||
@@ -141,6 +141,12 @@ typedef struct {
|
||||
int control_socket; // our local [server] control socket
|
||||
int timing_socket; // local timing socket
|
||||
|
||||
uint16_t remote_control_port;
|
||||
uint16_t remote_timing_port;
|
||||
uint16_t local_audio_port;
|
||||
uint16_t local_control_port;
|
||||
uint16_t local_timing_port;
|
||||
|
||||
int64_t latency_delayed_timestamp; // this is for debugging only...
|
||||
int64_t reference_timestamp;
|
||||
uint64_t reference_timestamp_time;
|
||||
|
||||
@@ -691,9 +691,10 @@ void *rtp_timing_receiver(void *arg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id, int *sock) {
|
||||
static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id,
|
||||
int *sock) {
|
||||
// look for a port in the range, if any was specified.
|
||||
int desired_port = config.udp_port_base;
|
||||
uint16_t desired_port = config.udp_port_base;
|
||||
int ret;
|
||||
|
||||
int local_socket = socket(ip_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
@@ -731,7 +732,7 @@ static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_
|
||||
"check for restrictive firewall settings or a bad router!");
|
||||
}
|
||||
|
||||
int sport;
|
||||
uint16_t sport;
|
||||
SOCKADDR local;
|
||||
socklen_t local_len = sizeof(local);
|
||||
getsockname(local_socket, (struct sockaddr *)&local, &local_len);
|
||||
@@ -751,123 +752,128 @@ static int bind_port(int ip_family, const char *self_ip_address, uint32_t scope_
|
||||
return sport;
|
||||
}
|
||||
|
||||
void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int cport, int tport, int *lsport, int *lcport,
|
||||
int *ltport, rtsp_conn_info *conn) {
|
||||
void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport,
|
||||
rtsp_conn_info *conn) {
|
||||
|
||||
// this gets the local and remote ip numbers (and ports used for the TCD stuff)
|
||||
// we use the local stuff to specify the address we are coming from and
|
||||
// we use the remote stuff to specify where we're goint to
|
||||
|
||||
if (conn->rtp_running)
|
||||
die("rtp_setup called with active stream!");
|
||||
warn("rtp_setup has been called with al already-active stream -- ignored. Possible duplicate "
|
||||
"SETUP call?");
|
||||
else {
|
||||
|
||||
debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport);
|
||||
debug(2, "rtp_setup: cport=%d tport=%d.", cport, tport);
|
||||
|
||||
// print out what we know about the client
|
||||
void *client_addr = NULL, *self_addr = NULL;
|
||||
// int client_port, self_port;
|
||||
// char client_port_str[64];
|
||||
// char self_addr_str[64];
|
||||
// print out what we know about the client
|
||||
void *client_addr = NULL, *self_addr = NULL;
|
||||
// int client_port, self_port;
|
||||
// char client_port_str[64];
|
||||
// char self_addr_str[64];
|
||||
|
||||
conn->connection_ip_family =
|
||||
remote->SAFAMILY; // keep information about the kind of ip of the client
|
||||
conn->connection_ip_family =
|
||||
remote->SAFAMILY; // keep information about the kind of ip of the client
|
||||
|
||||
#ifdef AF_INET6
|
||||
if (conn->connection_ip_family == AF_INET6) {
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote;
|
||||
client_addr = &(sa6->sin6_addr);
|
||||
// client_port = ntohs(sa6->sin6_port);
|
||||
sa6 = (struct sockaddr_in6 *)local;
|
||||
self_addr = &(sa6->sin6_addr);
|
||||
// self_port = ntohs(sa6->sin6_port);
|
||||
conn->self_scope_id = sa6->sin6_scope_id;
|
||||
}
|
||||
if (conn->connection_ip_family == AF_INET6) {
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)remote;
|
||||
client_addr = &(sa6->sin6_addr);
|
||||
// client_port = ntohs(sa6->sin6_port);
|
||||
sa6 = (struct sockaddr_in6 *)local;
|
||||
self_addr = &(sa6->sin6_addr);
|
||||
// self_port = ntohs(sa6->sin6_port);
|
||||
conn->self_scope_id = sa6->sin6_scope_id;
|
||||
}
|
||||
#endif
|
||||
if (conn->connection_ip_family == AF_INET) {
|
||||
struct sockaddr_in *sa4 = (struct sockaddr_in *)remote;
|
||||
client_addr = &(sa4->sin_addr);
|
||||
// client_port = ntohs(sa4->sin_port);
|
||||
sa4 = (struct sockaddr_in *)local;
|
||||
self_addr = &(sa4->sin_addr);
|
||||
// self_port = ntohs(sa4->sin_port);
|
||||
}
|
||||
if (conn->connection_ip_family == AF_INET) {
|
||||
struct sockaddr_in *sa4 = (struct sockaddr_in *)remote;
|
||||
client_addr = &(sa4->sin_addr);
|
||||
// client_port = ntohs(sa4->sin_port);
|
||||
sa4 = (struct sockaddr_in *)local;
|
||||
self_addr = &(sa4->sin_addr);
|
||||
// self_port = ntohs(sa4->sin_port);
|
||||
}
|
||||
|
||||
inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string,
|
||||
sizeof(conn->client_ip_string));
|
||||
inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string,
|
||||
sizeof(conn->self_ip_string));
|
||||
inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string,
|
||||
sizeof(conn->client_ip_string));
|
||||
inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string,
|
||||
sizeof(conn->self_ip_string));
|
||||
|
||||
debug(2, "Set up play connection from %s to self at %s on RTSP conversation thread %d.",
|
||||
conn->client_ip_string, conn->self_ip_string, conn->connection_number);
|
||||
debug(2, "Set up play connection from %s to self at %s on RTSP conversation thread %d.",
|
||||
conn->client_ip_string, conn->self_ip_string, conn->connection_number);
|
||||
|
||||
// set up a the record of the remote's control socket
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *servinfo;
|
||||
// set up a the record of the remote's control socket
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *servinfo;
|
||||
|
||||
memset(&conn->rtp_client_control_socket, 0, sizeof(conn->rtp_client_control_socket));
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = conn->connection_ip_family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
char portstr[20];
|
||||
snprintf(portstr, 20, "%d", cport);
|
||||
if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0)
|
||||
die("Can't get address of client's control port");
|
||||
memset(&conn->rtp_client_control_socket, 0, sizeof(conn->rtp_client_control_socket));
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = conn->connection_ip_family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
char portstr[20];
|
||||
snprintf(portstr, 20, "%d", cport);
|
||||
if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0)
|
||||
die("Can't get address of client's control port");
|
||||
|
||||
#ifdef AF_INET6
|
||||
if (servinfo->ai_family == AF_INET6) {
|
||||
memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
|
||||
// ensure the scope id matches that of remote. this is needed for link-local addresses.
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_control_socket;
|
||||
sa6->sin6_scope_id = conn->self_scope_id;
|
||||
} else
|
||||
if (servinfo->ai_family == AF_INET6) {
|
||||
memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
|
||||
// ensure the scope id matches that of remote. this is needed for link-local addresses.
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_control_socket;
|
||||
sa6->sin6_scope_id = conn->self_scope_id;
|
||||
} else
|
||||
#endif
|
||||
memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
|
||||
freeaddrinfo(servinfo);
|
||||
memcpy(&conn->rtp_client_control_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
|
||||
freeaddrinfo(servinfo);
|
||||
|
||||
// set up a the record of the remote's timing socket
|
||||
memset(&conn->rtp_client_timing_socket, 0, sizeof(conn->rtp_client_timing_socket));
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = conn->connection_ip_family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
snprintf(portstr, 20, "%d", tport);
|
||||
if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0)
|
||||
die("Can't get address of client's timing port");
|
||||
// set up a the record of the remote's timing socket
|
||||
memset(&conn->rtp_client_timing_socket, 0, sizeof(conn->rtp_client_timing_socket));
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = conn->connection_ip_family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
snprintf(portstr, 20, "%d", tport);
|
||||
if (getaddrinfo(conn->client_ip_string, portstr, &hints, &servinfo) != 0)
|
||||
die("Can't get address of client's timing port");
|
||||
#ifdef AF_INET6
|
||||
if (servinfo->ai_family == AF_INET6) {
|
||||
memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
|
||||
// ensure the scope id matches that of remote. this is needed for link-local addresses.
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_timing_socket;
|
||||
sa6->sin6_scope_id = conn->self_scope_id;
|
||||
} else
|
||||
if (servinfo->ai_family == AF_INET6) {
|
||||
memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in6));
|
||||
// ensure the scope id matches that of remote. this is needed for link-local addresses.
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->rtp_client_timing_socket;
|
||||
sa6->sin6_scope_id = conn->self_scope_id;
|
||||
} else
|
||||
#endif
|
||||
memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
|
||||
freeaddrinfo(servinfo);
|
||||
memcpy(&conn->rtp_client_timing_socket, servinfo->ai_addr, sizeof(struct sockaddr_in));
|
||||
freeaddrinfo(servinfo);
|
||||
|
||||
// now, we open three sockets -- one for the audio stream, one for the timing and one for the
|
||||
// control
|
||||
// now, we open three sockets -- one for the audio stream, one for the timing and one for the
|
||||
// control
|
||||
conn->remote_control_port = cport;
|
||||
conn->remote_timing_port = tport;
|
||||
|
||||
*lsport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
|
||||
&conn->audio_socket);
|
||||
*lcport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
|
||||
&conn->control_socket);
|
||||
*ltport = bind_port(conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id,
|
||||
&conn->timing_socket);
|
||||
conn->local_audio_port = bind_port(conn->connection_ip_family, conn->self_ip_string,
|
||||
conn->self_scope_id, &conn->audio_socket);
|
||||
conn->local_control_port = bind_port(conn->connection_ip_family, conn->self_ip_string,
|
||||
conn->self_scope_id, &conn->control_socket);
|
||||
conn->local_timing_port = bind_port(conn->connection_ip_family, conn->self_ip_string,
|
||||
conn->self_scope_id, &conn->timing_socket);
|
||||
|
||||
debug(2, "listening for audio, control and timing on ports %d, %d, %d.", *lsport, *lcport,
|
||||
*ltport);
|
||||
debug(2, "listening for audio, control and timing on ports %d, %d, %d.", conn->local_audio_port,
|
||||
conn->local_control_port, conn->local_timing_port);
|
||||
|
||||
conn->reference_timestamp = 0;
|
||||
// pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
|
||||
// pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
|
||||
// pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
|
||||
conn->reference_timestamp = 0;
|
||||
// pthread_create(&rtp_audio_thread, NULL, &rtp_audio_receiver, NULL);
|
||||
// pthread_create(&rtp_control_thread, NULL, &rtp_control_receiver, NULL);
|
||||
// pthread_create(&rtp_timing_thread, NULL, &rtp_timing_receiver, NULL);
|
||||
|
||||
conn->request_sent = 0;
|
||||
conn->rtp_running = 1;
|
||||
conn->request_sent = 0;
|
||||
conn->rtp_running = 1;
|
||||
|
||||
#ifdef CONFIG_METADATA
|
||||
send_ssnc_metadata('clip', strdup(conn->client_ip_string), strlen(conn->client_ip_string), 1);
|
||||
send_ssnc_metadata('svip', strdup(conn->self_ip_string), strlen(conn->self_ip_string), 1);
|
||||
send_ssnc_metadata('clip', strdup(conn->client_ip_string), strlen(conn->client_ip_string), 1);
|
||||
send_ssnc_metadata('svip', strdup(conn->self_ip_string), strlen(conn->self_ip_string), 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void get_reference_timestamp_stuff(int64_t *timestamp, uint64_t *timestamp_time,
|
||||
|
||||
@@ -12,8 +12,7 @@ void *rtp_audio_receiver(void *arg);
|
||||
void *rtp_control_receiver(void *arg);
|
||||
void *rtp_timing_receiver(void *arg);
|
||||
|
||||
void rtp_setup(SOCKADDR *local, SOCKADDR *remote, int controlport, int timingport,
|
||||
int *local_server_port, int *local_control_port, int *local_timing_port,
|
||||
void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t controlport, uint16_t timingport,
|
||||
rtsp_conn_info *conn);
|
||||
void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn);
|
||||
void rtp_request_client_pause(rtsp_conn_info *conn); // ask the client to pause
|
||||
|
||||
@@ -668,7 +668,10 @@ static void msg_write_response(int fd, rtsp_message *resp) {
|
||||
static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
|
||||
debug(2, "Connection %d: RECORD", conn->connection_number);
|
||||
|
||||
player_play(conn); // the thread better be 0
|
||||
if (conn->player_thread)
|
||||
warn("Duplicate RECORD message -- ignored");
|
||||
else
|
||||
player_play(conn); // the thread better be 0
|
||||
|
||||
resp->respcode = 200;
|
||||
// I think this is for telling the client what the absolute minimum latency
|
||||
@@ -700,7 +703,6 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
|
||||
}
|
||||
}
|
||||
}
|
||||
// usleep(500000);
|
||||
}
|
||||
|
||||
static void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
|
||||
@@ -724,11 +726,9 @@ static void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_m
|
||||
debug(3,
|
||||
"TEARDOWN: synchronously terminating the player thread of RTSP conversation thread %d (2).",
|
||||
conn->connection_number);
|
||||
// if (rtsp_playing()) {
|
||||
player_stop(conn);
|
||||
debug(3, "TEARDOWN: successful termination of playing thread of RTSP conversation thread %d.",
|
||||
conn->connection_number);
|
||||
//}
|
||||
}
|
||||
|
||||
static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
|
||||
@@ -763,8 +763,7 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
|
||||
static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
|
||||
debug(2, "Connection %d: SETUP", conn->connection_number);
|
||||
int cport, tport;
|
||||
int lsport, lcport, ltport;
|
||||
uint16_t cport, tport;
|
||||
|
||||
char *ar = msg_get_header(req, "Active-Remote");
|
||||
if (ar) {
|
||||
@@ -787,48 +786,52 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
}
|
||||
|
||||
char *hdr = msg_get_header(req, "Transport");
|
||||
if (!hdr)
|
||||
if (!hdr) {
|
||||
debug(1, "SETUP doesn't contain a Transport header.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
char *p;
|
||||
p = strstr(hdr, "control_port=");
|
||||
if (!p)
|
||||
if (!p) {
|
||||
debug(1, "SETUP doesn't specify a control_port.");
|
||||
goto error;
|
||||
}
|
||||
p = strchr(p, '=') + 1;
|
||||
cport = atoi(p);
|
||||
|
||||
p = strstr(hdr, "timing_port=");
|
||||
if (!p)
|
||||
if (!p) {
|
||||
debug(1, "SETUP doesn't specify a timing_port.");
|
||||
goto error;
|
||||
}
|
||||
p = strchr(p, '=') + 1;
|
||||
tport = atoi(p);
|
||||
|
||||
// rtsp_take_player();
|
||||
rtp_setup(&conn->local, &conn->remote, cport, tport, &lsport, &lcport, <port, conn);
|
||||
if (!lsport)
|
||||
goto error;
|
||||
char *q;
|
||||
p = strstr(hdr, "control_port=");
|
||||
if (p) {
|
||||
q = strchr(p, ';'); // get past the control port entry
|
||||
*p++ = 0;
|
||||
if (q++)
|
||||
strcat(hdr, q); // should unsplice the control port entry
|
||||
if (conn->rtp_running) {
|
||||
if ((conn->remote_control_port != cport) || (conn->remote_timing_port != tport)) {
|
||||
warn("Duplicate SETUP message with different control (old %u, new %u) or timing (old %u, new "
|
||||
"%u) ports! This is probably fatal!",
|
||||
conn->remote_control_port, cport, conn->remote_timing_port, tport);
|
||||
} else {
|
||||
warn("Duplicate SETUP message with the same control (%u) and timing (%u) ports. This is "
|
||||
"probably not fatal.",
|
||||
conn->remote_control_port, conn->remote_timing_port);
|
||||
}
|
||||
} else {
|
||||
rtp_setup(&conn->local, &conn->remote, cport, tport, conn);
|
||||
}
|
||||
p = strstr(hdr, "timing_port=");
|
||||
if (p) {
|
||||
q = strchr(p, ';'); // get past the timing port entry
|
||||
*p++ = 0;
|
||||
if (q++)
|
||||
strcat(hdr, q); // should unsplice the timing port entry
|
||||
if (conn->local_audio_port == 0) {
|
||||
debug(1, "SETUP seems to specify a null audio port.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
char resphdr[256] = "";
|
||||
snprintf(resphdr, sizeof(resphdr), "RTP/AVP/"
|
||||
"UDP;unicast;interleaved=0-1;mode=record;control_port=%d;"
|
||||
"timing_port=%d;server_"
|
||||
"port=%d",
|
||||
lcport, ltport, lsport);
|
||||
"UDP;unicast;interleaved=0-1;mode=record;control_port=%d;"
|
||||
"timing_port=%d;server_"
|
||||
"port=%d",
|
||||
conn->local_control_port, conn->local_timing_port, conn->local_audio_port);
|
||||
|
||||
msg_add_header(resp, "Transport", resphdr);
|
||||
|
||||
@@ -1435,7 +1438,7 @@ static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_messag
|
||||
have_the_player = 1;
|
||||
} else if ((playing_conn) &&
|
||||
(playing_conn->connection_number == conn->connection_number)) { // duplicate ANNOUNCE
|
||||
debug(1, "Duplicate ANNOUNCE, by the look of it!");
|
||||
warn("Duplicate ANNOUNCE, by the look of it!");
|
||||
have_the_player = 1;
|
||||
} else {
|
||||
int should_wait = 0;
|
||||
|
||||
Reference in New Issue
Block a user