mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-14 00:04:13 +09:00
fixed incorrect framerate and missing maxfps plist settings in raop_handler.h
and added option to set these in the API through raop_set_display (renamed from raop_set_display_size).. Updated uxplay.cpp to include these new options. Moved version slightly to UxPlay-1.31 tested: confirmed to work using gstreamer videosink "fpsdisplaysink" to explicitly show reduction of streaming fps when set below 30 fps.
This commit is contained in:
@@ -14,10 +14,7 @@ include_directories(${X11_INCLUDE_DIR})
|
||||
endif (ZOOMFIX)
|
||||
|
||||
|
||||
if (NO_AVAHI_FIX)
|
||||
else()
|
||||
add_definitions(-DSUPPRESS_AVAHI_COMPAT_WARNING)
|
||||
endif(NO_AVAHI_FIX)
|
||||
|
||||
add_subdirectory(lib/llhttp)
|
||||
add_subdirectory(lib/playfair)
|
||||
|
||||
35
README.md
35
README.md
@@ -77,7 +77,21 @@ AirPlay services to your iPad, iPhone etc.
|
||||
**-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height,
|
||||
in pixels). (This may be a
|
||||
request made to the AirPlay client, and perhaps will not
|
||||
be the final resolution you get).
|
||||
be the final resolution you get.) w and h are whole numbers with four
|
||||
digits or less.
|
||||
|
||||
**-s wxh@r** As above, but also informs the AirPlay client about the screen
|
||||
refresh rate of the display. Default is r=60 (60 Hz); r is a whole number
|
||||
with three digits or less.
|
||||
|
||||
**-fps n ** sets a maximum frame rate (in frames per second) for the AirPlay
|
||||
client to stream video; n must be a whole number with 3 digits or less.
|
||||
(The client may choose to serve video at any frame rate lower
|
||||
than this; default is 30 fps.) Values greater than the display
|
||||
refresh rate are ignored, and replaced by the refresh rate. A setting
|
||||
below 30 fps might be useful to reduce latency if you are running more than
|
||||
one instance of uxplay at the same time.
|
||||
|
||||
|
||||
**-p** allows you to select the network ports used by UxPlay (these need
|
||||
to be opened if the server is behind a firewall). By itself, -p sets
|
||||
@@ -104,16 +118,16 @@ Also: image transforms that had been added to RPiPlay have been ported to UxPlay
|
||||
**-r {R|L}** 90 degree Right (clockwise) or Left (counter-clockwise)
|
||||
rotations; these are carried out after any **-f** transforms.
|
||||
|
||||
# New features available: (v 1.3 2021-08)
|
||||
# New features available: (v 1.31 2021-08-09)
|
||||
|
||||
1. Updates of the RAOP (AirPlay protocol, not AirPlay 2) collection of codes maintained
|
||||
1. Updates of the RAOP (AirPlay protocol) collection of codes maintained
|
||||
at https://github.com/FD-/RPiPlay.git so it is current as of 2021-08-01,
|
||||
adding all changes since the original release of UxPlay by antimof.
|
||||
This involved crypto updates, replacement
|
||||
of the included plist library by the system-installed version, and a change
|
||||
over to a library llhttp for http parsing.
|
||||
|
||||
2. Added the -s, -p, -m, -r and -f options.
|
||||
2. Added the -s, -p, -m, -r, -f, and -fps options.
|
||||
|
||||
3. If "cmake -DZOOMFIX=ON .." is run before compiling,
|
||||
the mirrored window is now visible to screen-sharing applications such as
|
||||
@@ -131,9 +145,10 @@ The program uxplay terminates when Ctrl-C is typed in the terminal window.
|
||||
5. In principle, multiple instances of uxplay can be run simultaneously
|
||||
using the **-m** (generate random MAC address) option to give each a
|
||||
different ("local" as opposed to "universal") MAC address.
|
||||
If the **-p** option is used, they also need separate network port choices.
|
||||
If the **-p [n]** option is used, they also need separate network port choices.
|
||||
(However, there may be a large latency, and running two instances of uxplay
|
||||
simultaneously on the same computer may not be very useful.)
|
||||
simultaneously on the same computer may not be very usefu;l using -fps option
|
||||
to force streaming framerates below 30fps could be useful.)
|
||||
|
||||
6. Without the **-p** [n] option, uxplay makes a random dynamic assignment of
|
||||
network ports. This will not work if most ports are closed by a firewall.
|
||||
@@ -148,14 +163,16 @@ with 4 or less digits. It seems that the width and height may be negotiated
|
||||
with the AirPlay client, so this may not be the actual screen geometry that
|
||||
displays.
|
||||
|
||||
8. The title on the GStreamer display window is now is the Airplay server name
|
||||
8. The title on the GStreamer display window is now is the AirPlay server name
|
||||
(default "UxPlay", but can be changed with option **-n**), rather than the program
|
||||
name "uxplay" (note the difference in capitalization).
|
||||
|
||||
9. The avahi_compat "nag" warning on startup is suppressed, by placing
|
||||
"AVAHI_COMPAT_NOWARN=1" into the runtime environment when uxplay starts.
|
||||
(This uses a call to putenv(), If for any reason you dont want this fix,
|
||||
run cmake as "cmake -DNO_AVAHI_FIX=ON -DZOOMFIX=ON .. ").
|
||||
(This uses a call to putenv() in a form that is believed to be safe against
|
||||
memory leaks, at least in modern Linux; if for any reason you don't want
|
||||
this fix, comment out the line in CMakeLists.txt that activates it when uxplay
|
||||
is compiled.)
|
||||
|
||||
# Disclaimer
|
||||
|
||||
|
||||
21
lib/raop.c
21
lib/raop.c
@@ -52,7 +52,8 @@ struct raop_s {
|
||||
|
||||
unsigned short display_width;
|
||||
unsigned short display_height;
|
||||
|
||||
unsigned short display_refresh_rate;
|
||||
unsigned short display_max_fps;
|
||||
};
|
||||
|
||||
struct raop_conn_s {
|
||||
@@ -319,9 +320,11 @@ raop_init(int max_clients, raop_callbacks_t *callbacks) {
|
||||
raop->data_lport = 0;
|
||||
raop->mirror_data_lport = 0;
|
||||
|
||||
/* initialize display width, height */
|
||||
/* initialize display width, height, refresh_rate, max_fps */
|
||||
raop->display_width = 1920;
|
||||
raop->display_height = 1080;
|
||||
raop->display_refresh_rate = 60;
|
||||
raop->display_max_fps = 30;
|
||||
|
||||
return raop;
|
||||
}
|
||||
@@ -354,11 +357,17 @@ raop_set_log_level(raop_t *raop, int level) {
|
||||
logger_set_level(raop->logger, level);
|
||||
}
|
||||
|
||||
void raop_set_display_size(raop_t *raop, unsigned short width, unsigned short height){
|
||||
assert(raop);
|
||||
void raop_set_display(raop_t *raop, unsigned short width, unsigned short height,
|
||||
unsigned short refresh_rate, unsigned short max_fps){
|
||||
assert(raop);
|
||||
|
||||
if (width) raop->display_width = width;
|
||||
if (height) raop->display_height = height;
|
||||
if (width) raop->display_width = width;
|
||||
if (height) raop->display_height = height;
|
||||
if (refresh_rate) raop->display_refresh_rate = refresh_rate;
|
||||
if (max_fps) raop->display_max_fps = max_fps;
|
||||
if (raop->display_max_fps > raop->display_refresh_rate) {
|
||||
raop->display_max_fps = raop->display_refresh_rate;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -54,7 +54,8 @@ RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks);
|
||||
|
||||
RAOP_API void raop_set_log_level(raop_t *raop, int level);
|
||||
RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls);
|
||||
RAOP_API void raop_set_display_size(raop_t *raop, unsigned short width, unsigned short height);
|
||||
RAOP_API void raop_set_display(raop_t *raop, unsigned short width, unsigned short height,
|
||||
unsigned short refresh_rate, unsigned short max_fps);
|
||||
RAOP_API void raop_set_port(raop_t *raop, unsigned short port);
|
||||
RAOP_API void raop_set_udp_ports(raop_t *raop, unsigned short port[3]);
|
||||
RAOP_API void raop_set_tcp_ports(raop_t *raop, unsigned short port[2]);
|
||||
|
||||
@@ -60,16 +60,16 @@ raop_handler_info(raop_conn_t *conn,
|
||||
plist_t audio_formats_node = plist_new_array();
|
||||
plist_t audio_format_0_node = plist_new_dict();
|
||||
plist_t audio_format_0_type_node = plist_new_uint(100);
|
||||
plist_t audio_format_0_audio_input_formats_node = plist_new_uint(67108860);
|
||||
plist_t audio_format_0_audio_output_formats_node = plist_new_uint(67108860);
|
||||
plist_t audio_format_0_audio_input_formats_node = plist_new_uint(0x3fffffc);
|
||||
plist_t audio_format_0_audio_output_formats_node = plist_new_uint(0x3fffffc);
|
||||
plist_dict_set_item(audio_format_0_node, "type", audio_format_0_type_node);
|
||||
plist_dict_set_item(audio_format_0_node, "audioInputFormats", audio_format_0_audio_input_formats_node);
|
||||
plist_dict_set_item(audio_format_0_node, "audioOutputFormats", audio_format_0_audio_output_formats_node);
|
||||
plist_array_append_item(audio_formats_node, audio_format_0_node);
|
||||
plist_t audio_format_1_node = plist_new_dict();
|
||||
plist_t audio_format_1_type_node = plist_new_uint(101);
|
||||
plist_t audio_format_1_audio_input_formats_node = plist_new_uint(67108860);
|
||||
plist_t audio_format_1_audio_output_formats_node = plist_new_uint(67108860);
|
||||
plist_t audio_format_1_audio_input_formats_node = plist_new_uint(0x3fffffc);
|
||||
plist_t audio_format_1_audio_output_formats_node = plist_new_uint(0x3fffffc);
|
||||
plist_dict_set_item(audio_format_1_node, "type", audio_format_1_type_node);
|
||||
plist_dict_set_item(audio_format_1_node, "audioInputFormats", audio_format_1_audio_input_formats_node);
|
||||
plist_dict_set_item(audio_format_1_node, "audioOutputFormats", audio_format_1_audio_output_formats_node);
|
||||
@@ -102,20 +102,20 @@ raop_handler_info(raop_conn_t *conn,
|
||||
|
||||
plist_t audio_latencies_node = plist_new_array();
|
||||
plist_t audio_latencies_0_node = plist_new_dict();
|
||||
plist_t audio_latencies_0_output_latency_micros_node = plist_new_uint(0);
|
||||
plist_t audio_latencies_0_output_latency_micros_node = plist_new_bool(0);
|
||||
plist_t audio_latencies_0_type_node = plist_new_uint(100);
|
||||
plist_t audio_latencies_0_audio_type_node = plist_new_string("default");
|
||||
plist_t audio_latencies_0_input_latency_micros_node = plist_new_uint(0);
|
||||
plist_t audio_latencies_0_input_latency_micros_node = plist_new_bool(0);
|
||||
plist_dict_set_item(audio_latencies_0_node, "outputLatencyMicros", audio_latencies_0_output_latency_micros_node);
|
||||
plist_dict_set_item(audio_latencies_0_node, "type", audio_latencies_0_type_node);
|
||||
plist_dict_set_item(audio_latencies_0_node, "audioType", audio_latencies_0_audio_type_node);
|
||||
plist_dict_set_item(audio_latencies_0_node, "inputLatencyMicros", audio_latencies_0_input_latency_micros_node);
|
||||
plist_array_append_item(audio_latencies_node, audio_latencies_0_node);
|
||||
plist_t audio_latencies_1_node = plist_new_dict();
|
||||
plist_t audio_latencies_1_output_latency_micros_node = plist_new_uint(0);
|
||||
plist_t audio_latencies_1_output_latency_micros_node = plist_new_bool(0);
|
||||
plist_t audio_latencies_1_type_node = plist_new_uint(101);
|
||||
plist_t audio_latencies_1_audio_type_node = plist_new_string("default");
|
||||
plist_t audio_latencies_1_input_latency_micros_node = plist_new_uint(0);
|
||||
plist_t audio_latencies_1_input_latency_micros_node = plist_new_bool(0);
|
||||
plist_dict_set_item(audio_latencies_1_node, "outputLatencyMicros", audio_latencies_1_output_latency_micros_node);
|
||||
plist_dict_set_item(audio_latencies_1_node, "type", audio_latencies_1_type_node);
|
||||
plist_dict_set_item(audio_latencies_1_node, "audioType", audio_latencies_1_audio_type_node);
|
||||
@@ -139,7 +139,8 @@ raop_handler_info(raop_conn_t *conn,
|
||||
plist_t displays_0_width_pixels_node = plist_new_uint(conn->raop->display_width);
|
||||
plist_t displays_0_height_pixels_node = plist_new_uint(conn->raop->display_height);
|
||||
plist_t displays_0_rotation_node = plist_new_bool(0);
|
||||
plist_t displays_0_refresh_rate_node = plist_new_real(1.0 / 60.0);
|
||||
plist_t displays_0_refresh_rate_node = plist_new_uint(conn->raop->display_refresh_rate);
|
||||
plist_t displays_0_max_fps_node = plist_new_uint(conn->raop->display_max_fps);
|
||||
plist_t displays_0_overscanned_node = plist_new_bool(1);
|
||||
plist_t displays_0_features = plist_new_uint(14);
|
||||
|
||||
@@ -152,6 +153,7 @@ raop_handler_info(raop_conn_t *conn,
|
||||
plist_dict_set_item(displays_0_node, "heightPixels", displays_0_height_pixels_node);
|
||||
plist_dict_set_item(displays_0_node, "rotation", displays_0_rotation_node);
|
||||
plist_dict_set_item(displays_0_node, "refreshRate", displays_0_refresh_rate_node);
|
||||
plist_dict_set_item(displays_0_node, "maxFPS", displays_0_max_fps_node);
|
||||
plist_dict_set_item(displays_0_node, "overscanned", displays_0_overscanned_node);
|
||||
plist_dict_set_item(displays_0_node, "features", displays_0_features);
|
||||
plist_array_append_item(displays_node, displays_0_node);
|
||||
|
||||
84
uxplay.cpp
84
uxplay.cpp
@@ -33,7 +33,7 @@
|
||||
#include "renderers/video_renderer.h"
|
||||
#include "renderers/audio_renderer.h"
|
||||
|
||||
#define VERSION "1.3"
|
||||
#define VERSION "1.31"
|
||||
|
||||
#define DEFAULT_NAME "UxPlay"
|
||||
#define DEFAULT_DEBUG_LOG false
|
||||
@@ -41,7 +41,7 @@
|
||||
#define HIGHEST_PORT 65535
|
||||
|
||||
|
||||
static int start_server (std::vector<char> hw_addr, std::string name, unsigned short display_size[2],
|
||||
static int start_server (std::vector<char> hw_addr, std::string name, unsigned short display[4],
|
||||
unsigned short tcp[2], unsigned short udp[3], videoflip_t videoflip[2],
|
||||
bool use_audio, bool debug_log);
|
||||
|
||||
@@ -116,7 +116,8 @@ static void print_info (char *name) {
|
||||
printf("Usage: %s [-n name] [-s wxh] [-p [n]]\n", name);
|
||||
printf("Options:\n");
|
||||
printf("-n name Specify the network name of the AirPlay server\n");
|
||||
printf("-s wxh Set display resolution: width w height h default 1920x1080\n");
|
||||
printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080@60\n");
|
||||
printf("-fps n Set maximum streaming fps, default 30 \n");
|
||||
printf("-f {H|V|I}Horizontal|Vertical flip, or both=Inversion=rotate 180 deg\n");
|
||||
printf("-r {R|L} rotate 90 degrees Right (cw) or Left (ccw)\n");
|
||||
printf("-p n Use fixed UDP+TCP network ports n:n+1:n+2. (n>1023)\n");
|
||||
@@ -127,9 +128,16 @@ static void print_info (char *name) {
|
||||
printf("-v/-h Displays this help and version information\n");
|
||||
}
|
||||
|
||||
static bool get_display_size (char *str, unsigned short *w, unsigned short *h) {
|
||||
// assume str = wxh is valid if w and h are positive decimal integers with less than 5 digits.
|
||||
static bool get_display_settings (char *str, unsigned short *w, unsigned short *h, unsigned short *r) {
|
||||
// assume str = wxh@r is valid if w and h are positive decimal integers
|
||||
// with no more than 5 digits, r no more than 3 digits.
|
||||
char *str1 = strchr(str,'x');
|
||||
char *str2 = strchr(str1,'@');
|
||||
if (str2) {
|
||||
if (strlen(str2) == 0) return false;
|
||||
str2[0] = '\0'; str2++;
|
||||
if (strlen(str2) > 3 || str2[0] == '-') return false;
|
||||
}
|
||||
if (str1 == NULL) return false;
|
||||
str1[0] = '\0'; str1++;
|
||||
if (str1[0] == '-') return false; // first character of str is never '-'
|
||||
@@ -139,6 +147,18 @@ static bool get_display_size (char *str, unsigned short *w, unsigned short *h) {
|
||||
if (*end || *w == 0) return false;
|
||||
*h = (unsigned short) strtoul(str1, &end, 10);
|
||||
if (*end || *h == 0) return false;
|
||||
if (!str2) return true;;
|
||||
*r = (unsigned short) strtoul(str2, &end, 10);
|
||||
if (*end || *r == 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_fps (char *str, unsigned short *n) {
|
||||
if (strlen(str) > 3) return false;
|
||||
char *end;
|
||||
unsigned long l;
|
||||
*n = (unsigned short) (l = strtoul(str, &end, 10)); // first character of str is never '-'
|
||||
if (*end || !l) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -185,6 +205,14 @@ static bool get_videorotate (char *str, videoflip_t *videoflip) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool option_has_value(int i, int argc, char *argv[]) {
|
||||
if (i >= argc - 1 || argv[i + 1][0] == '-') {
|
||||
fprintf(stderr,"invalid \"%s\" had no argument\n",argv[i]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
init_signals();
|
||||
|
||||
@@ -193,7 +221,7 @@ int main (int argc, char *argv[]) {
|
||||
bool use_audio = true;
|
||||
bool use_random_hw_addr = false;
|
||||
bool debug_log = DEFAULT_DEBUG_LOG;
|
||||
unsigned short display_size[2] = {0}, tcp[2] = {0}, udp[3] = {0};
|
||||
unsigned short display[4] = {0}, tcp[2] = {0}, udp[3] = {0};
|
||||
videoflip_t videoflip[2] = { NONE , NONE };
|
||||
|
||||
#ifdef SUPPRESS_AVAHI_COMPAT_WARNING
|
||||
@@ -207,40 +235,38 @@ int main (int argc, char *argv[]) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg(argv[i]);
|
||||
if (arg == "-n") {
|
||||
if (i == argc - 1) continue;
|
||||
if (!option_has_value(i, argc, argv)) exit(1);
|
||||
server_name = std::string(argv[++i]);
|
||||
} else if (arg == "-s") {
|
||||
if (i == argc - 1 || argv[i + 1][0] == '-') {
|
||||
fprintf(stderr,"invalid \"-s\" had no argument\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!option_has_value(i, argc, argv)) exit(1);
|
||||
std::string value(argv[++i]);
|
||||
if (!get_display_size(argv[i], &display_size[0], &display_size[1])) {
|
||||
fprintf(stderr, "invalid \"-s %s\"; default is \"-s 1920x1080\" (< 5 digits)\n",
|
||||
if (!get_display_settings(argv[i], &display[0], &display[1],&display[2])) {
|
||||
fprintf(stderr, "invalid \"-s %s\"; default is \"-s 1920x1080\" (up to 4 digits)\n",
|
||||
value.c_str());
|
||||
exit(1);
|
||||
}
|
||||
} else if (arg == "-fps") {
|
||||
if (!option_has_value(i, argc, argv)) exit(1);
|
||||
std::string value(argv[++i]);
|
||||
if (!get_fps(argv[i], &display[3])) {
|
||||
fprintf(stderr, "invalid \"-fps %s\"; default is \"-s 30\" (up to 3 digits)\n",
|
||||
value.c_str());
|
||||
exit(1);
|
||||
}
|
||||
} else if (arg == "-f") {
|
||||
if (i == argc - 1 || argv[i + 1][0] == '-') {
|
||||
fprintf(stderr,"invalid \"-f\" had no argument\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!option_has_value(i, argc, argv)) exit(1);
|
||||
if (!get_videoflip(argv[++i], &videoflip[0])) {
|
||||
fprintf(stderr,"invalid \"-f %s\" , unknown flip type, choices are H, V, I\n",argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
} else if (arg == "-r") {
|
||||
if (i == argc - 1 || argv[i + 1][0] == '-') {
|
||||
fprintf(stderr,"invalid \"-r\" had no argument\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!option_has_value(i, argc, argv)) exit(1);
|
||||
if (!get_videorotate(argv[++i], &videoflip[1])) {
|
||||
fprintf(stderr,"invalid \"-r %s\" , unknown rotation type, choices are R, L\n",argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
} else if (arg == "-p") {
|
||||
if (i == argc - 1 || argv[i + 1][0] == '-') {
|
||||
if (i >= argc - 1 || argv[i + 1][0] == '-') {
|
||||
tcp[0] = 7100; tcp[1] = 7000;
|
||||
udp[0] = 7011; udp[1] = 6001; udp[2] = 6000;
|
||||
continue;
|
||||
@@ -266,7 +292,8 @@ int main (int argc, char *argv[]) {
|
||||
print_info(argv[0]);
|
||||
exit(0);
|
||||
} else {
|
||||
LOGI("unknown option %s, skipping\n",argv[i]);
|
||||
LOGE("unknown option %s, stopping\n",argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +311,7 @@ int main (int argc, char *argv[]) {
|
||||
mac_address.clear();
|
||||
|
||||
relaunch:
|
||||
if (start_server(server_hw_addr, server_name, display_size, tcp, udp,
|
||||
if (start_server(server_hw_addr, server_name, display, tcp, udp,
|
||||
videoflip,use_audio, debug_log) != 0) {
|
||||
return 1;
|
||||
}
|
||||
@@ -357,7 +384,7 @@ extern "C" void log_callback (void *cls, int level, const char *msg) {
|
||||
|
||||
}
|
||||
|
||||
int start_server (std::vector<char> hw_addr, std::string name, unsigned short display_size[2],
|
||||
int start_server (std::vector<char> hw_addr, std::string name, unsigned short display[4],
|
||||
unsigned short tcp[2], unsigned short udp[3], videoflip_t videoflip[2],
|
||||
bool use_audio, bool debug_log) {
|
||||
raop_callbacks_t raop_cbs;
|
||||
@@ -399,8 +426,9 @@ int start_server (std::vector<char> hw_addr, std::string name, unsigned short di
|
||||
if (video_renderer) video_renderer_start(video_renderer);
|
||||
if (audio_renderer) audio_renderer_start(audio_renderer);
|
||||
|
||||
/* write desired display pixel width, pixel height to raop (use 0 for default values) */
|
||||
raop_set_display_size(raop, display_size[0], display_size[1]);
|
||||
/* write desired display pixel width, pixel height, refresh_rate, */
|
||||
/* and max_fps to raop (use 0 for default values) */
|
||||
raop_set_display(raop, display[0], display[1], display[2], display[3]);
|
||||
|
||||
/* network port selection (ports listed as "0" will be dynamically assigned) */
|
||||
raop_set_tcp_ports(raop, tcp);
|
||||
|
||||
Reference in New Issue
Block a user