Задать вопрос
A_fox
@A_fox
Серверы, Linux, open-source, автоматизация

Почему вызовы в Element X зависают на “Ожидание медиа…” при работе с LiveKit и JWT на своем сервере?

Я пытаюсь развернуть свой собственный стек для видеозвонков на Matrix. Конфигурация следующая:
  • Matrix Synapse (v1.135.0, регистрация закрыта, federation отключён)

  • Нет веб-интерфейса или контейнера Element Call (только signaling)

  • LiveKit (v1.9.0, работает локально)

  • lk-jwt-service (v0.3.0, commit 114f0f4560...)

  • Все сервисы работают на одном сервере (Ubuntu 22.04.5 LTS) с сертификатами Let's Encrypt для TLS

  • В качестве reverse proxy используется nginx (v1.18.0)

  • Docker не используется, все сервисы запускаются нативно

  • Стек полностью изолирован, federation не используется


Моя цель:
Включить 1:1 и групповые аудио/видеозвонки только в клиенте Element X (без веб-интерфейса). Документация по такой конфигурации крайне скудная и фрагментированная. Есть множество гайдов и источников, но многие из них противоречат друг другу или описывают только отдельные части процесса.

Я пытался следовать последним рекомендациям по установке Element X + LiveKit + JWT-only, но так и не смог добиться рабочей связки.

Главная проблема:
Входящие звонки появляются в клиенте Element X (push-уведомление приходит, вызов можно "принять"), но после ответа нет ни аудио, ни видео, а экран звонка зависает на “Ожидание медиа…”. Медиа-соединение не устанавливается, и звонок в итоге обрывается по таймауту.

Это мой конфиг homeserver.yaml для Matrix Synapse:

pid_file: "/var/run/matrix-synapse.pid"
listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    bind_addresses: ['127.0.0.1']
    resources:
      - names: [client, federation]
        compress: false

database:
  name: psycopg2
  txn_limit: 10000
  args:
    user: matrix
    password: ***********    
    database: matrix
    host: localhost
    port: 5432
    cp_min: 5
    cp_max: 20

public_baseurl: https://syn.example.com
log_config: "/etc/matrix-synapse/log.yaml"

media_store_path: /var/lib/matrix-synapse/media

signing_key_path: "/etc/matrix-synapse/homeserver.signing.key"
trusted_key_servers:
  - server_name: "example.com"

suppress_key_server_warning: true
enable_registration: false
macaroon_secret_key: "**************"
registration_shared_secret: "*******************"
search_all_users: true
prefer_local_users: true

experimental_features:
  msc3266_enabled: true
  msc4222_enabled: true

max_event_delay_duration: 24h

rc_message:
  per_second: 0.5
  burst_count: 30

rc_delayed_event_mgmt:
  per_second: 1
  burst_count: 20

allow_public_rooms_over_federation: false
federation_domain_whitelist: ["example.com"]
federation_sender_enabled: false

admin_users:
  - "@admin:example.com"


Вот переменные окружения, используемые для lk-jwt-service:
LIVEKIT_KEY=mykey
LIVEKIT_SECRET=mysecret
LIVEKIT_URL=wss://call.example.com/livekit
LIVEKIT_JWT_PORT=8080
LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com


Вот мой конфиг livekit.yaml:
port: 7880

log_level: info

rtc:
  port_range_start: 50000
  port_range_end: 55000
  tcp_port: 7881
  use_external_ip: true
  interfaces:
    includes:
      - eth0

turn:
  enabled: true
  tls_port: 5349
  relay_range_start: 50000
  relay_range_end: 55000
  external_tls: true
  domain: call.example.com
  cert_file: /etc/letsencrypt/live/call.example.com/fullchain.pem
  key_file: /etc/letsencrypt/live/call.example.com/privkey.pem

keys:
  mykey: mysecret


Ниже приведены мои конфиги nginx.
syn.* — основной домен Matrix Synapse
call.* — используется для сигнальных соединений LiveKit (медиа/звонки по wss и https)
и главный домен — публичный основной домен, только для отдачи well-known файлов Matrix (для автообнаружения)
(Все домены работают на одном сервере с валидными сертификатами Let's Encrypt)

Вот мой nginx-конфиг для Matrix Synapse (syn.*):
server {
    listen 80;
    server_name syn.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name syn.example.com;

    ssl_certificate     /etc/letsencrypt/live/syn.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/syn.example.com/privkey.pem;

    access_log /var/log/nginx/matrix.access.log;
    error_log  /var/log/nginx/matrix.error.log warn;

    location /_matrix {
        proxy_pass http://127.0.0.1:8008;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /_synapse {
        proxy_pass http://127.0.0.1:8008;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location / {
        root /var/www/syn;
        index index.html;
    }
}


Вот мой nginx-конфиг для звонков (call.*):
server {
    listen 443 ssl http2;
    server_name call.*;

    ssl_certificate     /etc/letsencrypt/live/call.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/call.example.com/privkey.pem;

    access_log /var/log/nginx/element-call.access.log;
    error_log  /var/log/nginx/element-call.error.log;

    location /livekit/sfu {
        proxy_pass http://127.0.0.1:7880/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;
    }

    location /livekit/jwt {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name call.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        return 404;
    }
}


Вот мой nginx-конфиг для основного домена для раздачи файлов Matrix .well-known:
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    access_log /var/log/nginx/matrix.access.log;
    error_log  /var/log/nginx/matrix.error.log warn;

    location /.well-known/matrix/client {
        alias /var/www/example/.well-known/matrix/client;
        add_header Content-Type application/json;
        add_header Access-Control-Allow-Origin *;
    }

    root /var/www/example;
}

server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

Вот мой файл .well-known/matrix/client (отдается по адресу https://example.com/.well-known/matrix/client):
{
  "m.homeserver": {
    "base_url": "https://syn.example.com"
  },
  "org.matrix.msc4143.rtc_foci": [
    {
      "type": "livekit",
      "livekit_service_url": "https://call.example.com/livekit/sfu"
    }
  ]
}

Тестирую на Element X версии 25.07.1 (Android).

Я перепробовал всё, что только можно, включая несколько новых попыток настройки с нуля. Мои конфиги максимально близки к официальным мануалам и референсным примерам, но проблема в том, что эти руководства часто противоречат друг другу или не раскрывают важных деталей.

Буду очень благодарен, если кто-то сможет указать, что я упустил или делаю не так. Любые советы и предложения приветствуются!
  • Вопрос задан
  • 26 просмотров
Подписаться 1 Средний Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы