Compare commits
13
Commits
3deed92dd9
..
latest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce6bba0510 | ||
|
|
9ea399ded5 | ||
|
|
9f7723d104 | ||
|
|
5b84843217 | ||
|
|
bd8decb4b3 | ||
|
|
52f43d4d61 | ||
|
|
7eaba5e3f3 | ||
|
|
124db82a0c | ||
|
|
b4d001eb26 | ||
|
|
60c51a1635 | ||
|
|
d208b0c277 | ||
|
|
2f836cd366 | ||
|
|
f7f2cf75ab |
@@ -0,0 +1,97 @@
|
||||
# Caminho rápido: só Windows x64, disparado à mão.
|
||||
#
|
||||
# O `release.yml` continua sendo o release de verdade (todas as plataformas,
|
||||
# publica release e alimenta o electron-updater). Este aqui existe para quando
|
||||
# você só quer um .exe para testar, sem marcar versão.
|
||||
#
|
||||
# Arquivo separado de propósito: o release.yml veio do upstream e recebe merges;
|
||||
# mexer nele criaria conflito a cada atualização.
|
||||
name: Build Windows (rápido)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
arch:
|
||||
description: Arquitetura
|
||||
required: false
|
||||
default: x64
|
||||
type: choice
|
||||
options: [x64, arm64]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# windows-2022, não windows-latest: a imagem latest traz o Visual Studio 18,
|
||||
# que o node-gyp embutido no electron-rebuild não detecta ao compilar os
|
||||
# módulos nativos. Mesma razão documentada no release.yml.
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.34.3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
# ~100MB de binários do Electron e das ferramentas do NSIS, baixados a
|
||||
# cada execução sem isto. É o maior ganho depois de cortar o arm64.
|
||||
- name: Cache Electron binaries
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\electron\Cache
|
||||
~\AppData\Local\electron-builder\Cache
|
||||
key: electron-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: electron-cache-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared package
|
||||
run: pnpm --filter @backspace/shared build
|
||||
|
||||
# O postinstall termina em `|| console.warn` para que quem não tem
|
||||
# ferramentas de build consiga instalar. No CI isso transforma falha em
|
||||
# sucesso silencioso: o instalador sai sem o módulo nativo e o app
|
||||
# compartilha tela sem som, sem erro nenhum. Então verifica-se o
|
||||
# resultado em vez de confiar no código de saída.
|
||||
- name: Verify native audio module compiled
|
||||
shell: bash
|
||||
run: |
|
||||
found=$(find node_modules/.pnpm -path '*electron-native-screenshare*' -name '*.node' 2>/dev/null | head -5)
|
||||
if [ -z "$found" ]; then
|
||||
echo "::error::electron-native-screenshare has no compiled .node — the installer would ship without system-audio capture"
|
||||
find node_modules/.pnpm -maxdepth 1 -name 'electron-native-screenshare*' -printf '%p\n' 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
echo "OK:"; echo "$found"
|
||||
|
||||
- name: Compile desktop TypeScript
|
||||
working-directory: packages/desktop
|
||||
run: pnpm exec tsc
|
||||
|
||||
# --publish never: sem release, sem precisar de tag nem bump de versão.
|
||||
# O instalador sai como artefato desta execução.
|
||||
- name: Build installer
|
||||
working-directory: packages/desktop
|
||||
run: pnpm exec electron-builder --win --${{ inputs.arch || 'x64' }} --publish never
|
||||
env:
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
|
||||
- name: Upload installer
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: backspace-windows-${{ inputs.arch || 'x64' }}
|
||||
path: packages/desktop/dist-electron/*.exe
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,183 @@
|
||||
# Compila no GitHub, publica no Gitea.
|
||||
#
|
||||
# O GitHub entra só como máquina de build — é dele que vêm os runners Windows
|
||||
# de que o módulo nativo de áudio precisa. A distribuição fica no Gitea, que
|
||||
# serve os arquivos a qualquer um: assim o electron-updater não precisa de
|
||||
# credencial embutida no app, o que aconteceria com um repositório privado no
|
||||
# GitHub.
|
||||
name: Publicar no Gitea
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Tag a publicar (ex. v1.1.0)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-2022
|
||||
args: --win --x64
|
||||
- os: ubuntu-latest
|
||||
args: --linux --x64
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Install Linux build dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libx11-dev libxtst-dev libxt-dev \
|
||||
libxkbcommon-dev libxkbcommon-x11-dev libxkbfile-dev \
|
||||
libxrandr-dev libxinerama-dev libx11-xcb-dev \
|
||||
libpipewire-0.3-dev libpulse-dev
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.34.3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
- name: Cache Electron binaries
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/electron
|
||||
~/.cache/electron-builder
|
||||
~\AppData\Local\electron\Cache
|
||||
~\AppData\Local\electron-builder\Cache
|
||||
key: electron-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: electron-cache-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build shared package
|
||||
run: pnpm --filter @backspace/shared build
|
||||
|
||||
# O postinstall termina em `|| console.warn` para quem não tem ferramentas
|
||||
# de build. No CI isso esconde falha: o instalador sairia sem captura de
|
||||
# áudio do sistema e ninguém saberia. Verifica-se o resultado.
|
||||
- name: Verify native audio module compiled
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
found=$(find node_modules/.pnpm -path '*electron-native-screenshare*' -name '*.node' | head -5)
|
||||
[ -n "$found" ] || { echo "::error::sem .node compilado — instalador sairia sem áudio do sistema"; exit 1; }
|
||||
echo "$found"
|
||||
|
||||
- name: Compile desktop TypeScript
|
||||
working-directory: packages/desktop
|
||||
run: pnpm exec tsc
|
||||
|
||||
# --publish never: o electron-builder não sabe enviar para o Gitea. Ele
|
||||
# gera os instaladores e o latest.yml (o índice que o app consulta), e o
|
||||
# passo seguinte faz o upload.
|
||||
- name: Build installers
|
||||
working-directory: packages/desktop
|
||||
run: pnpm exec electron-builder ${{ matrix.args }} --publish never
|
||||
env:
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
|
||||
- name: Upload to Gitea release
|
||||
shell: bash
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GITEA_API: https://git.resenha.website/api/v1/repos/devsyncwrld/backspace
|
||||
TAG: latest
|
||||
run: |
|
||||
set -uo pipefail
|
||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||
echo "::error::segredo GITEA_TOKEN não configurado"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sem -f e imprimindo o corpo: a versão anterior usava `curl -sf`, que
|
||||
# engole a resposta de erro, então uma falha aqui só aparecia como um
|
||||
# JSONDecodeError sem dizer o motivo.
|
||||
api() {
|
||||
local method=$1 path=$2; shift 2
|
||||
curl -s -w '\n%{http_code}' -X "$method" \
|
||||
-H "Authorization: token $GITEA_TOKEN" "$GITEA_API$path" "$@"
|
||||
}
|
||||
body() { sed '$d' <<<"$1"; }
|
||||
code() { tail -n1 <<<"$1"; }
|
||||
# jq em vez de python embutido: um heredoc multilinha dentro de um
|
||||
# bloco literal de YAML encerra o bloco na primeira linha sem recuo.
|
||||
json_id() { jq -r '.id // empty'; }
|
||||
|
||||
find_release() {
|
||||
local r; r=$(api GET "/releases/tags/$TAG")
|
||||
[ "$(code "$r")" = "200" ] && body "$r" | json_id || echo ""
|
||||
}
|
||||
|
||||
# O corpo do POST fica em ASCII de proposito: o shell do runner
|
||||
# Windows corrompe UTF-8 na requisicao e o Gitea recusa com
|
||||
# "invalid UTF-8 within /name" (HTTP 422).
|
||||
ID=$(find_release)
|
||||
if [ -z "$ID" ]; then
|
||||
R=$(api POST "/releases" -H 'Content-Type: application/json' \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"Latest build\",\"target_commitish\":\"main\",\"body\":\"Installers published automatically by CI.\"}")
|
||||
if [ "$(code "$R")" = "201" ]; then
|
||||
ID=$(body "$R" | json_id)
|
||||
echo "release criada: $ID"
|
||||
else
|
||||
echo "criação retornou HTTP $(code "$R"): $(body "$R")"
|
||||
# O outro job da matriz pode tê-la criado no mesmo instante.
|
||||
ID=$(find_release)
|
||||
[ -n "$ID" ] || { echo "::error::não foi possível obter nem criar a release"; exit 1; }
|
||||
echo "release encontrada após corrida: $ID"
|
||||
fi
|
||||
else
|
||||
echo "release existente: $ID"
|
||||
fi
|
||||
|
||||
# Remove só os anexos que esta plataforma vai repor, para os dois jobs
|
||||
# não apagarem o trabalho um do outro.
|
||||
R=$(api GET "/releases/$ID/assets")
|
||||
if [ "$(code "$R")" = "200" ]; then
|
||||
body "$R" | jq -r '.[] | "\(.id) \(.name)"' | while read -r aid aname; do
|
||||
case "$aname" in
|
||||
${{ runner.os == 'Windows' && '*.exe|latest.yml' || '*.AppImage|*.deb|latest-linux.yml' }})
|
||||
echo "removendo anexo antigo: $aname"
|
||||
api DELETE "/releases/$ID/assets/$aid" > /dev/null ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
sent=0
|
||||
for f in packages/desktop/dist-electron/*.exe \
|
||||
packages/desktop/dist-electron/*.AppImage \
|
||||
packages/desktop/dist-electron/*.deb \
|
||||
packages/desktop/dist-electron/latest*.yml; do
|
||||
name=$(basename "$f")
|
||||
R=$(api POST "/releases/$ID/assets?name=$name" -F "attachment=@$f")
|
||||
if [ "$(code "$R")" != "201" ]; then
|
||||
echo "::error::falha ao enviar $name — HTTP $(code "$R"): $(body "$R")"
|
||||
exit 1
|
||||
fi
|
||||
echo "enviado: $name"
|
||||
sent=$((sent+1))
|
||||
done
|
||||
[ "$sent" -gt 0 ] || { echo "::error::o build não produziu instaladores"; exit 1; }
|
||||
echo "$sent arquivo(s) publicados"
|
||||
@@ -14,22 +14,26 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# macOS — universal build (arm64 + x64) on Apple Silicon runner
|
||||
- os: macos-latest
|
||||
args: --mac --arm64 --x64
|
||||
# Fork: só as plataformas que o grupo usa. Windows arm64 saiu porque
|
||||
# dobrava o job mais lento (duas distribuições do Electron, módulos
|
||||
# nativos compilados duas vezes) e ninguém roda Windows em ARM.
|
||||
# Linux arm64 saiu pelo mesmo motivo. Com o arm64 fora, o passo que
|
||||
# instalava o fpm nativo perdeu a razão de existir: ele só era
|
||||
# necessário porque o fpm embutido no electron-builder é x86_64 e
|
||||
# falhava no runner arm64.
|
||||
# macOS removido neste fork: ninguém do grupo usa, e o runner macOS
|
||||
# é cobrado a 10x num repositório privado — era a plataforma mais cara
|
||||
# da matriz, compilada em toda tag para zero usuários.
|
||||
# Windows — x64 + arm64 on x64 runner.
|
||||
# Pinned to windows-2022 (VS 2022 / v17): the windows-latest image
|
||||
# ships VS "18", which the node-gyp bundled with electron-rebuild
|
||||
# cannot detect ("unknown version undefined") when compiling
|
||||
# uiohook-napi in postinstall.
|
||||
- os: windows-2022
|
||||
args: --win --x64 --arm64
|
||||
args: --win --x64
|
||||
# Linux — x64 on x64 runner
|
||||
- os: ubuntu-latest
|
||||
args: --linux --x64
|
||||
# Linux — arm64 on arm64 runner
|
||||
- os: ubuntu-24.04-arm
|
||||
args: --linux --arm64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -59,17 +63,6 @@ jobs:
|
||||
libxkbcommon-dev libxkbcommon-x11-dev libxkbfile-dev \
|
||||
libxrandr-dev libxinerama-dev libx11-xcb-dev
|
||||
|
||||
- name: Install fpm for .deb packaging
|
||||
if: runner.os == 'Linux'
|
||||
# electron-builder bundles an x86_64-only fpm; on the arm64 runner it
|
||||
# aborts the .deb target with "cannot execute binary file: Exec format
|
||||
# error". Install fpm natively and set USE_SYSTEM_FPM (below) so both
|
||||
# arches package their .deb with a host-native fpm. ruby-dev + make are
|
||||
# needed for fpm's native gem dependencies.
|
||||
run: |
|
||||
sudo apt-get install -y ruby ruby-dev build-essential
|
||||
sudo gem install --no-document fpm
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
@@ -97,7 +90,3 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
# Use the host-native fpm installed above instead of electron-builder's
|
||||
# bundled x86_64 fpm (which can't run on the arm64 runner). No-op on
|
||||
# macOS/Windows, which don't build .deb.
|
||||
USE_SYSTEM_FPM: "true"
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Gerar o instalador do app desktop
|
||||
|
||||
O workflow **`.github/workflows/release.yml`** (herdado do upstream) já compila
|
||||
para macOS, Windows e Linux e publica os instaladores como release. Ele dispara
|
||||
ao empurrar uma tag `v*`.
|
||||
|
||||
Não foi preciso escrever workflow novo — foi preciso **adaptá-lo ao fork**.
|
||||
|
||||
## Por que GitHub e não Gitea
|
||||
|
||||
O Gitea desta instância não tem Actions habilitado, e mesmo habilitado ele
|
||||
**não oferece máquinas hospedadas**: seria preciso registrar um PC Windows
|
||||
como runner e mantê-lo ligado. O GitHub fornece runners Windows prontos, que é
|
||||
exatamente o que falta — o módulo nativo do compartilhamento de áudio usa
|
||||
WASAPI e só compila no Windows, com o compilador da Microsoft.
|
||||
|
||||
O Gitea continua sendo o repositório principal. O GitHub entra apenas como
|
||||
espelho para compilar.
|
||||
|
||||
## Passos (uma vez)
|
||||
|
||||
1. Criar um repositório no GitHub — o `electron-builder.yml` está apontado para
|
||||
`syncwrld/resenhacord`. **Se o seu for outro nome, ajuste lá.**
|
||||
2. Adicionar o espelho e empurrar:
|
||||
```
|
||||
cd /opt/backspace
|
||||
git remote add github git@github.com:syncwrld/resenhacord.git
|
||||
git push github main
|
||||
```
|
||||
3. Marcar uma versão e empurrar a tag — é ela que dispara a compilação:
|
||||
```
|
||||
git tag v1.0.1 && git push github v1.0.1
|
||||
```
|
||||
4. Os instaladores aparecem na aba Releases do GitHub em ~15 minutos.
|
||||
|
||||
## O que isso resolve de quebra
|
||||
|
||||
O `electron-updater` já estava instalado no projeto mas **sem feed** — o app
|
||||
não se atualizava sozinho. Como o `publish` agora aponta para as releases do
|
||||
fork, o app passa a encontrar versões novas por conta própria. Ninguém do grupo
|
||||
precisa reinstalar na mão de novo.
|
||||
|
||||
## Adaptações feitas para o fork
|
||||
|
||||
| O quê | Por quê |
|
||||
|---|---|
|
||||
| `publish.owner` → `syncwrld` | Apontava para o repositório do upstream |
|
||||
| `postinstall` reconstrói também `electron-native-screenshare` | Rodava `electron-rebuild` só no `uiohook-napi`. O módulo novo seria compilado para a ABI do Node em vez da do Electron e falharia ao carregar — e como o carregamento degrada em silêncio, o sintoma seria "compartilha sem som", sem erro visível |
|
||||
| Nota sobre `asarUnpack` | O `uiohook-napi` tem `build/` excluído do asar porque distribui binários prontos. O módulo novo **não** distribui: `build/Release/*.node` é a única cópia e não pode ser excluída |
|
||||
|
||||
## Detalhe herdado que vale preservar
|
||||
|
||||
O runner do Windows está fixado em `windows-2022`, não `windows-latest`. O
|
||||
comentário no workflow explica: a imagem `latest` traz o Visual Studio 18, que
|
||||
o node-gyp embutido no `electron-rebuild` não detecta ("unknown version
|
||||
undefined"). Não mude isso sem testar.
|
||||
+4
-3
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "backspace",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"description": "Open, self-hosted communication platform — text, voice, video, and federation",
|
||||
"description": "Open, self-hosted communication platform \u2014 text, voice, video, and federation",
|
||||
"license": "AGPL-3.0-only",
|
||||
"author": "Jannis Braun",
|
||||
"homepage": "https://github.com/TheZwiss/backspace",
|
||||
@@ -32,8 +32,9 @@
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"better-sqlite3",
|
||||
"esbuild",
|
||||
"electron",
|
||||
"electron-native-screenshare",
|
||||
"esbuild",
|
||||
"sharp"
|
||||
],
|
||||
"patchedDependencies": {
|
||||
|
||||
@@ -14,14 +14,27 @@ files:
|
||||
- "!**/node_modules/uiohook-napi/build/**"
|
||||
- "!**/node_modules/uiohook-napi/build.bak/**"
|
||||
- "!**/node_modules/uiohook-napi/bin/**"
|
||||
# electron-native-screenshare has no prebuilds — build/Release/*.node is the
|
||||
# only copy, so it must NOT be excluded the way uiohook-napi's is. asarUnpack
|
||||
# below takes it out of the archive so the loader can find it.
|
||||
asarUnpack:
|
||||
- "**/*.node"
|
||||
npmRebuild: false
|
||||
afterPack: ./scripts/afterPack.js
|
||||
publish:
|
||||
- provider: github
|
||||
owner: TheZwiss
|
||||
repo: backspace
|
||||
# Updates are served from this fork's own Gitea, not from GitHub.
|
||||
#
|
||||
# GitHub is only the build machine — it has the Windows runners the native
|
||||
# audio module needs. Its repository is private, and electron-updater against
|
||||
# a private GitHub repo would need a token shipped inside the app, which is a
|
||||
# leaked token. Gitea serves release assets to anyone, so no credential ends
|
||||
# up in the installer.
|
||||
#
|
||||
# The tag is fixed at `latest` on purpose: electron-updater fetches
|
||||
# latest.yml before it knows which version exists, so the URL cannot contain
|
||||
# a version. CI replaces that release's assets on every publish.
|
||||
- provider: generic
|
||||
url: https://git.resenha.website/devsyncwrld/backspace/releases/download/latest/
|
||||
protocols:
|
||||
- name: Backspace
|
||||
schemes:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backspace/desktop",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"description": "Backspace",
|
||||
@@ -18,7 +18,7 @@
|
||||
"clean": "rm -rf dist dist-electron",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"postinstall": "electron-rebuild -f -w uiohook-napi || node -e \"console.warn('[desktop] uiohook-napi native rebuild skipped - needs build tools (make, g++, python3). Only required to RUN the desktop app; the server, web client, and Docker image are unaffected.')\""
|
||||
"postinstall": "electron-rebuild -f -w uiohook-napi,electron-native-screenshare || node -e \"console.warn('[desktop] uiohook-napi native rebuild skipped - needs build tools (make, g++, python3). Only required to RUN the desktop app; the server, web client, and Docker image are unaffected.')\""
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-native-screenshare": "^1.2.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Room, Track, LocalAudioTrack } from 'livekit-client';
|
||||
import { Room, Track, LocalAudioTrack, AudioPresets } from 'livekit-client';
|
||||
import { isElectron } from '../platform/platform';
|
||||
|
||||
/**
|
||||
@@ -16,6 +16,8 @@ import { isElectron } from '../platform/platform';
|
||||
class NativeScreenAudio {
|
||||
private ctx: AudioContext | null = null;
|
||||
private destination: MediaStreamAudioDestinationNode | null = null;
|
||||
private gain: GainNode | null = null;
|
||||
private limiter: DynamicsCompressorNode | null = null;
|
||||
private track: LocalAudioTrack | null = null;
|
||||
private unsubscribe: (() => void) | null = null;
|
||||
private nextStartTime = 0;
|
||||
@@ -28,6 +30,14 @@ class NativeScreenAudio {
|
||||
/** Beyond this the stream has stalled; resync rather than drift forever. */
|
||||
private static readonly MAX_DRIFT_S = 0.5;
|
||||
|
||||
/**
|
||||
* WASAPI hands back the process's own mix, which sits far below the level a
|
||||
* microphone track arrives at — listeners had to run the participant at 200%
|
||||
* to hear anything. Boosted here rather than asking every listener to
|
||||
* compensate, with a limiter after it so loud passages do not clip.
|
||||
*/
|
||||
private static readonly BOOST = 3.0;
|
||||
|
||||
isActive(): boolean {
|
||||
return this.track !== null;
|
||||
}
|
||||
@@ -38,6 +48,21 @@ class NativeScreenAudio {
|
||||
|
||||
this.ctx = new AudioContext();
|
||||
this.destination = this.ctx.createMediaStreamDestination();
|
||||
|
||||
// source → gain → limiter → destination. The limiter is a compressor with
|
||||
// a high ratio and fast attack: it only engages on peaks the boost would
|
||||
// otherwise clip, and is transparent below that.
|
||||
this.gain = this.ctx.createGain();
|
||||
this.gain.gain.value = NativeScreenAudio.BOOST;
|
||||
this.limiter = this.ctx.createDynamicsCompressor();
|
||||
this.limiter.threshold.value = -6;
|
||||
this.limiter.knee.value = 0;
|
||||
this.limiter.ratio.value = 20;
|
||||
this.limiter.attack.value = 0.003;
|
||||
this.limiter.release.value = 0.1;
|
||||
this.gain.connect(this.limiter);
|
||||
this.limiter.connect(this.destination);
|
||||
|
||||
this.nextStartTime = 0;
|
||||
|
||||
this.unsubscribe = window.backspace.onNativeAudioData((data, meta) => {
|
||||
@@ -54,6 +79,13 @@ class NativeScreenAudio {
|
||||
try {
|
||||
await room.localParticipant.publishTrack(this.track, {
|
||||
source: Track.Source.ScreenShareAudio,
|
||||
// Defaults are tuned for speech: mono, low bitrate, and DTX on, which
|
||||
// stops transmitting during silence. On game and music audio that
|
||||
// reads as clipped and thin.
|
||||
audioPreset: AudioPresets.musicHighQualityStereo,
|
||||
forceStereo: true,
|
||||
dtx: false,
|
||||
red: false,
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
@@ -91,7 +123,7 @@ class NativeScreenAudio {
|
||||
|
||||
const source = ctx.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
source.connect(destination);
|
||||
source.connect(this.gain ?? destination);
|
||||
|
||||
const now = ctx.currentTime;
|
||||
if (this.nextStartTime < now || this.nextStartTime > now + NativeScreenAudio.MAX_DRIFT_S) {
|
||||
@@ -112,6 +144,8 @@ class NativeScreenAudio {
|
||||
} catch { /* already gone */ }
|
||||
this.track = null;
|
||||
}
|
||||
this.gain = null;
|
||||
this.limiter = null;
|
||||
this.destination = null;
|
||||
if (this.ctx) {
|
||||
try {
|
||||
|
||||
Generated
+21
-2
@@ -25,6 +25,9 @@ importers:
|
||||
|
||||
packages/desktop:
|
||||
dependencies:
|
||||
electron-native-screenshare:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
electron-updater:
|
||||
specifier: ^6.3.0
|
||||
version: 6.8.3
|
||||
@@ -876,11 +879,11 @@ packages:
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
|
||||
deprecated: 'Merged into tsx: https://tsx.is'
|
||||
deprecated: 'Merged into tsx: https://tsx.hirok.io'
|
||||
|
||||
'@esbuild-kit/esm-loader@2.6.5':
|
||||
resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
|
||||
deprecated: 'Merged into tsx: https://tsx.is'
|
||||
deprecated: 'Merged into tsx: https://tsx.hirok.io'
|
||||
|
||||
'@esbuild/aix-ppc64@0.19.12':
|
||||
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
|
||||
@@ -2119,6 +2122,7 @@ packages:
|
||||
|
||||
'@ungap/structured-clone@1.3.0':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
|
||||
|
||||
'@vitejs/plugin-react@4.7.0':
|
||||
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
||||
@@ -2158,6 +2162,7 @@ packages:
|
||||
'@xmldom/xmldom@0.8.11':
|
||||
resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
deprecated: this version has critical issues, please update to the latest version
|
||||
|
||||
abbrev@1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
@@ -2953,6 +2958,11 @@ packages:
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
electron-native-screenshare@1.2.0:
|
||||
resolution: {integrity: sha512-O7m+/AeE0UWFq8Z18THGSrPHRIxYiYtjdMKtVhGcNRE/sUN6Fj9EgXDOmmJnORCh303NWaNgmAnNz+JbeLa48Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
os: [win32, linux, darwin]
|
||||
|
||||
electron-publish@25.1.7:
|
||||
resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==}
|
||||
|
||||
@@ -4184,6 +4194,9 @@ packages:
|
||||
node-addon-api@1.7.2:
|
||||
resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-api-version@0.2.1:
|
||||
resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==}
|
||||
|
||||
@@ -8257,6 +8270,10 @@ snapshots:
|
||||
- electron-builder-squirrel-windows
|
||||
- supports-color
|
||||
|
||||
electron-native-screenshare@1.2.0:
|
||||
dependencies:
|
||||
node-addon-api: 7.1.1
|
||||
|
||||
electron-publish@25.1.7:
|
||||
dependencies:
|
||||
'@types/fs-extra': 9.0.13
|
||||
@@ -9929,6 +9946,8 @@ snapshots:
|
||||
node-addon-api@1.7.2:
|
||||
optional: true
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
node-api-version@0.2.1:
|
||||
dependencies:
|
||||
semver: 7.7.4
|
||||
|
||||
Reference in New Issue
Block a user