Compare commits

...

9 Commits

Author SHA1 Message Date
mofeng-git
96a6e7edcd fix: 设置 gostc 监听地址为本地避免未授权访问 2025-12-03 13:41:28 +08:00
mofeng-git
50c3e6a32a feat: 支持 NOGOSTC dcoker 环境变量 #204 2025-12-03 13:37:03 +08:00
mofeng-git
c8305cc65d feat: 支持 turn 中转,可以远程访问 h264/webrtc #197 2025-12-03 13:09:41 +08:00
SilentWind
aae4e936db
Update README.md 2025-11-30 14:49:18 +08:00
mofeng-git
45a04f7570 feat: 添加 docker 环境 CH9329NUM #195 2025-11-27 17:41:34 +08:00
mofeng-git
53ba69f4aa fix: 删除多余参数 2025-11-05 09:15:26 +08:00
mofeng-git
53229a9055 feat: 优化编译参数 2025-11-04 22:55:12 +08:00
SilentWind
f97df0d830
Merge pull request #183 from nuintun/fix-html-tag
fix: 修复由于非法的 HTML 标签造成的宏中脚本事件个数信息位置错误的问题
2025-10-19 21:08:27 +08:00
nuintun
8ed5e4abc3
fix: 修复由于非法的 HTML 标签造成的宏中脚本事件个数信息位置错误的问题 2025-10-19 21:05:44 +08:00
13 changed files with 271 additions and 53 deletions

View File

@ -50,6 +50,29 @@ jobs:
with:
fetch-depth: 0
- name: Inject TURN config (optional)
if: ${{ env.TURN_HOST != '' }}
run: |
mkdir -p configs/kvmd/override.d
cat > configs/kvmd/override.d/turn.yaml <<EOF
janus:
stun:
host: ${TURN_HOST}
port: ${TURN_PORT}
local_ice_servers:
- urls:
- "stun:${TURN_HOST}:${TURN_PORT}"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
username: "${TURN_USER}"
credential: "${TURN_PASS}"
EOF
env:
TURN_HOST: ${{ secrets.TURN_HOST }}
TURN_PORT: ${{ secrets.TURN_PORT }}
TURN_USER: ${{ secrets.TURN_USER }}
TURN_PASS: ${{ secrets.TURN_PASS }}
- name: Set build environment
id: build_env
shell: bash
@ -184,4 +207,4 @@ jobs:
if [ "${{ github.event.inputs.create_release }}" = "true" ]; then
echo "| **Release** | [${{ env.RELEASE_TAG }}](${{ steps.release.outputs.url }}) |" >> $GITHUB_STEP_SUMMARY
fi
fi
fi

View File

@ -47,6 +47,29 @@ jobs:
with:
fetch-depth: 0
- name: Inject TURN config (optional)
if: ${{ env.TURN_HOST != '' }}
run: |
mkdir -p configs/kvmd/override.d
cat > configs/kvmd/override.d/turn.yaml <<EOF
janus:
stun:
host: ${TURN_HOST}
port: ${TURN_PORT}
local_ice_servers:
- urls:
- "stun:${TURN_HOST}:${TURN_PORT}"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
username: "${TURN_USER}"
credential: "${TURN_PASS}"
EOF
env:
TURN_HOST: ${{ secrets.TURN_HOST }}
TURN_PORT: ${{ secrets.TURN_PORT }}
TURN_USER: ${{ secrets.TURN_USER }}
TURN_PASS: ${{ secrets.TURN_PASS }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
@ -117,6 +140,29 @@ jobs:
with:
fetch-depth: 0
- name: Inject TURN config (optional)
if: ${{ env.TURN_HOST != '' }}
run: |
mkdir -p configs/kvmd/override.d
cat > configs/kvmd/override.d/turn.yaml <<EOF
janus:
stun:
host: ${TURN_HOST}
port: ${TURN_PORT}
local_ice_servers:
- urls:
- "stun:${TURN_HOST}:${TURN_PORT}"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=udp"
- "turn:${TURN_HOST}:${TURN_PORT}?transport=tcp"
username: "${TURN_USER}"
credential: "${TURN_PASS}"
EOF
env:
TURN_HOST: ${{ secrets.TURN_HOST }}
TURN_PORT: ${{ secrets.TURN_PORT }}
TURN_USER: ${{ secrets.TURN_USER }}
TURN_PASS: ${{ secrets.TURN_PASS }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
@ -191,4 +237,4 @@ jobs:
echo "- **Platforms**: ${{ github.event.inputs.platforms }}" >> $GITHUB_STEP_SUMMARY
echo "- **Aliyun Enabled**: ${{ github.event.inputs.enable_aliyun }}" >> $GITHUB_STEP_SUMMARY
echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta.outputs.tags }}" | sed 's/^/ - /' >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta.outputs.tags }}" | sed 's/^/ - /' >> $GITHUB_STEP_SUMMARY

View File

@ -32,6 +32,8 @@
**One-KVM** 是基于开源 [PiKVM](https://github.com/pikvm/pikvm) 项目进行二次开发的 DIY IP-KVM 解决方案。该方案利用成本较低的硬件设备,实现 BIOS 级别的远程服务器或工作站管理功能。
> 本项目目前并无适配树莓派的计划。这是因为树莓派平台本质上属于 PiKVM 官方硬件生态和盈利的一部分。我们非常尊重和感谢上游项目 PiKVM ,因此 One-KVM 的设备适配主要聚焦于补充性场景,尽量避免与 PiKVM 官方产品产生重叠,以支持其可持续发展。
### 应用场景
- **家庭实验室主机管理** - 远程管理服务器和开发设备
@ -323,4 +325,4 @@ sudo docker run --name kvmd -itd \
本项目基于以下优秀开源项目进行二次开发:
- [PiKVM](https://github.com/pikvm/pikvm) - 开源的 DIY IP-KVM 解决方案
- [PiKVM](https://github.com/pikvm/pikvm) - 开源的 DIY IP-KVM 解决方案

View File

@ -113,58 +113,75 @@ RUN git clone --depth=1 https://github.com/gvalkov/python-evdev.git /tmp/python-
&& python3 setup.py bdist_wheel --dist-dir /tmp/wheel/ \
&& rm -rf /tmp/python-evdev
# 编译安装 libnice、libsrtp、libwebsockets 和 janus-gateway
RUN git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libnice \
# 编译安装 libnice、libsrtp、libwebsockets 和 janus-gateway显式 Release 与按架构优化)
RUN export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
&& if [ "${TARGETARCH}" = arm64 ]; then export CFLAGS="$COMMON_CFLAGS -march=armv8-a"; \
elif [ "${TARGETARCH}" = arm ]; then export CFLAGS="$COMMON_CFLAGS -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -mtune=cortex-a7"; \
else export CFLAGS="$COMMON_CFLAGS -march=x86-64 -mtune=generic"; fi \
&& export CXXFLAGS="$CFLAGS" LDFLAGS="-Wl,-O1 -Wl,--as-needed" \
&& git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libnice \
&& cd /tmp/libnice \
&& meson --prefix=/usr build && ninja -C build && ninja -C build install \
&& meson setup build --prefix=/usr --buildtype=release -Doptimization=2 -Dc_args="$CFLAGS" -Dcpp_args="$CXXFLAGS" \
&& ninja -C build && ninja -C build install \
&& rm -rf /tmp/libnice \
&& curl https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz -L -o /tmp/libsrtp-2.2.0.tar.gz \
&& cd /tmp \
&& tar xf libsrtp-2.2.0.tar.gz \
&& cd libsrtp-2.2.0 \
&& ./configure --prefix=/usr --enable-openssl \
&& CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" ./configure --prefix=/usr --enable-openssl \
&& make shared_library -j$(nproc) && make install \
&& cd /tmp \
&& rm -rf /tmp/libsrtp* \
&& git clone --depth=1 https://github.com/warmcat/libwebsockets /tmp/libwebsockets \
&& cd /tmp/libwebsockets \
&& mkdir build && cd build \
&& cmake -DLWS_MAX_SMP=1 -DLWS_WITHOUT_EXTENSIONS=0 -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_C_FLAGS="-fpic" .. \
&& cmake -DLWS_MAX_SMP=1 -DLWS_WITHOUT_EXTENSIONS=0 -DCMAKE_INSTALL_PREFIX:PATH=/usr \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="$CFLAGS -fPIC" -DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS -fPIC" .. \
&& make -j$(nproc) && make install \
&& cd /tmp \
&& rm -rf /tmp/libwebsockets \
&& git clone --depth=1 https://github.com/meetecho/janus-gateway.git /tmp/janus-gateway \
&& cd /tmp/janus-gateway \
&& sh autogen.sh \
&& ./configure --enable-static --enable-websockets --enable-plugin-audiobridge \
&& CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" ./configure --enable-static --enable-websockets --enable-plugin-audiobridge \
--disable-data-channels --disable-rabbitmq --disable-mqtt --disable-all-plugins \
--disable-all-loggers --prefix=/usr \
&& make -j$(nproc) && make install \
&& cd /tmp \
&& rm -rf /tmp/janus-gateway
# 编译 Rockchip MPP、RGA 和 FFmpeg(仅 arm64
# 编译 Rockchip MPP、RGA仅 arm64,显式 Release 与按架构优化
RUN if [ ${TARGETARCH} = arm64 ]; then \
git clone --depth=1 https://github.com/rockchip-linux/mpp.git /tmp/rkmpp \
export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
&& export CFLAGS="$COMMON_CFLAGS -march=armv8-a" \
&& export CXXFLAGS="$CFLAGS" \
&& git clone --depth=1 https://github.com/rockchip-linux/mpp.git /tmp/rkmpp \
&& mkdir -p /tmp/rkmpp/rkmpp_build && cd /tmp/rkmpp/rkmpp_build \
&& cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DBUILD_TEST=OFF .. \
&& cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DBUILD_TEST=OFF \
-DCMAKE_C_FLAGS_RELEASE="$CFLAGS" -DCMAKE_CXX_FLAGS_RELEASE="$CXXFLAGS" .. \
&& make -j$(nproc) \
&& make install \
&& git clone -b jellyfin-rga --depth=1 https://github.com/nyanmisaka/rk-mirrors.git /tmp/rkrga \
&& cd /tmp/ \
&& meson setup rkrga rkrga_build --prefix=/usr --libdir=lib --buildtype=release -Dcpp_args=-fpermissive -Dlibdrm=false -Dlibrga_demo=false \
&& meson setup rkrga rkrga_build --prefix=/usr --libdir=lib --buildtype=release -Doptimization=2 \
-Dc_args="$CFLAGS" -Dcpp_args="$CXXFLAGS -fpermissive" -Dlibdrm=false -Dlibrga_demo=false \
&& meson configure rkrga_build > /dev/null \
&& ninja -C rkrga_build install \
&& rm -rf /tmp/rkmpp /tmp/rkrga; \
fi
# 编译 ustreamer
# 编译 ustreamer(按架构优化)
RUN sed --in-place --expression 's|^#include "refcount.h"$|#include "../refcount.h"|g' /usr/include/janus/plugins/plugin.h \
&& git clone --depth=1 https://github.com/mofeng-git/ustreamer /tmp/ustreamer \
&& export COMMON_CFLAGS='-O2 -pipe -fPIC -fstack-protector-strong -D_FORTIFY_SOURCE=2' \
&& if [ "${TARGETARCH}" = arm64 ]; then export CFLAGS="$COMMON_CFLAGS -march=armv8-a"; \
elif [ "${TARGETARCH}" = arm ]; then export CFLAGS="$COMMON_CFLAGS -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -mtune=cortex-a7"; \
else export CFLAGS="$COMMON_CFLAGS -march=x86-64 -mtune=generic"; fi \
&& export CXXFLAGS="$CFLAGS" \
&& if [ ${TARGETARCH} = arm64 ]; then \
make -j$(nproc) WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_MPP=1 WITH_DRM=1 -C /tmp/ustreamer; \
make -j$(nproc) CFLAGS="$CFLAGS" WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_MPP=1 WITH_DRM=1 -C /tmp/ustreamer; \
else \
make -j$(nproc) WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_DRM=1 -C /tmp/ustreamer; \
make -j$(nproc) CFLAGS="$CFLAGS" WITH_PYTHON=1 WITH_JANUS=1 WITH_FFMPEG=1 WITH_DRM=1 -C /tmp/ustreamer; \
fi \
&& /tmp/ustreamer/ustreamer -v \
&& /tmp/ustreamer/ustreamer-dump -v \
@ -178,9 +195,8 @@ RUN mkdir /tmp/lib \
/tmp/lib/ \
&& find /usr/lib -name "libsrtp2.so.*" -exec cp {} /tmp/lib/ \; \
&& find /usr/lib -name "libwebsockets.so.*" -exec cp {} /tmp/lib/ \; \
&& if [ ${TARGETARCH} = arm64 ]; then \
find /usr/lib -name "libsw*.so.*" -exec cp {} /tmp/lib/ \; \
&& find /usr/lib -name "libpostproc.so.*" -exec cp {} /tmp/lib/ \; \
&& find /usr/lib -name "librockchip*" -exec cp {} /tmp/lib/ \; \
&& find /usr/lib -name "librga.so.*" -exec cp {} /tmp/lib/ \; ; \
fi
&& [ "${TARGETARCH}" = "arm64" ] && \
find /usr/lib -name "libsw*.so.*" -exec cp {} /tmp/lib/ \; && \
find /usr/lib -name "libpostproc.so.*" -exec cp {} /tmp/lib/ \; && \
find /usr/lib -name "librockchip*" -exec cp {} /tmp/lib/ \; && \
find /usr/lib -name "librga.so.*" -exec cp {} /tmp/lib/ \; || true

View File

@ -159,7 +159,7 @@ EOF
fi
if [ "$NOIPMI" == "1" ]; then
log_info "已禁用IPMI功能"
log_info "已禁用 IPMI 功能"
rm -r /usr/share/kvmd/extras/ipmi
else
cat >> /etc/kvmd/supervisord.conf << EOF
@ -177,11 +177,30 @@ redirect_stderr=true
EOF
fi
if [ "$NOGOSTC" == "1" ]; then
log_info "已禁用 GOSTC 功能"
rm -rf /usr/share/kvmd/extras/gostc
else
cat >> /etc/kvmd/supervisord.conf << EOF
[program:kvmd-gostc]
command=/usr/bin/gostc -web-addr 127.0.0.1:18080
autostart=true
autorestart=true
startsecs=5
priority=300
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true
EOF
fi
#switch OTG config
if [ "$OTG" == "1" ]; then
log_info "已启用 OTG 功能"
sed -i "s/ch9329/otg/g" /etc/kvmd/override.yaml
sed -i "s/device: \/dev\/ttyUSB0//g" /etc/kvmd/override.yaml
sed -i "s|device: /dev/ttyUSB0||g" /etc/kvmd/override.yaml
if [ "$NOMSD" == 1 ]; then
log_info "已禁用 MSD 功能"
else
@ -190,8 +209,8 @@ EOF
fi
if [ ! -z "$VIDEONUM" ]; then
if sed -i "s/\/dev\/video0/\/dev\/video$VIDEONUM/g" /etc/kvmd/override.yaml && \
sed -i "s/\/dev\/video0/\/dev\/video$VIDEONUM/g" /etc/kvmd/janus/janus.plugin.ustreamer.jcfg; then
if sed -i "s|/dev/video0|/dev/video$VIDEONUM|g" /etc/kvmd/override.yaml && \
sed -i "s|/dev/video0|/dev/video$VIDEONUM|g" /etc/kvmd/janus/janus.plugin.ustreamer.jcfg; then
log_info "视频设备已设置为 /dev/video$VIDEONUM"
fi
fi
@ -208,6 +227,12 @@ EOF
fi
fi
if [ ! -z "$CH9329NUM" ]; then
if sed -i "s|/dev/ttyUSB0|/dev/ttyUSB$CH9329NUM|g" /etc/kvmd/override.yaml; then
log_info "CH9329 串口设备已设置为 $CH9329NUM"
fi
fi
if [ ! -z "$CH9329TIMEOUT" ]; then
if sed -i "s/read_timeout: 0.3/read_timeout: $CH9329TIMEOUT/g" /etc/kvmd/override.yaml; then
log_info "CH9329 超时已设置为 $CH9329TIMEOUT"
@ -272,4 +297,4 @@ if [ "$OTG" == "1" ]; then
fi
log_info "One-KVM 配置文件准备完成,正在启动服务..."
exec supervisord -c /etc/kvmd/supervisord.conf
exec supervisord -c /etc/kvmd/supervisord.conf

View File

@ -66,9 +66,7 @@ kvmd:
- "--jpeg-sink-mode=0660"
- "--h264-bitrate={h264_bitrate}"
- "--h264-gop={h264_gop}"
- "--h264-preset=ultrafast"
- "--h264-hwenc=disabled"
- "--h264-hwenc-fallback"
- "--slowdown"
gpio:
drivers:
@ -159,10 +157,6 @@ media:
jpeg:
sink: 'kvmd::ustreamer::jpeg'
janus:
stun:
host: stun.cloudflare.com
port: 3478
otgnet:
commands:

View File

@ -53,17 +53,6 @@ stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true
[program:kvmd-gostc]
command=/usr/bin/gostc -web-addr 0.0.0.0:18080
autostart=true
autorestart=true
startsecs=5
priority=300
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true
[program:clean_when_exit]
command=/etc/kvmd/clean_when_exit.sh
autostart=true
@ -74,4 +63,3 @@ stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true

View File

@ -81,6 +81,7 @@ from ..validators.net import valid_port
from ..validators.net import valid_ports_list
from ..validators.net import valid_mac
from ..validators.net import valid_ssl_ciphers
from ..validators.net import valid_ice_servers
from ..validators.hid import valid_hid_key
from ..validators.hid import valid_hid_mouse_output
@ -860,6 +861,7 @@ def _get_config_scheme() -> dict:
], type=valid_command),
"cmd_remove": Option([], type=valid_options),
"cmd_append": Option([], type=valid_options),
"local_ice_servers": Option([], type=valid_ice_servers, unpack_as="ice_servers"),
},
"watchdog": {

View File

@ -2,6 +2,8 @@ import asyncio
import asyncio.subprocess
import socket
import dataclasses
import json
from typing import Any
import netifaces
@ -43,6 +45,7 @@ class JanusRunner: # pylint: disable=too-many-instance-attributes
cmd: list[str],
cmd_remove: list[str],
cmd_append: list[str],
ice_servers: list[dict[str, Any]],
) -> None:
self.__stun = Stun(stun_host, stun_port, stun_timeout, stun_retries, stun_retries_delay)
@ -52,6 +55,7 @@ class JanusRunner: # pylint: disable=too-many-instance-attributes
self.__check_retries_delay = check_retries_delay
self.__cmd = tools.build_cmd(cmd, cmd_remove, cmd_append)
self.__ice_servers = ice_servers
self.__janus_task: (asyncio.Task | None) = None
self.__janus_proc: (asyncio.subprocess.Process | None) = None # pylint: disable=no-member
@ -173,13 +177,25 @@ class JanusRunner: # pylint: disable=too-many-instance-attributes
part.format(**placeholders)
for part in cmd
]
self.__janus_proc = await aioproc.run_process(
cmd=cmd,
env={"JANUS_USTREAMER_WEB_ICE_URL": f"stun:{netcfg.stun_host}:{netcfg.stun_port}"},
)
env = {}
ice_payload = self.__build_ice_payload(netcfg)
if ice_payload:
env["JANUS_USTREAMER_WEB_ICE_URL"] = ice_payload
self.__janus_proc = await aioproc.run_process(cmd=cmd, env=env or None)
get_logger(0).info("Started Janus pid=%d: %s", self.__janus_proc.pid, tools.cmdfmt(cmd))
async def __kill_janus_proc(self) -> None:
if self.__janus_proc:
await aioproc.kill_process(self.__janus_proc, 5, get_logger(0))
self.__janus_proc = None
def __build_ice_payload(self, netcfg: _Netcfg) -> (str | None):
if self.__ice_servers:
try:
return f"json:{json.dumps(self.__ice_servers, ensure_ascii=False)}"
except Exception as ex: # pragma: no cover
get_logger(0).error("Can't encode ICE servers: %s", tools.efmt(ex))
return None
if netcfg.stun_host and netcfg.stun_port:
return f"stun:{netcfg.stun_host}:{netcfg.stun_port}"
return None

View File

@ -136,7 +136,12 @@ class Stun:
return (StunNatType.FULL_CONE_NAT, resp)
if first.changed is None:
raise RuntimeError(f"Changed addr is None: {first}")
get_logger(0).warning(
"STUN server %s:%d responded without CHANGED-ADDRESS; skipping NAT type detection",
self.__host,
self.__port,
)
return (StunNatType.ERROR, first)
resp = await self.__make_request("Change request [ext_ip != src_ip]", first.changed, b"")
if not resp.ok:
return (StunNatType.CHANGED_ADDR_ERROR, resp)

View File

@ -120,3 +120,39 @@ def valid_ssl_ciphers(arg: Any) -> str:
def valid_url(arg: Any) -> str:
# XXX: VERY primitive
return check_re_match(arg, "HTTP(S) URL", r"^https?://[\[\w]+\S*")
def valid_ice_servers(arg: Any) -> list[dict[str, Any]]:
name = "ICE servers list"
if arg is None:
return []
if not isinstance(arg, list):
raise_error(arg, name)
servers: list[dict[str, Any]] = []
for item in arg:
if not isinstance(item, dict):
raise_error(item, "ICE server entry")
urls = item.get("urls")
if isinstance(urls, str):
urls_list = [valid_stripped_string_not_empty(urls, "ICE server URL")]
elif isinstance(urls, list):
urls_list = [
valid_stripped_string_not_empty(url, "ICE server URL")
for url in urls
]
else:
raise_error(urls, "ICE server URLs")
if not urls_list:
raise_error(urls, "ICE server URLs")
server: dict[str, Any] = {"urls": urls_list}
username = item.get("username")
if username is not None:
server["username"] = valid_stripped_string_not_empty(username, "ICE username")
credential = item.get("credential")
if credential is not None:
server["credential"] = valid_stripped_string_not_empty(credential, "ICE credential")
credential_type = item.get("credential_type") or item.get("credentialType")
if credential_type is not None:
server["credentialType"] = valid_stripped_string_not_empty(credential_type, "ICE credentialType")
servers.append(server)
return servers

View File

@ -781,7 +781,7 @@
</tr>
<tr>
<td i18n="macro_script_events">Scripted events:</td>
<tdvalue id="hid-recorder-events-count">0</tdvalue>
<td id="hid-recorder-events-count">0</td>
<td><sup><i i18n="macro_include_delays">include delays</i></sup></td>
</tr>
</table>

View File

@ -111,7 +111,7 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __organizeH
}
};
var __ensureJanus = function(internal) {
var __ensureJanus = function(internal) {
if (__janus === null && !__stop && (!__ensuring || internal)) {
__ensuring = true;
__setInactive();
@ -131,10 +131,75 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __organizeH
});
}
};
var __decodeIcePayload = function(payload) {
if (typeof payload !== "string") {
return null;
}
let data = payload.trim();
if (data.startsWith("json:")) {
data = data.slice(5).trim();
}
if (data.startsWith("{") || data.startsWith("[")) {
try {
let parsed = JSON.parse(data);
if (Array.isArray(parsed)) {
return parsed;
} else if (parsed && Array.isArray(parsed.servers)) {
return parsed.servers;
}
return null;
} catch (error) {
__logError("Can't parse ICE payload:", error);
}
}
return null;
};
var __normalizeIceEntry = function(entry) {
if (!entry || typeof entry !== "object") {
return null;
}
let urls = entry.urls;
if (typeof urls === "string") {
urls = [urls];
}
if (!Array.isArray(urls) || urls.length === 0) {
return null;
}
let normalized = {"urls": urls};
if (entry.username) {
normalized.username = entry.username;
}
if (entry.credential) {
normalized.credential = entry.credential;
}
if (entry.credentialType) {
normalized.credentialType = entry.credentialType;
}
return normalized;
};
var __normalizeIceServers = function(payload) {
let parsed = __decodeIcePayload(payload);
if (!parsed) {
return null;
}
let servers = [];
for (let entry of parsed) {
let normalized = __normalizeIceEntry(entry);
if (normalized) {
servers.push(normalized);
}
}
return (servers.length > 0 ? servers : null);
};
var __getIceServers = function() {
if (__ice !== null && __ice.url) {
__logInfo("Using the custom ICE Server got from uStreamer:", __ice);
let normalized = __normalizeIceServers(__ice.url);
if (normalized !== null) {
return normalized;
}
return [{"urls": __ice.url}];
} else {
return [];