Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/en/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ rtp2httpd [options]
- `-m, --maxclients <number>` - Maximum concurrent clients (default: 5)
- `-w, --workers <number>` - Number of worker processes (default: 1)

`--listen` can be specified multiple times to listen on multiple addresses or ports:

```bash
rtp2httpd --listen 5140 --listen 192.168.1.1:8081 --listen '[::1]:5140'
```

#### Upstream Network Interface Configuration

- `-i, --upstream-interface <interface>` - Default upstream interface (applies to all traffic types, lowest priority)
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ rtp2httpd [选项]
- `-m, --maxclients <数量>` - 最大并发客户端数 (默认: 5)
- `-w, --workers <数量>` - 工作进程数 (默认: 1)

`--listen` 可以重复指定,用于同时监听多个地址或端口:

```bash
rtp2httpd --listen 5140 --listen 192.168.1.1:8081 --listen '[::1]:5140'
```

#### 上游网络接口配置

- `-i, --upstream-interface <接口>` - 默认上游接口(作用于所有流量类型,优先级最低)
Expand Down
15 changes: 15 additions & 0 deletions e2e/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,21 @@ def test_listen_port(self, r2h_binary):
finally:
r2h.stop()

def test_multiple_listen_ports(self, r2h_binary):
"""Repeated -l flags should expose the HTTP server on every port."""
port1 = find_free_port()
port2 = find_free_port()
r2h = R2HProcess(r2h_binary, port1, extra_args=["-v", "4", "-l", str(port2)])
try:
r2h.start()

for port in (port1, port2):
assert wait_for_port(port)
status, _, _ = http_get("127.0.0.1", port, "/status")
assert status == 200
finally:
r2h.stop()


# ---------------------------------------------------------------------------
# Max clients (-m)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,115 @@
"require uci";

return view.extend({
normalizeListenValues: function (value) {
var input = Array.isArray(value) ? value : value ? [value] : [];
var values = [];

for (var i = 0; i < input.length; i++) {
var listen = String(input[i]).trim();
if (listen) {
values.push(listen);
}
}

return values;
},

getListenValues: function (section_id) {
return this.normalizeListenValues(
uci.get("rtp2httpd", section_id, "listen")
);
},

parseListenValue: function (value) {
var listen = String(value || "").trim();
var host = null;
var port = null;
var match;
var pos;

if (!listen) {
return null;
}

if (/^\d+$/.test(listen)) {
port = listen;
} else if (listen.charAt(0) === "[") {
match = listen.match(/^\[([^\]]+)\]:(\d+)$/);
if (!match) {
return null;
}
host = "[" + match[1] + "]";
port = match[2];
} else {
pos = listen.lastIndexOf(":");
if (pos <= 0 || pos !== listen.indexOf(":")) {
return null;
}
host = listen.substring(0, pos);
port = listen.substring(pos + 1);
}

if (!/^\d+$/.test(port)) {
return null;
}

var portNumber = Number(port);
if (portNumber < 1 || portNumber > 65535) {
return null;
}

if (host !== null) {
if (
host === "*" ||
host.indexOf("/") >= 0 ||
/\s/.test(host)
) {
return null;
}
}

return {
host: host,
port: port,
};
},

validateListenValue: function (value) {
var listen = String(value || "").trim();

if (!listen) {
return true;
}

if (/^\*:/.test(listen)) {
return _(
"Use a bare port such as 5140 to listen on all addresses; *:5140 is not supported here."
);
}

if (!this.parseListenValue(listen)) {
return _(
"Use port, address:port, hostname:port, or [IPv6]:port, for example 5140 or 192.168.1.1:8081."
);
}

return true;
},

getPrimaryListenTarget: function (section_id) {
var values = this.getListenValues(section_id);
var target = values.length > 0 ? this.parseListenValue(values[0]) : null;
var port;

if (target) {
return target;
}

port = uci.get("rtp2httpd", section_id, "port") || "5140";
return this.parseListenValue(port) || { host: null, port: "5140" };
},

// Helper function to open a page (status or player)
openPage: function (section_id, pageType) {
var pathConfigKey =
Expand All @@ -24,6 +133,7 @@ return view.extend({
var token = null;
var pagePath = defaultPath;
var hostname = null;
var listenTarget = null;
var use_config_file = uci.get("rtp2httpd", section_id, "use_config_file");

if (use_config_file === "1") {
Expand Down Expand Up @@ -59,8 +169,9 @@ return view.extend({
pagePath = pagePathMatch[1];
}
} else {
// Get port, token, hostname and page path from UCI config
port = uci.get("rtp2httpd", section_id, "port") || "5140";
// Get listen address, token, hostname and page path from UCI config
listenTarget = self.getPrimaryListenTarget(section_id);
port = listenTarget.port;
token = uci.get("rtp2httpd", section_id, "r2h_token");
hostname = uci.get("rtp2httpd", section_id, "hostname");
pagePath = uci.get("rtp2httpd", section_id, uciPathKey) || defaultPath;
Expand All @@ -72,7 +183,10 @@ return view.extend({
}

// Use configured hostname or fallback to window.location.hostname
var targetHostname = hostname || window.location.hostname;
var targetHostname =
hostname ||
(listenTarget && listenTarget.host) ||
window.location.hostname;

// If hostname doesn't have protocol, prepend http:// for URL parsing
var hasProtocol = /^https?:\/\//i.test(targetHostname);
Expand Down Expand Up @@ -100,6 +214,10 @@ return view.extend({
var finalHost = url.hostname;
var finalPort = "";

if (finalHost.indexOf(":") >= 0 && finalHost.charAt(0) !== "[") {
finalHost = "[" + finalHost + "]";
}

if (!hasProtocol) {
// No protocol in original hostname: use configured port if URL port is empty
if (!url.port) {
Expand Down Expand Up @@ -234,10 +352,58 @@ return view.extend({
});
};

o = s.taboption("basic", form.Value, "port", _("Port"));
o.datatype = "port";
o = s.taboption(
"basic",
form.DynamicList,
"listen",
_("Listen Addresses"),
_(
"HTTP listen addresses. Use a bare port for all addresses (e.g., 5140), address:port for IPv4/hostnames, or [IPv6]:port."
)
);
o.placeholder = "5140";
o.rmempty = true;
o.depends("use_config_file", "0");
o.cfgvalue = function (section_id) {
var values = self.getListenValues(section_id);
var port;

if (values.length > 0) {
return values;
}

port = uci.get("rtp2httpd", section_id, "port");
return port ? [port] : [];
};
o.formvalue = function (section_id) {
var elem = this.getUIElement(section_id);

return self.normalizeListenValues(elem ? elem.getValue() : null);
};
o.write = function (section_id, value) {
var values = self.normalizeListenValues(value);
var config = this.uciconfig || this.section.uciconfig || this.map.config;
var sid = this.ucisection || section_id;
var option = this.ucioption || this.option;

if (values.length > 0) {
this.map.data.set(config, sid, option, values);
} else {
this.map.data.unset(config, sid, option);
}

this.map.data.unset(config, sid, "port");
};
o.remove = function (section_id) {
var config = this.uciconfig || this.section.uciconfig || this.map.config;
var sid = this.ucisection || section_id;

this.map.data.unset(config, sid, this.ucioption || this.option);
this.map.data.unset(config, sid, "port");
};
o.validate = function (section_id, value) {
return self.validateListenValue(value);
};

o = s.taboption("basic", form.ListValue, "verbose", _("Logging level"));
o.value("0", "Fatal");
Expand Down
28 changes: 23 additions & 5 deletions openwrt-support/luci-app-rtp2httpd/po/templates/rtp2httpd.pot
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,17 @@ msgid ""
"40000-40100). Leave empty to use random ports."
msgstr ""

#: htdocs/luci-static/resources/view/rtp2httpd.js:242
#: htdocs/luci-static/resources/view/rtp2httpd.js:361
msgid ""
"HTTP listen addresses. Use a bare port for all addresses (e.g., 5140), "
"address:port for IPv4/hostnames, or [IPv6]:port."
msgstr ""

#: htdocs/luci-static/resources/view/rtp2httpd.js:359
msgid "Listen Addresses"
msgstr ""

#: htdocs/luci-static/resources/view/rtp2httpd.js:300
msgid "Logging level"
msgstr ""

Expand Down Expand Up @@ -224,10 +234,6 @@ msgstr ""
msgid "Please configure External M3U URL first"
msgstr ""

#: htdocs/luci-static/resources/view/rtp2httpd.js:237
msgid "Port"
msgstr ""

#: htdocs/luci-static/resources/view/rtp2httpd.js:540
msgid "R2H Token"
msgstr ""
Expand All @@ -251,6 +257,18 @@ msgid ""
"disable CORS."
msgstr ""

#: htdocs/luci-static/resources/view/rtp2httpd.js:92
msgid ""
"Use a bare port such as 5140 to listen on all addresses; *:5140 is not "
"supported here."
msgstr ""

#: htdocs/luci-static/resources/view/rtp2httpd.js:98
msgid ""
"Use port, address:port, hostname:port, or [IPv6]:port, for example 5140 or "
"192.168.1.1:8081."
msgstr ""

#: htdocs/luci-static/resources/view/rtp2httpd.js:507
msgid "Status Dashboard"
msgstr ""
Expand Down
32 changes: 27 additions & 5 deletions openwrt-support/luci-app-rtp2httpd/po/zh_Hans/rtp2httpd.po
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,19 @@ msgstr ""
"FCC 客户端套接字使用的本地 UDP 端口范围(格式:起始端口-结束端口,例如:"
"40000-40100)。留空则使用随机端口。"

#: htdocs/luci-static/resources/view/rtp2httpd.js:242
#: htdocs/luci-static/resources/view/rtp2httpd.js:361
msgid ""
"HTTP listen addresses. Use a bare port for all addresses (e.g., 5140), "
"address:port for IPv4/hostnames, or [IPv6]:port."
msgstr ""
"HTTP 监听地址。使用裸端口表示监听所有地址(例如:5140),IPv4/主机名使用 "
"address:port,IPv6 使用 [IPv6]:port。"

#: htdocs/luci-static/resources/view/rtp2httpd.js:359
msgid "Listen Addresses"
msgstr "监听地址"

#: htdocs/luci-static/resources/view/rtp2httpd.js:300
msgid "Logging level"
msgstr "日志级别"

Expand Down Expand Up @@ -252,10 +264,6 @@ msgstr "播放器页面路径"
msgid "Please configure External M3U URL first"
msgstr "请先配置外部 M3U URL"

#: htdocs/luci-static/resources/view/rtp2httpd.js:237
msgid "Port"
msgstr "端口"

#: htdocs/luci-static/resources/view/rtp2httpd.js:540
msgid "R2H Token"
msgstr "HTTP 请求认证令牌"
Expand All @@ -281,6 +289,20 @@ msgstr ""
"设置 Access-Control-Allow-Origin 响应头以启用 CORS 跨域访问。设为 * 允许所有"
"来源,或指定域名(如 https://example.com)。留空则禁用 CORS。"

#: htdocs/luci-static/resources/view/rtp2httpd.js:92
msgid ""
"Use a bare port such as 5140 to listen on all addresses; *:5140 is not "
"supported here."
msgstr "使用 5140 这样的裸端口监听所有地址;这里不支持 *:5140。"

#: htdocs/luci-static/resources/view/rtp2httpd.js:98
msgid ""
"Use port, address:port, hostname:port, or [IPv6]:port, for example 5140 or "
"192.168.1.1:8081."
msgstr ""
"请使用端口、address:port、hostname:port 或 [IPv6]:port 格式,例如 5140 或 "
"192.168.1.1:8081。"

#: htdocs/luci-static/resources/view/rtp2httpd.js:507
msgid "Status Dashboard"
msgstr "状态面板"
Expand Down
7 changes: 7 additions & 0 deletions openwrt-support/rtp2httpd/files/rtp2httpd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ config rtp2httpd
option respawn '1'
# option use_config_file '0'
# option verbose '1'

# HTTP listen addresses. Use a bare port to listen on all addresses.
# Multiple listen addresses are supported.
# list listen '5140'
# list listen '192.168.1.1:8081'
# list listen '[::1]:5140'
# Legacy single-port option, kept for backward compatibility:
# option port '5140'

# Upstream Interface Configuration
Expand Down
Loading
Loading