Paw Ormstrup Madsen. English

Projekter · Hearth

Sådan bygger jeg Hearth

Hearth er mit eget chat-, stemme- og streamingprogram til familie og venner: et lille Discord, der kører på min server derhjemme. Det er ikke færdigt, og det bliver det nok aldrig. Her er historien indtil nu: hvordan delene hænger sammen, hvorfor apps blev native, hvordan spil streames med lav forsinkelse, og hvordan AI-agenter skriver koden, mens jeg styrer.

  • 4apps: web, Linux, Windows og Android
  • 21 msforsinkelse på en stream i 1440p60 på lokalnettet
  • 543commits indtil videre
  • 320versioner af serveren rullet ud
  • 3.800+automatiske tests
  • 58åbne opgaver, for det bliver aldrig færdigt

Tallene er fra oktober 2026.

Hvorfor bygge sit eget?

Hearth er til en lille kreds: familien, vennerne, en spilgruppe. Der er tekstkanaler, stemmekanaler og spilstreaming, ligesom i Discord, men det kører på min egen server. Ingen sporing, ingen reklamer og ingen cloud-konti. Det er ikke et produkt, og ingen sælger det.

Før den første linje kode skrev jeg principperne ned, og de afgør stadig de fleste valg:

  • Det kører derhjemme på en almindelig hjemmeserver med Docker.
  • Privat som standard: ingen skrifttyper, scripts eller indlejringer hentet fra andre.
  • Lavet til spil: lav forsinkelse på stemme og streams, og taster, der virker, mens spillet har fokus.
  • En rigtig app til hvert styresystem, og webappen til resten.
  • Enkelt at drive: én proces og én databasefil. Alle integrationer kan slås fra.

I dag kan Hearth blandt andet: svar, reaktioner, redigering, @-omtaler, fastgjorte beskeder, afstemninger, påmindelser og søgning i al tekst. Billeder mister deres metadata på serveren. Stemmekanaler har støjdæmpning, push-to-talk og lydstyrke pr. person, og man kan se op til fire streams ad gangen. Man kommer kun ind med en invitation, uden e-mail og telefonnummer, og push-notifikationer sendes med serverens egen nøgle, uden Firebase. Ember er en chatbot, der svarer, når nogen nævner den, med en sprogmodel på min egen hardware og aldrig i skyen.

Sådan voksede det

Det første, der blev bygget, var det sværeste: Hearth kunne streame et spil i 1440p60, sende tekstbeskeder og føre stemmeopkald, før der var meget andet. Det voksede i den her rækkefølge:

  1. Principperne og arkitekturen. Spilstreaming i 1440p60 med én kodning på grafikkortet, tekstchat, stemmekanaler over WebRTC og «Go live» i appen.
  2. Push-notifikationer med serverens egen nøgle, egen byggemaskine og automatisk udrulning til hjemmeserveren.
  3. Native apps, som blev standard på Linux, Windows og Android.
  4. Planen for Windows-appens installation og opdateringer direkte fra hjemmeserveren.
  5. Byggekanalen, hvor brugerne kan ønske sig ændringer.
  6. Nu: 58 åbne opgaver. Mere om dem længere nede.

Sådan hænger delene sammen

I midten står hubben: én ASP.NET Core-server på .NET 10, der gemmer alt i én SQLite-fil og taler med apps over SignalR. Den serverer også webappen, som er Blazor WebAssembly, og filerne til at installere apps. Stemme og streams går derimod ikke gennem serveren.

Hubben på hjemmeserverenASP.NET Core · SignalR · SQLite
  • WebBlazor WebAssembly
  • LinuxGTK 4 i C#
  • WindowsWinUI 3
  • AndroidKotlin og Compose
Serveren holder styr på samtalerne, mens lyd og billede går direkte mellem dem, der taler og ser med.
  • Stemme: alle sender til alle over WebRTC med Opus i 96 kbit/s, op til 12 i en kanal. Desktop-apps har deres egen stemmemotor i GStreamer med ekkodæmpning, støjdæmpning og en jitterbuffer, der udjævner nettets ujævnheder.
  • Streams: spillet kodes én gang på streamerens PC og sendes til hver seer over sin egen WebRTC-forbindelse. Der er ingen medieserver i midten, og forbindelser ud af huset kan gå via en TURN-server, når de ikke kan nå hinanden direkte.
  • Fælles kode: logikken bag desktop-apps og webappen ligger i ét fælles C#-bibliotek, og Android-appen har den samme logik i Kotlin.

Fra webvisning til native apps

De første apps var webappen pakket ind i et vindue. Det gik fint på computeren, men ikke på telefonen: Chrome lukker mikrofonen fem sekunder efter, at skærmen slukker, og så stopper opkaldet midt i en samtale.

Løsningen blev målt, før den blev valgt. En tynd Kotlin-app med en baggrundstjeneste til opkaldet holdt stemmen kørende med slukket skærm, i baggrunden og i strømsparetilstand, i syv minutter på Android 16 og 17. Den viste første billede efter 201 millisekunder mod 383 for webappen i Chrome og fyldte 0,42 MB at hente mod 18,2 MB for en tom .NET MAUI-app.

Så besluttede jeg at gå hele vejen: GTK 4 på Linux og WinUI 3 på Windows, oven på det fælles C#-bibliotek, og Kotlin med Jetpack Compose på Android. Skærmene blev skiftet ud én ad gangen bag en indstilling, indtil de native apps blev standard alle tre steder. Webappen er der stadig til alle andre.

  • Hearths Linux-app med kanaler til venstre, en samtale om aftenens spil i midten og to brugere online til højre
    Linux-appen i GTK 4 med demodata fra testene.Skærmbillede
  • En afstemning om pizza, tacos eller sushi på fredag, hvor tacos har vundet
    En afstemning i webappen på en telefon.Skærmbillede

Spilstreaming med lav forsinkelse

Desktop-appen optager spillet og koder det på grafikkortet én gang pr. kvalitet: 1440p60 i 30 Mbit/s til seere derhjemme og 720p30 i 4 Mbit/s til seere over internettet. Den virker med grafikkort fra både NVIDIA, AMD og Intel og kan falde tilbage til processoren. På Linux optages skærmen gennem PipeWire, og på Windows bliver billederne på grafikkortet hele vejen. Spillets egen lyd kommer med.

Forsinkelsen bliver målt, ikke gættet: hvert billede får en stregkode, som seeren læser igen. Med NVENC over lokalnettet i 1440p60 er forsinkelsen 21 millisekunder i halvdelen af tilfældene og under 30 i 95 procent af dem.

Undervejs dukkede der problemer op, som kun måling kunne finde. Et nøglebillede kom som en stor klump, seerens afkoder kom bagud og bad om et nyt nøglebillede, og det tvang et nyt frem for alle seere. Streamen tabte omkring 25 billeder pr. 15 sekunder, indtil en mindre buffer og en sammenlægning af ønskerne tog det ned til nul. Og et AMD-grafikkort på et roligt skrivebord sov mellem billederne og brugte 11,6 millisekunder på at kode et billede mod 4,8, når det var vågent. Under et rigtigt spil er kortet vågent, så det mærkes mest i testene.

  • Hearths desktop-app med en stream af et farverigt testbillede i sit eget vindue foran appen
    En stream i sit eget vindue fra desktop-appen. Testbilledet med de farvede firkanter bruges i de automatiske tests.Skærmbillede
  • Webappen viser en livestream i 1440p60 med fuld kvalitet
    Den samme slags stream i webappen, i 1440p60 og fuld kvalitet.Skærmbillede

AI-agenter skriver koden, jeg styrer

541 af de 543 commits er skrevet sammen med Claude, og de to andre er lavet af en bot. Mit arbejde er at bestemme, hvad der skal bygges, skrive principperne, gennemgå ændringerne og sige ja eller nej.

  • En organisator og flere agenter: én Claude-session sorterer opgaverne, sender agenter i gang og gennemgår deres arbejde. Hver agent arbejder i sin egen kopi af koden og åbner en pull request, men ingen agent merger selv.
  • Højst fire ad gangen: flere agenter end det, og testene for billedhastighed og stemme fejler, fordi maskinen bliver for travl.
  • Ingen konflikter i ændringsloggen: hver pull request skriver sin egen lille fil, så parallelle ændringer ikke støder sammen.
  • Review på alt: hver pull request skal gennem et automatisk review, før den kan merges, og en lokal sprogmodel giver en ekstra vurdering.

Byggekanalen

Brugerne kan skrive et ønske i en byggekanal i Hearth. Botten Agent Smith, der kører på en lokal sprogmodel, skriver en plan, og en byggetjeneste lader Claude Code lave ændringen i en lukket sandkasse og åbne en pull request. Den kan ikke røre ved drift og sikkerhed, og kun jeg kan godkende ændringen. To ændringer er allerede kommet ud på den måde.

Drift derhjemme

Hver merge bygger et nyt image på min egen byggemaskine og sender det til hjemmeserveren. Før skiftet tages et snapshot, bagefter tjekkes det, at serveren svarer, og de fem seneste versioner bliver gemt, så en dårlig version kan rulles tilbage på et øjeblik. 320 versioner er rullet ud sådan indtil nu.

Apps opdaterer sig selv: Windows-appen henter små deltaopdateringer fra hjemmeserveren, Linux-appen kommer fra et signeret pakkearkiv, og Android-appen fra hubben eller fra et lukket testspor i Google Play. Webappen genindlæser efter en ny version, men aldrig midt i et opkald. Logs og målinger går gennem OpenTelemetry til Grafana, og hver enhed kan slå det fra.

Er software nogensinde færdigt?

Nej. Så længe nogen bruger Hearth, kommer der nye ønsker, nye fejl og nye versioner af Android og Windows. Lige nu er der 58 åbne opgaver, blandt andet:

  • Bygget, men slået fra: direkte beskeder, private kanaler, chat i stemmekanaler, kalender og en kanal til huslige opgaver. De slås til, når alle apps kan dem.
  • Byggekanalen skal slås til for alle brugere.
  • Windows: kodesignering, så installationen ikke giver en advarsel, og jævnere billeder.
  • Streaming: mere robust skærmoptagelse, og AV1 er næste codec.
  • Tests, der en gang imellem fejler uden grund.

Derfor er det vigtigste ikke at blive færdig, men at gøre den næste ændring sikker og billig: tests, der fanger fejlene, automatisk udrulning, og at kunne rulle tilbage på et øjeblik.

Vil du selv bygge noget lignende?

Det her har virket for mig:

  1. Skriv principperne ned først. Så er hundrede små valg truffet på forhånd, både for dig og for dine AI-agenter.
  2. Mål, før du vælger. Android-appen blev valgt på tal, ikke på mavefornemmelse.
  3. Hold driften enkel. Én proces, én databasefil og én container er let at tage backup af og flytte.
  4. Lad lyd og billede gå direkte. Til en lille kreds er WebRTC mellem deltagerne nok, og der er ingen medieserver at drive.
  5. Gør udrulning kedelig. Snapshot, sundhedstjek og et klik for at rulle tilbage.
  6. Giv AI-agenter rammer. Egen kopi af koden, pull requests, review, og et menneske, der merger.
  7. Slå halvfærdige funktioner fra. Så kan de ligge i hovedgrenen og blive testet, uden at brugerne ser dem.

Vil du se Hearth?

Hearth er kun for inviterede. Skriv til mig, hvis du vil have en invitation, eller hvis du vil have hjælp til at bygge noget lignende.