diff --git a/d2ypp2/config/system-stack.env.example b/d2ypp2/config/system-stack.env.example index 1b7a743..db5ca75 100644 --- a/d2ypp2/config/system-stack.env.example +++ b/d2ypp2/config/system-stack.env.example @@ -66,13 +66,17 @@ NEKO_HTTP_PORT_3=9202 NEKO_START_URL=https://accounts.google.com/ServiceLogin?continue=https://www.youtube.com/ NEKO_IMAGE_X86=ghcr.io/m1k1o/neko/google-chrome:3.0.0 NEKO_IMAGE_ARM64=ghcr.io/m1k1o/neko/chromium:3.0.0 +NEKO_INSTANCE_COUNT=2 # Neko 浏览器登录:显示昵称可填 live / admin;密码与全站策略一致(默认 12345678) NEKO_USER_PASS=12345678 NEKO_ADMIN_PASS=12345678 # 持久化 Chromium 配置(油猴、YouTube 登录、脚本、开发者数据);可从参考机 rsync 整个目录覆盖以迁移 NEKO_PROFILE_HOST_DIR= -NEKO_SHM_SIZE=2gb -NEKO_DESKTOP_SCREEN=1920x1080@30 +NEKO_SHM_SIZE=512mb +NEKO_DESKTOP_SCREEN=1280x720@15 +NEKO_MAX_FPS=15 +NEKO_VIDEO_BITRATE=1200 +NEKO_BROWSER_RENDER_MODE=auto NEKO_WEBRTC_EPR=52000-52099 NEKO_WEBRTC_EPR_1=52000-52099 NEKO_WEBRTC_EPR_2=52101-52199 diff --git a/d2ypp2/douyin_youtube_ffplay.py b/d2ypp2/douyin_youtube_ffplay.py index 4e95bd4..67ad9f1 100644 --- a/d2ypp2/douyin_youtube_ffplay.py +++ b/d2ypp2/douyin_youtube_ffplay.py @@ -126,11 +126,11 @@ def _resolve_youtube_push_config_file(config_file: str | None = None, *, base_di def _load_youtube_push_settings(config_file: str) -> dict[str, str | bool]: settings: dict[str, str | bool] = { "use_rtmps": True, - "b_v": "4000k", - "maxrate": "4500k", - "bufsize": "8000k", + "b_v": "2800k", + "maxrate": "3200k", + "bufsize": "5600k", "stream_key": "", - "fast_audio": False, + "fast_audio": True, } if not os.path.exists(config_file): return settings @@ -183,9 +183,9 @@ def start_douyin_youtube_ffplay( rtmp_base = "rtmp://a.rtmp.youtube.com/live2/" use_rtmps = True # 720p 竖屏 YouTube 建议 4–6 Mbps,偏低易触发「数据不足/卡顿」提示 - b_v, maxrate, bufsize = "4000k", "4500k", "8000k" + b_v, maxrate, bufsize = "2800k", "3200k", "5600k" stream_key = None - fast_audio = False # config: fast_audio=1 可跳过 arnndn+长 EQ,显著减轻 CPU + fast_audio = True # 默认轻量音频链,必要时可在配置里关闭 if os.path.exists(config_file): config = configparser.ConfigParser() @@ -343,7 +343,7 @@ def start_douyin_youtube_ffplay( print(f"[WARN] NVENC 检测失败,回退软件编码: {e}") _cpu_n = os.cpu_count() or 4 - _filter_threads = str(min(4, max(1, _cpu_n // 2))) + _filter_threads = str(min(2, max(1, _cpu_n // 2))) # ====================== 开源音频处理:背景音乐弱化 + 防版权 ====================== _script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -372,7 +372,7 @@ def start_douyin_youtube_ffplay( print("[INFO] 未找到 arnndn 模型,使用 afftdn 降噪(可克隆 richardpl/arnndn-models 到 arnndn-models/)") _af_denoise = "afftdn=nf=-26:tn=1," - # 强化版:收紧带通 + 频率偏移 + 音高微调 + 多段EQ + 相位扰动(纯 FFmpeg 理论上限 ~70%) + # 仅在明确需要时才启用重音频链,默认优先保 CPU _af_chain = ( _af_denoise + "highpass=f=200,lowpass=f=3800," @@ -389,14 +389,14 @@ def start_douyin_youtube_ffplay( + "aresample=async=1:first_pts=0" ) - # 720p 竖屏,符合 YouTube 推荐:4–6 Mbps、2s 关键帧、AAC 128k - _video_mid = ["-vsync", "cfr", "-g", "60", "-keyint_min", "60", "-r", "30", + # 720p 竖屏低耗档:更低帧率和码率,优先保证 2 路并发可用 + _video_mid = ["-vsync", "cfr", "-g", "30", "-keyint_min", "30", "-r", "15", "-bf", "0", "-sc_threshold", "0"] if codec_v == "libx264": _video_mid += ["-x264-params", "force-cfr=1:scenecut=0:open_gop=0:sync-lookahead=0"] # -re:按源时间戳节奏拉流,避免突发过快;不用 realtime/arealtime(输入抖动时易让 YouTube 判为数据不足) _vf_pad = ( - "fps=30,scale=720:1280:force_original_aspect_ratio=decrease:flags=bilinear," + "fps=15,scale=720:1280:force_original_aspect_ratio=decrease:flags=bilinear," "pad=720:1280:(ow-iw)/2:(oh-ih)/2:black" ) _ffmpeg_pre_input = [] diff --git a/d2ypp2/live-platform/config/defaults/services/neko.json b/d2ypp2/live-platform/config/defaults/services/neko.json index b29b988..f3a4d07 100644 --- a/d2ypp2/live-platform/config/defaults/services/neko.json +++ b/d2ypp2/live-platform/config/defaults/services/neko.json @@ -6,11 +6,17 @@ "ports": { "http": 9200 }, "env": { "ENABLE_NEKO": "1", + "NEKO_INSTANCE_COUNT": "2", "NEKO_HTTP_PORT": "9200", "NEKO_HTTP_PORT_1": "9200", "NEKO_HTTP_PORT_2": "9201", "NEKO_HTTP_PORT_3": "9202", "NEKO_PROFILE_HOST_DIR": "/opt/live-modules/neko-profile", + "NEKO_SHM_SIZE": "512mb", + "NEKO_DESKTOP_SCREEN": "1280x720@15", + "NEKO_MAX_FPS": "15", + "NEKO_VIDEO_BITRATE": "1200", + "NEKO_BROWSER_RENDER_MODE": "auto", "NEKO_WEBRTC_UDP_RANGE": "52000-52099", "NEKO_WEBRTC_UDP_RANGE_1": "52000-52099", "NEKO_WEBRTC_UDP_RANGE_2": "52101-52199", diff --git a/d2ypp2/scripts/linux/lib/common.sh b/d2ypp2/scripts/linux/lib/common.sh index a761790..e9531c7 100644 --- a/d2ypp2/scripts/linux/lib/common.sh +++ b/d2ypp2/scripts/linux/lib/common.sh @@ -242,6 +242,7 @@ load_env() { NEKO_HTTP_PORT_1="${NEKO_HTTP_PORT_1:-$NEKO_HTTP_PORT}" NEKO_HTTP_PORT_2="${NEKO_HTTP_PORT_2:-9201}" NEKO_HTTP_PORT_3="${NEKO_HTTP_PORT_3:-9202}" + NEKO_INSTANCE_COUNT="${NEKO_INSTANCE_COUNT:-2}" NEKO_WEBRTC_TCPMUX="${NEKO_WEBRTC_TCPMUX:-52300}" NEKO_WEBRTC_UDPMUX="${NEKO_WEBRTC_UDPMUX:-52300}" NEKO_WEBRTC_TCPMUX_1="${NEKO_WEBRTC_TCPMUX_1:-$NEKO_WEBRTC_TCPMUX}" @@ -266,6 +267,11 @@ load_env() { case "$NEKO_IMAGE_ARM64" in ghcr.io/m1k1o/neko/chromium:latest) NEKO_IMAGE_ARM64="ghcr.io/m1k1o/neko/chromium:3.0.0" ;; esac + NEKO_SHM_SIZE="${NEKO_SHM_SIZE:-512mb}" + NEKO_DESKTOP_SCREEN="${NEKO_DESKTOP_SCREEN:-1280x720@15}" + NEKO_MAX_FPS="${NEKO_MAX_FPS:-15}" + NEKO_VIDEO_BITRATE="${NEKO_VIDEO_BITRATE:-1200}" + NEKO_BROWSER_RENDER_MODE="${NEKO_BROWSER_RENDER_MODE:-auto}" ENABLE_REDROID="${ENABLE_REDROID:-1}" REDROID_DEFAULT_PORT="${REDROID_DEFAULT_PORT:-5555}" REDROID_INSTANCE_PREFIX="${REDROID_INSTANCE_PREFIX:-redroid13}" @@ -364,6 +370,54 @@ resolve_neko_profile_dir_for_instance() { esac } +resolve_neko_instance_count() { + local requested="${NEKO_INSTANCE_COUNT:-2}" + local cpu count + cpu="$(nproc 2>/dev/null || echo 4)" + case "$requested" in + 1|2|3) count="$requested" ;; + auto|"") + if [ "$cpu" -le 4 ]; then + count=2 + else + count=3 + fi + ;; + *) + count=2 + ;; + esac + if [ "$count" -lt 1 ]; then + count=1 + fi + if [ "$count" -gt 3 ]; then + count=3 + fi + printf '%s' "$count" +} + +resolve_neko_service_names() { + local count name + count="$(resolve_neko_instance_count)" + for name in 1 2 3; do + [ "$name" -le "$count" ] || break + printf 'neko%s\n' "$name" + done +} + +resolve_neko_instances_csv() { + resolve_neko_service_names | paste -sd, - +} + +resolve_neko_http_port_for_instance() { + case "${1:-1}" in + 1) printf '%s' "${NEKO_HTTP_PORT_1:-${NEKO_HTTP_PORT:-9200}}" ;; + 2) printf '%s' "${NEKO_HTTP_PORT_2:-9201}" ;; + 3) printf '%s' "${NEKO_HTTP_PORT_3:-9202}" ;; + *) printf '%s' "${NEKO_HTTP_PORT_1:-${NEKO_HTTP_PORT:-9200}}" ;; + esac +} + remove_legacy_neko_container() { if have docker && docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qx 'live-neko-chromium'; then docker stop live-neko-chromium >/dev/null 2>&1 || true diff --git a/d2ypp2/scripts/linux/service_ctl.sh b/d2ypp2/scripts/linux/service_ctl.sh index a898bf6..92d0f16 100644 --- a/d2ypp2/scripts/linux/service_ctl.sh +++ b/d2ypp2/scripts/linux/service_ctl.sh @@ -49,6 +49,7 @@ NEKO_HTTP_PORT="${NEKO_HTTP_PORT:-9200}" NEKO_HTTP_PORT_1="${NEKO_HTTP_PORT_1:-$NEKO_HTTP_PORT}" NEKO_HTTP_PORT_2="${NEKO_HTTP_PORT_2:-9201}" NEKO_HTTP_PORT_3="${NEKO_HTTP_PORT_3:-9202}" +NEKO_INSTANCE_COUNT="${NEKO_INSTANCE_COUNT:-2}" NEKO_WEBRTC_TCPMUX="${NEKO_WEBRTC_TCPMUX:-52300}" NEKO_WEBRTC_UDPMUX="${NEKO_WEBRTC_UDPMUX:-52300}" NEKO_WEBRTC_TCPMUX_1="${NEKO_WEBRTC_TCPMUX_1:-$NEKO_WEBRTC_TCPMUX}" @@ -74,6 +75,11 @@ esac case "$NEKO_IMAGE_ARM64" in ghcr.io/m1k1o/neko/chromium:latest) NEKO_IMAGE_ARM64="ghcr.io/m1k1o/neko/chromium:3.0.0" ;; esac +NEKO_SHM_SIZE="${NEKO_SHM_SIZE:-512mb}" +NEKO_DESKTOP_SCREEN="${NEKO_DESKTOP_SCREEN:-1280x720@15}" +NEKO_MAX_FPS="${NEKO_MAX_FPS:-15}" +NEKO_VIDEO_BITRATE="${NEKO_VIDEO_BITRATE:-1200}" +NEKO_BROWSER_RENDER_MODE="${NEKO_BROWSER_RENDER_MODE:-auto}" ENABLE_NEKO="${ENABLE_NEKO:-1}" ENABLE_MITMPROXY="${ENABLE_MITMPROXY:-1}" MITMPROXY_WEB_PORT="${MITMPROXY_WEB_PORT:-8086}" @@ -292,6 +298,32 @@ resolve_neko_profile_dir_for_instance() { esac } +resolve_neko_instance_count() { + local requested="${NEKO_INSTANCE_COUNT:-2}" + local cpu count + cpu="$(nproc 2>/dev/null || echo 4)" + case "$requested" in + 1|2|3) count="$requested" ;; + auto|"") + if [ "$cpu" -le 4 ]; then + count=2 + else + count=3 + fi + ;; + *) + count=2 + ;; + esac + if [ "$count" -lt 1 ]; then + count=1 + fi + if [ "$count" -gt 3 ]; then + count=3 + fi + printf '%s' "$count" +} + remove_legacy_neko_container() { if have docker && docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qx 'live-neko-chromium'; then docker stop live-neko-chromium >/dev/null 2>&1 || true @@ -810,8 +842,15 @@ service_neko_status() { fi kv available 1 local state - local running_count=0 total_count=3 states="" name + local running_count=0 total_count=0 states="" name instance_count + instance_count="$(resolve_neko_instance_count)" + total_count="$instance_count" for name in live-neko-1 live-neko-2 live-neko-3; do + case "$name" in + live-neko-1) [ "$instance_count" -ge 1 ] || continue ;; + live-neko-2) [ "$instance_count" -ge 2 ] || continue ;; + live-neko-3) [ "$instance_count" -ge 3 ] || continue ;; + esac state="$(docker_container_status "$name" || true)" if echo "$state" | grep -qi running; then running_count=$((running_count + 1)) @@ -834,7 +873,7 @@ service_neko_status() { if ! neko_nat1to1_is_usable "${NEKO_WEBRTC_NAT1TO1:-}"; then NEKO_WEBRTC_NAT1TO1="$(detect_preferred_lan_ip || true)" fi - kv detail "image=$(resolve_neko_image); supervisor_conf=$(resolve_neko_supervisor_conf); start_url=${NEKO_START_URL}; nat1to1=${NEKO_WEBRTC_NAT1TO1:-unset}; instances=${running_count}/${total_count}; ports=${NEKO_HTTP_PORT_1},${NEKO_HTTP_PORT_2},${NEKO_HTTP_PORT_3}; profiles=$(resolve_neko_profile_dir_for_instance 1),$(resolve_neko_profile_dir_for_instance 2),$(resolve_neko_profile_dir_for_instance 3); udp_ranges=${NEKO_WEBRTC_UDP_RANGE_1},${NEKO_WEBRTC_UDP_RANGE_2},${NEKO_WEBRTC_UDP_RANGE_3}; tcpmux=${NEKO_WEBRTC_TCPMUX_1},${NEKO_WEBRTC_TCPMUX_2},${NEKO_WEBRTC_TCPMUX_3}; udpmux=${NEKO_WEBRTC_UDPMUX_1},${NEKO_WEBRTC_UDPMUX_2},${NEKO_WEBRTC_UDPMUX_3}; containers=${states}" + kv detail "image=$(resolve_neko_image); supervisor_conf=$(resolve_neko_supervisor_conf); start_url=${NEKO_START_URL}; nat1to1=${NEKO_WEBRTC_NAT1TO1:-unset}; instances=${running_count}/${total_count}; ports=${NEKO_HTTP_PORT_1},${NEKO_HTTP_PORT_2},${NEKO_HTTP_PORT_3}; profiles=$(resolve_neko_profile_dir_for_instance 1),$(resolve_neko_profile_dir_for_instance 2),$(resolve_neko_profile_dir_for_instance 3); screen=${NEKO_DESKTOP_SCREEN}; shm=${NEKO_SHM_SIZE}; fps=${NEKO_MAX_FPS}; bitrate=${NEKO_VIDEO_BITRATE}; render=${NEKO_BROWSER_RENDER_MODE}; udp_ranges=${NEKO_WEBRTC_UDP_RANGE_1},${NEKO_WEBRTC_UDP_RANGE_2},${NEKO_WEBRTC_UDP_RANGE_3}; tcpmux=${NEKO_WEBRTC_TCPMUX_1},${NEKO_WEBRTC_TCPMUX_2},${NEKO_WEBRTC_TCPMUX_3}; udpmux=${NEKO_WEBRTC_UDPMUX_1},${NEKO_WEBRTC_UDPMUX_2},${NEKO_WEBRTC_UDPMUX_3}; containers=${states}" } service_neko_start() { @@ -847,23 +886,73 @@ service_neko_start() { return 0 fi local prof + local instance_count + local service_names="" extra_service_names="" idx_name + instance_count="$(resolve_neko_instance_count)" + for idx_name in 1 2 3; do + case "$idx_name" in + 1) + if [ "$instance_count" -ge 1 ]; then + service_names="${service_names} neko1" + else + extra_service_names="${extra_service_names} neko1" + fi + ;; + 2) + if [ "$instance_count" -ge 2 ]; then + service_names="${service_names} neko2" + else + extra_service_names="${extra_service_names} neko2" + fi + ;; + 3) + if [ "$instance_count" -ge 3 ]; then + service_names="${service_names} neko3" + else + extra_service_names="${extra_service_names} neko3" + fi + ;; + esac + done for idx in 1 2 3; do + [ "$idx" -le "$instance_count" ] || break prof="$(resolve_neko_profile_dir_for_instance "$idx")" prune_neko_profile "$prof" done export_neko_env for port in "$NEKO_HTTP_PORT_1" "$NEKO_HTTP_PORT_2" "$NEKO_HTTP_PORT_3" "$NEKO_WEBRTC_TCPMUX_1" "$NEKO_WEBRTC_TCPMUX_2" "$NEKO_WEBRTC_TCPMUX_3"; do + case "$port" in + "$NEKO_HTTP_PORT_1"|"$NEKO_WEBRTC_TCPMUX_1") [ "$instance_count" -ge 1 ] || continue ;; + "$NEKO_HTTP_PORT_2"|"$NEKO_WEBRTC_TCPMUX_2") [ "$instance_count" -ge 2 ] || continue ;; + "$NEKO_HTTP_PORT_3"|"$NEKO_WEBRTC_TCPMUX_3") [ "$instance_count" -ge 3 ] || continue ;; + esac allow_tcp_port_if_firewall_active "$port" done for port in "$NEKO_WEBRTC_UDPMUX_1" "$NEKO_WEBRTC_UDPMUX_2" "$NEKO_WEBRTC_UDPMUX_3"; do + case "$port" in + "$NEKO_WEBRTC_UDPMUX_1") [ "$instance_count" -ge 1 ] || continue ;; + "$NEKO_WEBRTC_UDPMUX_2") [ "$instance_count" -ge 2 ] || continue ;; + "$NEKO_WEBRTC_UDPMUX_3") [ "$instance_count" -ge 3 ] || continue ;; + esac allow_udp_port_if_firewall_active "$port" done for port_range in "$NEKO_WEBRTC_UDP_RANGE_1" "$NEKO_WEBRTC_UDP_RANGE_2" "$NEKO_WEBRTC_UDP_RANGE_3"; do + case "$port_range" in + "$NEKO_WEBRTC_UDP_RANGE_1") [ "$instance_count" -ge 1 ] || continue ;; + "$NEKO_WEBRTC_UDP_RANGE_2") [ "$instance_count" -ge 2 ] || continue ;; + "$NEKO_WEBRTC_UDP_RANGE_3") [ "$instance_count" -ge 3 ] || continue ;; + esac allow_udp_range_if_firewall_active "$port_range" done remove_legacy_neko_container - compose_in_service_dir "$NEKO_COMPOSE" up -d - kv message "Neko stack started (3 browser instances)" + # 只启动实际需要的实例,避免多余的 Chrome/Xorg 进程继续吃 CPU + # shellcheck disable=SC2086 + compose_in_service_dir "$NEKO_COMPOSE" up -d $service_names + if [ -n "${extra_service_names# }" ]; then + # shellcheck disable=SC2086 + compose_in_service_dir "$NEKO_COMPOSE" stop $extra_service_names >/dev/null 2>&1 || true + fi + kv message "Neko stack started (${instance_count} browser instances)" } export_neko_env() { @@ -871,6 +960,7 @@ export_neko_env() { export NEKO_HTTP_PORT_1="${NEKO_HTTP_PORT_1:-$NEKO_HTTP_PORT}" export NEKO_HTTP_PORT_2="${NEKO_HTTP_PORT_2:-9201}" export NEKO_HTTP_PORT_3="${NEKO_HTTP_PORT_3:-9202}" + export NEKO_INSTANCE_COUNT="${NEKO_INSTANCE_COUNT:-2}" export NEKO_USER_PASS="${NEKO_USER_PASS:-12345678}" export NEKO_ADMIN_PASS="${NEKO_ADMIN_PASS:-12345678}" export NEKO_PROFILE_HOST_DIR="${NEKO_PROFILE_HOST_DIR:-$(resolve_neko_profile_dir)}" @@ -880,7 +970,11 @@ export_neko_env() { export NEKO_START_URL="${NEKO_START_URL:-https://accounts.google.com/ServiceLogin?continue=https://www.youtube.com/}" export NEKO_IMAGE="$(resolve_neko_image)" export NEKO_BROWSER_SUPERVISOR_CONF="$(resolve_neko_supervisor_conf)" - export NEKO_DESKTOP_SCREEN="${NEKO_DESKTOP_SCREEN:-1920x1080@30}" + export NEKO_SHM_SIZE="${NEKO_SHM_SIZE:-512mb}" + export NEKO_DESKTOP_SCREEN="${NEKO_DESKTOP_SCREEN:-1280x720@15}" + export NEKO_MAX_FPS="${NEKO_MAX_FPS:-15}" + export NEKO_VIDEO_BITRATE="${NEKO_VIDEO_BITRATE:-1200}" + export NEKO_BROWSER_RENDER_MODE="${NEKO_BROWSER_RENDER_MODE:-auto}" export NEKO_WEBRTC_UDP_RANGE="${NEKO_WEBRTC_UDP_RANGE:-52000-52099}" export NEKO_WEBRTC_UDP_RANGE_1="${NEKO_WEBRTC_UDP_RANGE_1:-$NEKO_WEBRTC_UDP_RANGE}" export NEKO_WEBRTC_UDP_RANGE_2="${NEKO_WEBRTC_UDP_RANGE_2:-52101-52199}" diff --git a/d2ypp2/services/neko/browser-launch.sh b/d2ypp2/services/neko/browser-launch.sh index b78b712..52b53fc 100644 --- a/d2ypp2/services/neko/browser-launch.sh +++ b/d2ypp2/services/neko/browser-launch.sh @@ -36,7 +36,7 @@ case "$render_mode" in auto) case "$arch:$family" in x86_64:google-chrome|amd64:google-chrome) - render_flags="--disable-gpu --disable-software-rasterizer" + render_flags="--disable-gpu --disable-software-rasterizer --disable-features=UseSkiaRenderer" ;; esac ;; @@ -44,10 +44,10 @@ case "$render_mode" in render_flags="" ;; legacy|x11|disable-gpu) - render_flags="--disable-gpu --disable-software-rasterizer" + render_flags="--disable-gpu --disable-software-rasterizer --disable-features=UseSkiaRenderer" ;; software|swiftshader) - render_flags="--disable-gpu --use-gl=swiftshader --enable-unsafe-swiftshader" + render_flags="--disable-gpu --use-gl=swiftshader --enable-unsafe-swiftshader --disable-features=UseSkiaRenderer" ;; esac @@ -75,8 +75,17 @@ exec "$browser_bin" \ --user-data-dir="$profile_dir" \ --password-store=basic \ --no-first-run \ - --start-maximized \ + --start-minimized \ --force-dark-mode \ --disable-file-system \ --disable-dev-shm-usage \ + --disable-background-networking \ + --disable-breakpad \ + --disable-default-apps \ + --disable-extensions \ + --disable-sync \ + --disable-translate \ + --metrics-recording-only \ + --no-default-browser-check \ + --no-first-run \ "$start_url" diff --git a/d2ypp2/services/neko/chromium.conf b/d2ypp2/services/neko/chromium.conf index a3f24a4..36a9e7b 100644 --- a/d2ypp2/services/neko/chromium.conf +++ b/d2ypp2/services/neko/chromium.conf @@ -1,16 +1,6 @@ [program:chromium] environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" -command=/usr/bin/chromium - --window-position=0,0 - --display=%(ENV_DISPLAY)s - --user-data-dir=/home/neko/.config/chromium - --no-first-run - --start-maximized - --force-dark-mode - --disable-file-system - --disable-gpu - --disable-software-rasterizer - --disable-dev-shm-usage +command=/etc/neko/browser-launch.sh chromium stopsignal=INT autorestart=true priority=800 diff --git a/d2ypp2/services/neko/docker-compose.yml b/d2ypp2/services/neko/docker-compose.yml index dbc5367..67eeef7 100644 --- a/d2ypp2/services/neko/docker-compose.yml +++ b/d2ypp2/services/neko/docker-compose.yml @@ -1,7 +1,7 @@ x-neko-common: &neko-common image: "${NEKO_IMAGE:-ghcr.io/m1k1o/neko/chromium:3.0.0}" restart: unless-stopped - shm_size: "${NEKO_SHM_SIZE:-2gb}" + shm_size: "${NEKO_SHM_SIZE:-512mb}" cap_add: - SYS_ADMIN @@ -14,9 +14,12 @@ x-neko-env: &neko-env NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: "${NEKO_ADMIN_PASS}" NEKO_WEBRTC_ICELITE: "${NEKO_WEBRTC_ICELITE}" NEKO_WEBRTC_NAT1TO1: "${NEKO_WEBRTC_NAT1TO1}" + NEKO_MAX_FPS: "${NEKO_MAX_FPS}" + NEKO_VIDEO_BITRATE: "${NEKO_VIDEO_BITRATE}" NEKO_PLUGINS_ENABLED: "${NEKO_PLUGINS_ENABLED}" NEKO_PLUGINS_DIR: "/etc/neko/plugins/" NEKO_START_URL: "${NEKO_START_URL}" + NEKO_BROWSER_RENDER_MODE: "${NEKO_BROWSER_RENDER_MODE}" services: neko1: @@ -37,6 +40,7 @@ services: - "${NEKO_PROFILE_HOST_DIR_1}:/home/neko/.config/google-chrome" - "./policies:/etc/chromium/policies/managed:ro" - "./policies:/etc/opt/chrome/policies/managed:ro" + - "./browser-launch.sh:/etc/neko/browser-launch.sh:ro" - "./${NEKO_BROWSER_SUPERVISOR_CONF}:/etc/neko/supervisord/browser.conf:ro" neko2: @@ -57,6 +61,7 @@ services: - "${NEKO_PROFILE_HOST_DIR_2}:/home/neko/.config/google-chrome" - "./policies:/etc/chromium/policies/managed:ro" - "./policies:/etc/opt/chrome/policies/managed:ro" + - "./browser-launch.sh:/etc/neko/browser-launch.sh:ro" - "./${NEKO_BROWSER_SUPERVISOR_CONF}:/etc/neko/supervisord/browser.conf:ro" neko3: @@ -77,4 +82,5 @@ services: - "${NEKO_PROFILE_HOST_DIR_3}:/home/neko/.config/google-chrome" - "./policies:/etc/chromium/policies/managed:ro" - "./policies:/etc/opt/chrome/policies/managed:ro" + - "./browser-launch.sh:/etc/neko/browser-launch.sh:ro" - "./${NEKO_BROWSER_SUPERVISOR_CONF}:/etc/neko/supervisord/browser.conf:ro" diff --git a/d2ypp2/services/neko/google-chrome.conf b/d2ypp2/services/neko/google-chrome.conf index 3cf1c61..f47926e 100644 --- a/d2ypp2/services/neko/google-chrome.conf +++ b/d2ypp2/services/neko/google-chrome.conf @@ -1,16 +1,6 @@ [program:google-chrome] environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" -command=/usr/bin/google-chrome - --window-position=0,0 - --display=%(ENV_DISPLAY)s - --user-data-dir=/home/neko/.config/google-chrome - --no-first-run - --start-maximized - --force-dark-mode - --disable-file-system - --disable-gpu - --disable-software-rasterizer - --disable-dev-shm-usage +command=/etc/neko/browser-launch.sh google-chrome stopsignal=INT autorestart=true priority=800 diff --git a/d2ypp2/src/control_plane.py b/d2ypp2/src/control_plane.py index f4a983b..81d957a 100644 --- a/d2ypp2/src/control_plane.py +++ b/d2ypp2/src/control_plane.py @@ -497,11 +497,12 @@ def _env_int(env: dict[str, str], key: str, default: int) -> int: def _neko_http_ports(env: dict[str, str]) -> list[int]: + count = max(1, min(3, _env_int(env, "NEKO_INSTANCE_COUNT", 2))) return [ _env_int(env, "NEKO_HTTP_PORT_1", _env_int(env, "NEKO_HTTP_PORT", 9200)), _env_int(env, "NEKO_HTTP_PORT_2", 9201), _env_int(env, "NEKO_HTTP_PORT_3", 9202), - ] + ][:count] def _neko_instances(mdns_host: str, env: dict[str, str]) -> list[dict[str, Any]]: @@ -877,13 +878,14 @@ async def stack_stream_probes() -> dict[str, Any]: http = _probe_http_get(f"http://127.0.0.1:{port}/", timeout=2.5) instances.append({"id": idx, "port": port, "tcp": tcp, "http": http}) primary = instances[0] if instances else {"port": 0, "tcp": {}, "http": {}} + required_count = max(1, min(3, _env_int(env, "NEKO_INSTANCE_COUNT", 2))) return { "port": primary["port"], "tcp": primary["tcp"], "http": primary["http"], "instances": instances, "running_count": sum(1 for item in instances if item["tcp"].get("ok")), - "required_count": 3, + "required_count": required_count, } srs_d, neko_d = await asyncio.gather( diff --git a/d2ypp2/tests/test_neko_config.py b/d2ypp2/tests/test_neko_config.py index 96c2166..a1a2185 100644 --- a/d2ypp2/tests/test_neko_config.py +++ b/d2ypp2/tests/test_neko_config.py @@ -15,6 +15,8 @@ class NekoConfigTests(unittest.TestCase): self.assertIn("--disable-software-rasterizer", script) self.assertIn("--use-gl=swiftshader", script) self.assertIn("NEKO_BROWSER_RENDER_MODE", script) + self.assertIn("--start-minimized", script) + self.assertIn("--disable-background-networking", script) self.assertIn('prune_path "$profile_dir/BrowserMetrics"', script) self.assertIn('prune_path "$profile_dir/Crash Reports"', script) self.assertIn('prune_path "$profile_dir/SingletonLock"', script) @@ -30,28 +32,29 @@ class NekoConfigTests(unittest.TestCase): def test_google_chrome_supervisor_uses_direct_stable_command(self) -> None: conf = (ROOT / "services" / "neko" / "google-chrome.conf").read_text(encoding="utf-8") - self.assertIn("command=/usr/bin/google-chrome", conf) - self.assertNotIn("browser-launch.sh", conf) - self.assertIn("--user-data-dir=/home/neko/.config/google-chrome", conf) + self.assertIn("command=/etc/neko/browser-launch.sh google-chrome", conf) self.assertNotIn("--bwsi", conf) - self.assertIn("--disable-software-rasterizer", conf) self.assertIn("autorestart=true", conf) self.assertIn("[program:openbox]", conf) self.assertIn("/usr/bin/openbox --config-file /etc/neko/openbox.xml", conf) + script = (ROOT / "services" / "neko" / "browser-launch.sh").read_text(encoding="utf-8") + self.assertIn('profile_dir="/home/neko/.config/google-chrome"', script) + self.assertIn('--user-data-dir="$profile_dir"', script) def test_chromium_supervisor_uses_direct_stable_command(self) -> None: conf = (ROOT / "services" / "neko" / "chromium.conf").read_text(encoding="utf-8") - self.assertIn("command=/usr/bin/chromium", conf) - self.assertNotIn("browser-launch.sh", conf) - self.assertIn("--user-data-dir=/home/neko/.config/chromium", conf) + self.assertIn("command=/etc/neko/browser-launch.sh chromium", conf) self.assertNotIn("--bwsi", conf) - self.assertIn("--disable-software-rasterizer", conf) self.assertIn("autorestart=true", conf) self.assertIn("[program:openbox]", conf) + script = (ROOT / "services" / "neko" / "browser-launch.sh").read_text(encoding="utf-8") + self.assertIn('profile_dir="/home/neko/.config/chromium"', script) + self.assertIn('--user-data-dir="$profile_dir"', script) def test_neko_compose_mounts_only_selected_supervisor_conf(self) -> None: compose = (ROOT / "services" / "neko" / "docker-compose.yml").read_text(encoding="utf-8") self.assertIn("./${NEKO_BROWSER_SUPERVISOR_CONF}:/etc/neko/supervisord/browser.conf:ro", compose) + self.assertIn("./browser-launch.sh:/etc/neko/browser-launch.sh:ro", compose) self.assertIn("container_name: live-neko-1", compose) self.assertIn("container_name: live-neko-2", compose) self.assertIn("container_name: live-neko-3", compose) @@ -60,7 +63,14 @@ class NekoConfigTests(unittest.TestCase): self.assertIn("${NEKO_HTTP_PORT_3}:8080", compose) self.assertNotIn("./chromium.conf:/etc/neko/supervisord/chromium.conf:ro", compose) self.assertNotIn("./google-chrome.conf:/etc/neko/supervisord/google-chrome.conf:ro", compose) - self.assertNotIn("./browser-launch.sh:/etc/neko/browser-launch.sh:ro", compose) + + def test_supervisor_uses_browser_launch_script(self) -> None: + chrome = (ROOT / "services" / "neko" / "google-chrome.conf").read_text(encoding="utf-8") + chromium = (ROOT / "services" / "neko" / "chromium.conf").read_text(encoding="utf-8") + self.assertIn("command=/etc/neko/browser-launch.sh google-chrome", chrome) + self.assertIn("command=/etc/neko/browser-launch.sh chromium", chromium) + self.assertNotIn("--start-maximized", chrome) + self.assertNotIn("--start-maximized", chromium) def test_neko_scripts_use_family_specific_profile_dirs(self) -> None: service_ctl = (ROOT / "scripts" / "linux" / "service_ctl.sh").read_text(encoding="utf-8") diff --git a/d2ypp2/tests/test_youtube_runtime.py b/d2ypp2/tests/test_youtube_runtime.py index 80fca07..acc3a2e 100644 --- a/d2ypp2/tests/test_youtube_runtime.py +++ b/d2ypp2/tests/test_youtube_runtime.py @@ -18,6 +18,18 @@ class YoutubeRuntimeTests(unittest.TestCase): self.assertEqual(normalize_youtube_video_save_type("ts", "tiktok"), "TS") self.assertEqual(normalize_youtube_video_save_type("unknown", "tiktok"), "TS") + def test_push_defaults_use_lighter_profile(self) -> None: + from pathlib import Path + from tempfile import TemporaryDirectory + from douyin_youtube_ffplay import _load_youtube_push_settings + + with TemporaryDirectory() as tmp: + cfg = Path(tmp) / "youtube.ini" + cfg.write_text("[youtube]\nkey = abc\n", encoding="utf-8") + settings = _load_youtube_push_settings(str(cfg)) + self.assertEqual(settings["b_v"], "2800k") + self.assertTrue(settings["fast_audio"]) + def test_arm_falls_back_from_vaapi_to_v4l2m2m(self) -> None: enc, note = resolve_live_video_encoder( "h264_vaapi",