Radio z efektami na kanałach

Ten serwer przesyła głos radiowy między graczami na tym samym kanale i nakłada na niego efekt (wokoder 3200, radio analogowe, cyfrowe P25). Mowa zwykła zostaje na pma-voice.

Status tego serwera: sprawdzam…

Jak to działa

Gracz (FiveM)Skrypt cs_pdradio + ukryta ramka głosu w NUI, mikrofon, efekt
HTTPS / WSS
audio 16 kHz
VPSCaddy (HTTPS) → serwer Node (ten program), przekaźnik kanałów
API + sekret
(zgoda na kanał)
Serwer FiveMSprawdza uprawnienia, wydaje jednorazowy token wejścia na kanał

Test na localhost (bez gry)

Strona działa wyłącznie przez serwer. Nie otwieraj test.html z dysku (file://), bo przeglądarka zablokuje mikrofon i zapytania.

  1. Zainstaluj Node.js 18 lub nowszyPobierz wersję LTS z nodejs.org. Sprawdź:
    node -v
  2. Uruchom serwerW folderze web kliknij dwukrotnie start-test.bat. To samo ręcznie:
    cd web
    npm install
    npm start
  3. Otwórz stronę testowąhttp://localhost:3000/test — wpisz nick i kanał, kliknij „Połącz”, przytrzymaj spację i mów.
  4. Sprawdź z drugą osobą (albo drugą kartą)Otwórz tę samą stronę w drugiej karcie z tym samym kanałem. Albo w jednej karcie włącz „Słyszę siebie”. Używaj słuchawek.
  5. Pobaw się efektemZmieniaj preset (Wokoder 3200, Analog, Cyfrowy) i suwak jakości sygnału. Poniżej 45% głos zaczyna się rwać, poniżej 3% radio nie ma zasięgu.
Na localhost przeglądarka pozwala na mikrofon bez HTTPS. Na VPS HTTPS jest obowiązkowy — patrz niżej.

Wdrożenie na VPS (Ubuntu 22.04 / 24.04)

Twój VPS: 49.13.53.85 (Ubuntu). Wystarczy 1 vCPU i 1 GB RAM na kilkadziesiąt osób. Wszystkie polecenia poniżej są już dla tego adresu.

Dlaczego nie samo http://49.13.53.85? Menu w grze (NUI) działa na HTTPS, więc przeglądarka w FiveM zablokuje ramkę z http:// oraz mikrofon. Zwykły certyfikat nie wystawia się na sam numer IP, dlatego używamy nazwy 49-13-53-85.sslip.io: to darmowa usługa sslip.io, która sama wskazuje na 49.13.53.85, więc nie musisz kupować domeny. Gdy kiedyś kupisz własną domenę, wystarczy podmienić nazwę w Caddyfile i w config.lua.
  1. Sprawdź nazwę (bez konfiguracji DNS)Nazwa 49-13-53-85.sslip.io od razu wskazuje na 49.13.53.85. Sprawdź na swoim komputerze:
    nslookup 49-13-53-85.sslip.io
    Powinien wrócić adres 49.13.53.85.
  2. Zaloguj się i zaktualizuj system, włącz zaporęNajpierw połącz się (z komputera):
    na Twoim komputerze
    ssh root@49.13.53.85
    na VPS
    sudo apt update && sudo apt -y upgrade
    sudo apt -y install ufw curl
    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw enable

    Hetzner ma też osobną zaporę w panelu (Cloud Console → Firewalls). Jeśli ją masz, dodaj tam reguły przychodzące TCP 80 i 443 (22 już działa). Bez portu 80 certyfikat się nie wystawi.

    Portu 3000 nie otwieraj. Serwer Node nasłuchuje tylko lokalnie, a ruch z internetu przechodzi przez Caddy (HTTPS).

  3. Zainstaluj Node.js 20
    curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
    sudo apt -y install nodejs
    node -v
  4. Wgraj pliki serweraSkopiuj folder web z komputera na VPS (z komputera, nie z VPS):
    na Twoim komputerze
    scp -r web root@49.13.53.85:/tmp/cs-radio
    ssh root@49.13.53.85
    na VPS
    sudo mkdir -p /opt/cs-radio
    sudo cp -r /tmp/cs-radio/. /opt/cs-radio/
    cd /opt/cs-radio
    sudo npm install --omit=dev
  5. Utwórz plik .env z sekretemSekret to hasło między serwerem FiveM a serwerem webowym. Musi być długi i losowy:
    cd /opt/cs-radio
    SECRET=$(openssl rand -hex 32)
    echo "$SECRET"            # skopiuj, przyda się w sv_config.lua
    sudo tee .env >/dev/null <<EOF
    NODE_ENV=production
    HOST=127.0.0.1
    PORT=3000
    RADIO_SECRET=$SECRET
    EOF
    sudo chmod 600 .env

    W trybie production serwer odmówi startu, jeśli RADIO_SECRET jest pusty lub równy CHANGE_ME. Endpoint testowy /api/dev-token jest wtedy wyłączony.

  6. Uruchom jako usługę (systemd)
    /etc/systemd/system/cs-radio.service
    [Unit]
    Description=cs_pdradio web (voice relay)
    After=network.target
    
    [Service]
    WorkingDirectory=/opt/cs-radio
    ExecStart=/usr/bin/node server.js
    Restart=always
    RestartSec=3
    User=www-data
    Group=www-data
    NoNewPrivileges=true
    
    [Install]
    WantedBy=multi-user.target
    sudo chown -R www-data:www-data /opt/cs-radio
    sudo systemctl daemon-reload
    sudo systemctl enable --now cs-radio
    sudo systemctl status cs-radio --no-pager
    curl http://127.0.0.1:3000/api/health
  7. Włącz HTTPS (Caddy, certyfikat automatycznie)
    sudo apt -y install debian-keyring debian-archive-keyring apt-transport-https
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
    sudo apt update && sudo apt -y install caddy
    /etc/caddy/Caddyfile
    49-13-53-85.sslip.io {
        encode gzip
        reverse_proxy 127.0.0.1:3000
    }
    sudo systemctl reload caddy
    curl https://49-13-53-85.sslip.io/api/health

    Caddy sam pobierze certyfikat Let's Encrypt dla 49-13-53-85.sslip.io i obsłuży WebSocket (wss://). Wolisz nginx? Użyj bloku location / z proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; i proxy_pass http://127.0.0.1:3000;.

    HTTPS jest obowiązkowy. Menu NUI w FiveM działa na HTTPS, więc przeglądarka (CEF) zablokuje ramkę z http:// (mieszana treść) i mikrofon. Wyjątek to wyłącznie localhost.

  8. Sprawdź z zewnątrzNa swoim komputerze otwórz https://49-13-53-85.sslip.io/ (certyfikat wystawia się przy pierwszym wejściu, daj mu kilkanaście sekund). U góry tej strony status powinien być zielony, a https://49-13-53-85.sslip.io/test pozwoli przetestować głos na serwerze produkcyjnym dopiero po zalogowaniu na /live (hasło z instalatora). Bez logowania pokaże komunikat o braku dostępu, to jest zamierzone.

Aktualizacja i logi

sudo journalctl -u cs-radio -f          # logi na żywo
sudo systemctl restart cs-radio          # restart po zmianach
# aktualizacja: wgraj nowe pliki do /opt/cs-radio, potem
cd /opt/cs-radio && sudo npm install --omit=dev && sudo systemctl restart cs-radio

Podpięcie pod serwer FiveM

  1. Wgraj zasobySkopiuj do resources/ serwera gry oba foldery z resource/: cs_pdradio (skrypt) i cs_pdradio_props (modele: wieża radiowa, anteny, drabinka, przekaźnik komórkowy, serwerownia; 16 MB). Bez cs_pdradio_props wieże nie będą widoczne w świecie, a reszta radia działa normalnie.
  2. Adres dla graczy — config.lua
    cs_pdradio/config.lua
    Config.voice = {
        mode   = 'web',
        webUrl = 'https://49-13-53-85.sslip.io',   -- publiczny adres HTTPS (to już jest ustawione)
    }
  3. Sekret i adres API — sv_config.lua (tylko serwer)
    cs_pdradio/sv_config.lua
    ServerConfig = {
        -- działa z każdego miejsca (FiveM na VPS lub na innej maszynie):
        webApi = 'https://49-13-53-85.sslip.io',
        -- FiveM na TYM SAMYM VPS (49.13.53.85):  webApi = 'http://127.0.0.1:3000'
        secret = 'TU_WKLEJ_SEKRET_Z_ENV',          -- identyczny jak RADIO_SECRET
    }

    Sekret możesz też ustawić w server.cfg: set cs_pdradio_secret "..." (ma pierwszeństwo przed plikiem). Plik sv_config.lua nigdy nie trafia do graczy.

  4. server.cfg
    ensure pma-voice
    ensure cs_pdradio_props
    ensure cs_pdradio
    
    # administratorzy radia (panel /radioadmin)
    add_ace group.admin cs_pdradio.admin allow
    # opcjonalnie: odporność na jammery, uprawnienia do grup kanałów
    add_ace group.police cs_pdradio.jammerimmune allow
  5. Uruchom i sprawdźW grze: /radio, potem /radio power. Na ekranie radia nie powinno być napisu VOICE OFFLINE. W konsoli serwera przy problemie pojawi się komunikat o nieudanym grant. W panelu /radioadmin zakładka Overview pokazuje, czy serwer webowy jest online.
Skrypt nie zmienia mowy zwykłej: dalej działa pma-voice. Gdy ktoś nadaje na radiu, osoby obok słyszą go normalnie, jak w prawdziwym życiu.

Użycie i panel admina

KomendaOpis
/radioPokaż / schowaj radio (animacja z krótkofalówką)
/radio powerWłącz / wyłącz zasilanie
/radiovolume 0-100Głośność radia
/radioskin nazwaZmiana wyglądu (default, hi-vis, signalpro, voxguard)
/radiosignalAktualna jakość sygnału
/radiotalkWłącz / wyłącz animację nadawania
/radiorepeaterWłącz / wyłącz przekaźnik w pojeździe (jeśli admin go dodał)
/radiodebugStan radia w konsoli F8 (do diagnozy)
/radioadminPanel admina (ACE cs_pdradio.admin)

PTT (nadawanie) ustawiasz w ustawieniach GTA: Key Bindings → FiveM → Radio: push to talk (domyślnie CapsLock). Kursor nad radiem (żeby klikać przyciski) i inne klawisze ustawiasz tam samo.

Panel admina (/radioadmin)

ZakładkaCo robisz
OverviewKto jest na radiu i na którym kanale (z możliwością usunięcia), status serwera webowego
RepeatersPojazdy, które mogą działać jako ruchome wieże (etykieta, zasięg)
TowersDodajesz wieże w swojej pozycji, ustawiasz zasięg, zdrowie (zniszczone anteny), włączasz i wyłączasz, teleportujesz się do wieży
JammersStawiasz jammer w swojej pozycji z wybranym zasięgiem (obiekt w świecie). Radia w zasięgu się rozłączają
Dead zonesStrefy osłabiania sygnału (tunele, garaże podziemne)
ChannelsGrupy i kanały, uprawnienia przez ACE i joby, zapis na żywo
EffectsPreset efektu, siła wokodera, szum, sygnały „roger beep”. Zmiana dotyczy wszystkich graczy od razu

Dane (wieże, jammery, strefy, kanały, efekty) zapisują się w cs_pdradio/data/state.json i wracają po restarcie. Przycisk „Show on map” w stopce panelu rysuje wieże, jammery i strefy na mapie.

Efekty głosu

Efekty odwzorowują to, co naprawdę robi radio, zamiast „losowych” filtrów. Wszystko liczy się w przeglądarce odbiorcy, na podstawie jakości sygnału z gry.

PresetCo słychać
Analog FM radioPasmo 300–3000 Hz, kompresja i miękkie ograniczanie jak w radiu FM, „nosowy” środek pasma. Trzask otwarcia squelcha na początku odbioru, „ogon” szumu po puszczeniu PTT i sygnał końca nadawania (roger beep).
Vocoder 3200Prawdziwy wokoder LPC (jak kodeki radiowe): głos jest rozkładany na obwiednię widma, wysokość i głośność, a potem syntezowany z buczenia podążającego za wysokością głosu mówcy. Pasmo 300–3200 Hz. Brzmi cyfrowo i metalicznie, ale nadal jak ten człowiek.
Digital P25Ten sam wokoder, ale łagodniej (część oryginalnego głosu, więcej „oddechu”), pasmo 300–3400 Hz.
CleanPrawie bez efektu, tylko szerszy filtr.

Zasięg: jak psuje się sygnał

Analog FMCyfra / wokoder
Dobry sygnałCisza w tle, czysty głos (FM „wycisza” szum)Czysty głos kodeka
ŚredniPojawia się szum eteru, głos lekko „faluje” (zanikanie wielodrogowe)Pierwsze błędy ramek: pojedyncze „bulgotanie”
SłabySilny szum, głębokie falowanie, trzaski „popcorn” przy granicy zasięgu, krótkie wypadnięciaPowtarzane i wyciszane ramki, głos się „kruszy”, bez szumu
Brak (< 3%)Radio nie nadaje i nie odbiera (NO SIGNAL)

Wieże, przekaźniki i animacje

CoJak działa
Fizyczne wieżeWieże z panelu (Towers) pojawiają się w świecie jako model z antenami i drabinką. Liczba anten zależy od „Health”: 100% = 4, 75% = 3, 50% = 2, 25% = 1, 0% = brak. Włączasz to w Config.towers.props.
Niszczenie i naprawaZ Config.towers.destruction = true zestrzelona antena obniża zdrowie wieży o 25% dla wszystkich. Naprawa: stań przy wieży i naciśnij G (spawanie ok. 6 s). Uprawnienie do napraw ustawisz w Config.towers.repairAce.
Przekaźniki w pojazdachW panelu Repeaters dodajesz pojazd (usiądź w nim lub stań obok, np. przyczepa mobilecell). Gracz w takim pojeździe włącza go komendą /radiorepeater, a pojazd działa jak ruchoma wieża z zadanym zasięgiem. Wyłącza się sam po wyjściu z pojazdu.
AnimacjeOtwarcie radia pieszo: krótkofalówka w dłoni i animacja telefonu (jak w oryginale; w pojeździe bez animacji). Nadawanie: z otwartym radiem pozycja „rozmowa”, z zamkniętym animacja z ramieniem przy ustach. Gracz wyłącza ją komendą /radiotalk.
HUDGdy radio jest włączone, a duże okno schowane, w rogu ekranu widać mini radio z grafiką skina: kanał, status (kto nadaje) i zasięg. Wyłączysz w Config.hud.

Modele pochodzą z oryginalnego radia (folder cs_pdradio_props). Nie uruchamiaj ich razem ze starym zasobem z tymi samymi modelami, bo nazwy pojazdów (sonoran, mobilecell, serverrack) by się dublowały.

Dźwięki tła w mikrofonie (syrena, helikopter, strzały)

Gdy trzymasz PTT, do nadawanego głosu dochodzi to, co dzieje się wokół Ciebie. Słyszą to wszyscy na kanale, a radio spłaszcza i przesterowuje to tak jak prawdziwe.

SytuacjaCo słychać pod głosem
Radiowóz z włączoną syrenąSyrena (przemiatanie 0,7–1,5 kHz). Cichsza, gdy stoisz obok pojazdu pieszo, bardzo cicha w helikopterze
Samochód / motor / łódźSilnik i wiatr, głośniejsze wraz z prędkością
HelikopterUderzenia wirnika (ok. 21 Hz), wycie turbiny, wiatr. Samolot: ciągły szum silnika
Strzały podczas nadawaniaTrzask i huk wchodzą do mikrofonu, kompresor radia na moment przycisza głos

Skrypt Lua sam wykrywa typ pojazdu, syrenę (tylko z włączonym dźwiękiem), prędkość i strzały i wysyła to do radia. Efekt sprawdzisz na stronie /test (wybór pojazdu, syrena, prędkość, przyciski „Strzał” i „Seria”, a „Słyszę siebie” pokaże to, co słyszą inni).

Ekran radia: styl Windows 98

Ekran radia wygląda jak okno Windows 98 i ma osobny układ zależnie od tego, gdzie jesteś:

GdzieUkład
Pieszo (ręczna krótkofalówka)Wąskie okno: grupa, lista kanałów, głośność, zasięg, przyciski Prev / Next / Panic
W pojeździe (radio mobilne)Szersze okno: lista kanałów po lewej, panel statusu po prawej (grupa, kanał, zasięg, głośność)
Helikopter / samolotNajwiększe okno: kanały, radio oraz panel nawigacji (kurs, wysokość, prędkość)

Grafiki obudów (krótkofalówka, radio mobilne, panel lotniczy) pochodzą z oryginalnych skinów w skins/. Przyciski na ekranie klikasz po włączeniu kursora (klawisz „Radio: mouse cursor” albo przycisk □ w oknie). Komenda /radiodebug wypisuje stan radia w konsoli F8.

Zagłuszanie i trzaski

ŹródłoSkąd się bierze (Lua)Jak brzmi
Wieże, strefy martwe, wodaSkrypt liczy jakość sygnału z odległości od wież (panel → Towers), stref i pływaniaSzum, falowanie i trzaski (analog) albo błędy ramek (cyfra), aż do braku zasięgu
JammerPanel → Jammers. Poziom zagłuszania rośnie od ok. 1,8× zasięgu do środkaSzum na kanale, gwizd z przemiataniem i buczenie 100 Hz, głos coraz bardziej zagłuszony. W zasięgu jammera radio jest martwe, ale dalej słychać zakłócenia
PogodaBurza i deszcz w grze (Config.interference.weather)Szum tła i losowe trzaski podczas odbioru
Inne skryptyexports['cs_pdradio']:setExtraInterference('emp', 0.8) (0 usuwa)Trzaski na wszystkich radiach (EMP, rozbłysk słoneczny)

Odpowiedź na pytanie „czy to zależy od wież?”: tak, jakość sygnału (zasięg) liczy skrypt Lua po stronie gracza z danych o wieżach, strefach i wodzie, a serwer webowy tylko ją odtwarza jako dźwięk. Zagłuszanie jammerem i trzaski pogodowe to osobne źródła zakłóceń, niezależne od wież. Poziomy sprawdzisz w grze komendą /radiosignal.

Przytłumienie (muffling)

Każdy preset ma parametr muffle (0–100%), który ścina wysokie tony i zmiękcza środek pasma. Vocoder 3200 ma go domyślnie ustawionego mocniej (ciemniejsze, „przytłumione” brzmienie). Zmienisz go w /radioadmin → Effects (suwak „Muffling”) lub na stronie /test.

Liczy się gorszy sygnał z dwóch: nadawcy i odbiorcy. Szum eteru działa tylko wtedy, gdy ktoś nadaje (jak przy zamkniętym squelchu).

Strojenie: w panelu /radioadmin → Effects ustawiasz preset, siłę wokodera i szum. Własne wartości (pasmo, zniekształcenie, squelch itd.) są w public/radio-client.js w obiekcie PRESETS, a sam kodek w public/vocoder.js. Zmiany w tych plikach sprawdzisz od razu na stronie /test.

Rozwiązywanie problemów

ObjawPrzyczyna i rozwiązanie
Na ekranie VOICE OFFLINE, w konsoli „web server grant failed (HTTP 401)”Sekret w sv_config.lua (lub convar) różni się od RADIO_SECRET w .env. Ustaw identyczne i zrestartuj cs_pdradio oraz cs-radio.
HTTP 0 / timeout przy grantZły webApi albo serwer webowy nie działa. Z maszyny FiveM wykonaj curl <webApi>/api/health.
Radio działa, ale nic nie słychaćW konsoli F8 / DevTools sprawdź, czy ramka głosu się ładuje. Częsta przyczyna: webUrl to http:// zamiast https:// (mieszana treść).
„Radio: microphone access denied”Ramka musi być z HTTPS (lub localhost). Sprawdź też, czy w systemie jest ustawiony domyślny mikrofon.
Caddy: „too many certificates” / limit Let's Encrypt dla sslip.ioUsługa sslip.io jest współdzielona i czasem trafia na limity wystawiania certyfikatów. Rozwiązanie: darmowa nazwa z duckdns.org (np. twojradio.duckdns.org → 49.13.53.85) albo własna domena. Podmień nazwę w /etc/caddy/Caddyfile i w config.lua (webUrl).
Certyfikat się nie wystawia (Caddy)Rekord DNS nie wskazuje jeszcze na VPS albo porty 80/443 są zablokowane (zapora lub panel dostawcy VPS).
Rozłącza co kilka sekundProxy ucina WebSocket. W nginx dodaj nagłówki Upgrade i Connection oraz proxy_read_timeout 3600s;. Caddy robi to sam.
Słyszę siebie z opóźnieniemTo loopback tylko na stronie testowej. W grze nadawca siebie nie słyszy.
Nie mogę wejść na kanałBrak uprawnień do grupy (job / ACE) albo kanał nie istnieje. Sprawdź zakładkę Channels. Admin ma dostęp do wszystkich.
npm install nie działaZa stary Node (potrzebny 18+). Sprawdź node -v.

Bezpieczeństwo