Hvordan en innholdsleverandør publiserer spillelister med YouTube, NRK og lysbilder til kanalene på TV-ene, hvordan TV-en spiller dem av som vanlig lineær TV, og hvilken historikk og profilinformasjon leverandøren kan hente tilbake for å gjøre innholdet bedre. Dokumentet beskriver API-et slik det er bygget.
| Jeg vil … | Kall | Les |
|---|---|---|
| publisere dagens spilleliste til en kanal | PUT /playlists/{id}, så POST /channels/{id}/playlists | kap. 2, 5 og 9 |
| legge en kanal på en bestemt plass på én TV | PATCH /profiles/{ref}/lineup | kap. 2 og 10 |
| vite hvordan YouTube-, NRK- og lysbildeinnslag ser ut | – | kap. 5 (oversikten over de tre typene), 6 og 7 |
| se hva som går og hva som skal gå | GET /profiles/{ref}/now, GET /channels/{id}/schedule | kap. 11 og 16 |
| hente historikken: kanalbytter og programmer | GET /profiles/{ref}/history, GET /events | kap. 13 og 16 |
| hente bio og informasjon om brukeren | GET /profiles/{ref}/bio, /notes, /photos | kap. 14 |
| forstå en feilmelding | – | kap. 3 (alle feilkoder i én tabell) |
Alle adresser i tabellen er relative til https://<server>/api/publish. Denne dokumentasjonen
ligger også på serveren: https://<server>/docs/ og som PDF på
https://<server>/docs/enkeltv-api.pdf.
Enkel-TV-serveren er en frittstående applikasjon som skal endres sjelden, og som ikke vet noe om hva som er godt innhold. Den har fire ansvar:
Alt som handler om å velge innhold – intervjuer med de pårørende, kuratering, personalisering, KI og rettighetsavtaler – skjer hos innholdsleverandørene, utenfor serveren.
Profilen, ikke boksen. Alt i dette dokumentet hører til profilen – TV-brukeren som logger inn med
brukernavn og boks-ID. Kanaloppsett, bio, notater, bilder, samtykke og hendelser følger personen, ikke den fysiske
boksen. Boks-ID-en er bare passordet: går en boks i stykker eller må byttes, lager personalet en ny boks-ID i admin,
og den nye boksen får de samme kanalene, den samme bioen og den samme historikken. Leverandører ser profilen bare som
en pseudonym referanse, ref (f.eks. p_7Kq2mPx9Qa), aldri navn, brukernavn eller boks-ID.
Disse valgene ligger til grunn for det som er bygget, og for resten av dokumentet.
| Område | Beslutning |
|---|---|
| Profilen eier alt | TV-brukeren (profilen) eier kanaloppsett, bio, notater, bilder, samtykke og hendelser – ikke den fysiske boksen. Boks-ID-en er passordet, og personalet kan lage en ny; en ny boks beholder alt. Leverandører ser bare pseudonymet ref (p_…). |
| Én leverandør per profil | Hver profil har høyst én innholdsleverandør, valgt av toppledelsen i admin. Bare den leverandøren kan endre profilens kanaloppsett, lage personlige kanaler, spillelister og lysbildeserier for den, og lese dens hendelser og profildata. Unntaket er Enkel-TVs egne nøkler med events:all: de kan lese hendelser, historikk, kanaloppsett og «nå» for alle profiler, men ikke endre noe eller lese bio, notater og bilder. |
| Felles eller personlig | Kanaler, spillelister og lysbildeserier er enten felles (alle leverandører kan lese og bruke dem) eller personlige for én profil – f.eks. en lysbildeserie med private familiebilder. |
| Kanaler per profil | Hver profil har plass 1–99, standard 10. Pil opp/ned går 0 → 1 → … → siste → 0, og 0 er alltid klokkeskjermen. Plasser uten innhold viser standardoppsettet, så reserveprogrammet, og til sist «Kommer snart». |
| Blandede spillelister | En spilleliste kan veksle mellom YouTube, lysbilder og NRK. Innslagstypen (kind) bestemmer avspilleren. |
| NRK | Leverandøren oppgir bare NRK sin program-ID. TV-en henter avspillingsadressen hos NRK rett før avspilling, fordi adressene er kortlivede. NRK1–3 direkte er en egen kanaltype ("type": "live"), ikke innslag i en spilleliste. |
| Sendedøgnet | Starter kl. 06:00 norsk tid. Dagens spilleliste begynner med første innslag kl. 06:00 og går i løkke til kl. 06:00 neste morgen. |
| Samtaler | En samtale pauser all avspilling – lysbilder, YouTube og NRK. Etterpå fortsetter TV-en der den stoppet. Bytter man kanal, er man tilbake på direkte. |
| Lysbilder | Fritt oppsett med lag – bilde, video, tekst og form – i prosent av skjermen, med tider for tekst og lyd, og rolig zoom. Hele serien kan sendes som én zip-pakke. Høyst 200 MB per serie, ingen lagringskvote per leverandør. |
| API-nøkler | Lages av toppledelsen i Enkel-TV. Hver nøkkel hører til én leverandør og har rettigheter som krysses av. |
| Ingen spørsmålsbank | Leverandøren intervjuer de pårørende utenfor systemet. Personalet skriver bioen i admin. De pårørende sender notater og bilder fra mobilappen når noe har skjedd. |
| Ingen lokal KI | For hvert notat og bilde velger den som sender: leverandøren (også deres KI) eller bare personalet. |
| Alle hendelser lagres | Serveren lagrer hver hendelse og sletter eller summerer ingenting. |
| Ingen webhooks | Serveren varsler ingen. Leverandører spør selv. |
| Ingen mellomlagring | Boksen lagrer ikke innhold og må være på nett. |
| Stabilt API | API-et for leverandører er lite og endres sjelden. Feltene og feilkodene som står her, endres ikke; nye, valgfrie felt kan komme (kap. 18). |
Alt en leverandør gjør, går med vanlige HTTPS-kall til https://<server>/api/publish med API-nøkkelen.
Her er hele gangen med curl: sjekk nøkkelen, finn profilene, lagre en spilleliste med alle tre innslagstypene,
publiser den til en kanal for i dag eller i morgen, legg kanalen på en plass hos én profil, sjekk resultatet, og hent
historikk og bio. Bytt ut <server>, etv_… og p_7Kq2mPx9Qa med deres egne verdier.
Hver dag: steg 4 og 6 – lagre dagens spilleliste og publiser den til kanalen – og gjerne steg 8 for å se at den ligger der. Én gang: steg 1–3, 5 og 7. Kommer det ingen ny spilleliste, går den forrige videre.
Eksemplene er skrevet for bash (Mac, Linux, eller Git Bash/WSL på Windows). Lange JSON-dokumenter er
lettest å lagre i en fil og sende med --data-binary @fil.json i stedet for -d '…'.
ETV=https://<server>/api/publish AUTH="Authorization: Bearer etv_…" JSON="Content-Type: application/json" # 1. Nøkkelen: leverandør, rettigheter, formater og sendedøgn (kap. 16) curl -s -H "$AUTH" $ETV # 2. Profilene (TV-ene) nøkkelen har tilgang til – noter ref-en (p_…) curl -s -H "$AUTH" $ETV/profiles # → [ { "ref": "p_7Kq2mPx9Qa", "online": true, "channelCount": 15, # "consent": { "bio": true, "notes": true, "photos": false }, … } ]
Gir steg 1 401, er nøkkelen feil eller trukket tilbake. Er lista i steg 2 tom, er ingen TV-er koblet
til leverandøren ennå (kap. 3).
Spillelisten under har et lysbildeinnslag, og det peker på en lagret serie. Denne serien sendes som ren JSON, fordi bildene ligger på leverandørens egen https-server. Med egne bilde- og lydfiler sendes serien som zip-pakke (kap. 7). Feltene står i kap. 6.
curl -s -X PUT -H "$AUTH" -H "$JSON" $ETV/slideshows/enkeltv.ingrid-hagen -d '{
"format": "enkeltv-slideshow/1",
"title": "Hagen på Florø",
"visibility": "personal",
"profile": "p_7Kq2mPx9Qa",
"defaults": { "text": { "style": { "size": 6, "background": "#00000099", "padding": 1.2 } } },
"slides": [
{ "id": "hagen", "duration": 15,
"layers": [
{ "type": "image", "src": "https://bilder.example.no/ingrid/hagen-1965.jpg",
"motion": { "from": { "scale": 1 }, "to": { "scale": 1.1 } } },
{ "type": "text", "text": "Hagen på Florø, 1965", "x": 5, "y": 80, "w": 90,
"align": "center", "start": 1, "fadeIn": 1 } ] },
{ "id": "epletre", "duration": 15,
"layers": [
{ "type": "image", "src": "https://bilder.example.no/ingrid/epletreet.jpg" },
{ "type": "text", "text": "Epletreet i blomst", "x": 5, "y": 80, "w": 90, "align": "center" } ] }
]
}'
# → { "id": "enkeltv.ingrid-hagen", "version": 1, "changed": true, "duration": 30, "slides": 2, … }
Én spilleliste per dag og kanal er enklest. Gi den en ID med datoen. Innslagene spilles i rekkefølge fra kl. 06:00 og går i løkke til kl. 06:00 neste morgen (kap. 11). Feltene for de tre typene står samlet i kap. 5.
curl -s -X PUT -H "$AUTH" -H "$JSON" $ETV/playlists/enkeltv.ingrid-2026-10-06 -d '{
"format": "enkeltv-playlist/1",
"title": "Ingrid – tirsdag 6. oktober",
"visibility": "personal",
"profile": "p_7Kq2mPx9Qa",
"items": [
{ "id": "yt.ZlGyCfigmFg", "kind": "youtube", "title": "Allsang: Liten fuggel",
"duration": 208, "youtube": { "videoId": "ZlGyCfigmFg" } },
{ "id": "nrk.FUHA01005272", "kind": "nrk", "title": "Fleksnes", "duration": 2196,
"nrk": { "nrkId": "FUHA01005272", "type": "episode", "seriesTitle": "Fleksnes",
"season": 1, "episode": 1 } },
{ "id": "lysbilder.ingrid-hagen", "kind": "slideshow", "title": "Hagen på Florø",
"slideshow": { "ref": "enkeltv.ingrid-hagen" } }
]
}'
# → { "id": "enkeltv.ingrid-2026-10-06", "version": 1, "changed": true, "items": 3 }
Kanalen er det beboeren ser som «en TV-kanal», med et fast navn. Den lages én gang (steg 5). Etterpå publiseres en ny
spilleliste til kanalen hver dag med POST /channels/{id}/playlists (steg 6), uten å sende kanalen på nytt.
# 5. Bare første gang: PUT erstatter hele kanalen, også spillelistene den har curl -s -X PUT -H "$AUTH" -H "$JSON" $ETV/channels/enkeltv.ingrids-kanal -d '{ "format": "enkeltv-channel/1", "name": "Ingrids kanal", "description": "Tog, Bergen og gamle favoritter", "color": "#e8a87c", "visibility": "personal", "profile": "p_7Kq2mPx9Qa", "programming": { "type": "loop", "playlists": [] } }' # 6. Publiser spillelisten fra sendedøgnet tirsdag 6. oktober (fra kl. 06:00 norsk tid). # Uten "from" gjelder den fra dagens sendedøgn – altså med en gang. curl -s -X POST -H "$AUTH" -H "$JSON" $ETV/channels/enkeltv.ingrids-kanal/playlists \ -d '{ "playlistId": "enkeltv.ingrid-2026-10-06", "from": "2026-10-06" }' # → { "id": "enkeltv.ingrids-kanal", "version": 2, "changed": true }
Spillelisten gjelder til en nyere publiseres. Publiseres det to ganger for samme dag, gjelder den siste.
Oppføringer som aldri kan gjelde igjen, ryddes bort fra kanalen hver gang noe publiseres (kap. 9). Selve spillelistene
blir liggende til de slettes med DELETE /playlists/{id}.
# Plass 6 hos Ingrid. PATCH endrer bare plassene som er nevnt; "6": null fjerner plassen igjen. curl -s -X PATCH -H "$AUTH" -H "$JSON" $ETV/profiles/p_7Kq2mPx9Qa/lineup \ -d '{ "slots": { "6": "enkeltv.ingrids-kanal" } }' # → { "format": "enkeltv-lineup/1", "profile": "p_7Kq2mPx9Qa", "channelCount": 15, # "slots": { "6": "enkeltv.ingrids-kanal", … }, "resolved": [ … ], … }
TV-en får endringen innen et par sekunder (kap. 11). Plassen viser kanalen fra kl. 06:00 den dagen kanalen har en spilleliste; før det viser plassen standardoppsettet (kap. 10).
curl -s -H "$AUTH" "$ETV/channels/enkeltv.ingrids-kanal/schedule?date=2026-10-06" # → { "date": "2026-10-06", "playlistId": "enkeltv.ingrid-2026-10-06", # "entries": [ { "itemId": "yt.ZlGyCfigmFg", "startsAt": "2026-10-06T04:00:00.000Z", … }, … ] } curl -s -H "$AUTH" $ETV/profiles/p_7Kq2mPx9Qa/now # → { "showing": { "slot": 6, … }, "slots": [ { "slot": 6, "kind": "nrk", "title": "Fleksnes", … }, … ] }
# Hva ble sett i sendedøgnet 6. oktober (06:00–06:00 norsk tid = 04:00–04:00 UTC om sommeren) curl -s -H "$AUTH" "$ETV/profiles/p_7Kq2mPx9Qa/history?from=2026-10-06T04:00:00Z&to=2026-10-07T04:00:00Z" # Bioen og nye notater fra familien – krever samtykke fra eieren (403 no-consent ellers) curl -s -H "$AUTH" $ETV/profiles/p_7Kq2mPx9Qa/bio curl -s -H "$AUTH" "$ETV/profiles/p_7Kq2mPx9Qa/notes?after=0"
Historikken er forklart i kap. 13, bio og notater i kap. 14. Alle feilkodene står i kap. 3.
Leverandøren får en API-nøkkel av Enkel-TV. Hver nøkkel tilhører en leverandør og har et sett
rettigheter som krysses av når nøkkelen lages. En leverandør kan ha flere nøkler, f.eks. én per ansatt eller
per system; alt innhold tilhører leverandøren, ikke nøkkelen. Serveren kjenner igjen nøkkelen på en SHA-256-sjekksum, og
nøkkelen vises som regel bare én gang, når den lages – ta vare på den. Er den borte eller kommet på avveie, lager Enkel-TV en ny
og trekker tilbake den gamle, som da slutter å virke med en gang (401). Innholdet leverandøren har laget,
blir liggende.
Authorization: Bearer etv_VUFK… # nøkkelen begynner alltid med etv_ Content-Type: application/json # eller application/zip for lysbildepakker
Hvilken leverandør en profil (TV) har, velger Enkel-TV. GET /api/publish/profiles viser profilene nøkkelen
har tilgang til. En tom liste betyr at ingen profiler er koblet til leverandøren ennå – be Enkel-TV velge leverandøren
på TV-ene det gjelder.
«Egne profiler» er aktive profiler der nøkkelens leverandør er valgt som leverandør. En profil som ikke er leverandørens,
finnes ikke for nøkkelen (404 no-profile), bortsett fra lesing med events:all.
| Rettighet | Gir tilgang til |
|---|---|
| alle gyldige nøkler | GET /api/publish, lista over egne profiler (GET /profiles), standardoppsettet (GET /lineups/default), lesing av felles og egne kanaler, spillelister og lysbildeserier, og sendeplanen for en synlig kanal (GET /channels/{id}/schedule) |
library:write | Lagre og slette egne kanaler, spillelister og lysbildeserier (JSON og zip), publisere spillelister til egne kanaler (POST /channels/{id}/playlists) og POST /media/check |
lineups:write | Lese og endre kanaloppsettet til egne profiler (GET · PUT · PATCH /profiles/{ref}/lineup) og se hva som går nå (GET /profiles/{ref}/now) |
events:read | Hendelser (GET /events) og historikk (GET /profiles/{ref}/history) for egne profiler. Fyller også feltet showing i GET /profiles/{ref}/now når TV-en er på (ellers er det null) |
profiles:read | Bio og notater for egne profiler, når eieren har samtykket (403 no-consent ellers) |
photos:read | Familiebilder for egne profiler, når eieren har samtykket til bilder |
lineups:default | Endre standardoppsettet og reserveprogrammet som alle profiler arver (PUT /lineups/default). Bare for Enkel-TVs egen innholdstjeneste |
events:all | Hendelser og historikk for alle profiler. Lista GET /profiles viser da alle profiler, og sammen med lineups:write kan nøkkelen lese – men ikke endre – kanaloppsettet og «nå» for andre leverandørers profiler. Bare til Enkel-TVs egen analyse, ikke til eksterne leverandører |
Bio, notater og bilder kan bare leses for egne profiler – også med events:all. En vanlig nøkkel
for en ekstern leverandør har library:write, lineups:write og events:read, og
profiles:read og photos:read når leverandøren skal bruke bio, notater og bilder.
Alle svar er JSON med status 200, også når noe lagres. Feil har en HTTP-status og en kropp med error – en
melding på norsk – og som regel en fast code. Meldingen er til mennesker og kan bli bedre formulert; programmer
skal se på status og code. Valideringsfeil begynner med stien til feltet som er feil:
HTTP/1.1 400 Bad Request
{ "error": "items[3].youtube.videoId må være en YouTube-ID på 11 tegn", "code": "invalid-playlist" }
Dette er alle feilene /api/publish kan gi:
| Status | code | Når |
|---|---|---|
| 400 | invalid-channelinvalid-playlistinvalid-slideshowinvalid-lineup | Dokumentet følger ikke formatet (kanal, spilleliste, lysbildeserie eller kanaloppsett). Meldingen sier hvor, f.eks. slides[1].audio[0] slutter etter lysbildet: start + duration = 13.4 s, men lysbildet varer 12 s. Gjelder også en ugyldig ID i adressen, en referanse til noe som ikke finnes, manifest.json som ikke er gyldig JSON, en fil i pakken med ukjent filtype (media/bilde.heic: filtypen er ukjent – bruk JPEG, PNG, WebP, MP3, AAC, Ogg eller MP4) og en kanal som får mer enn 100 spillelister |
| 400 | invalid-zip | Zip-pakken er ødelagt eller bruker noe som ikke støttes, eller manifest.json mangler øverst i pakken (kap. 7) |
| 400 | invalid-json | Kroppen er ikke gyldig JSON, eller ikke et JSON-objekt |
| 400 | invalid-request | Feil i kroppen til POST /media/check, ugyldig date i /schedule (må være YYYY-MM-DD, år 1900 eller senere), eller en adresse med ødelagt prosentkoding (f.eks. %E0) |
| 400 | invalid-query | Ugyldig parameter: since, after, types eller limit i /events, from/to i /history, eller after i /notes |
| 400 | aborted | Opplastingen ble avbrutt før hele kroppen var sendt |
| 401 | – | Nøkkelen mangler, er ukjent eller er trukket tilbake |
| 403 | wrong-role | Kallet har en innlogging fra en av appene i stedet for en API-nøkkel |
| 403 | missing-scope | Nøkkelen mangler rettigheten: Nøkkelen mangler rettigheten «library:write» |
| 403 | not-owner | Objektet tilhører en annen leverandør (slette, eller publisere til en annen leverandørs kanal) |
| 403 | no-consent | Eieren av TV-en har ikke samtykket til bio, notater eller bilder |
| 403 | no-provider | Nøkkelen er ikke knyttet til en leverandør |
| 404 | not-found | Kanalen, spillelisten eller lysbildeserien finnes ikke, eller er en annen leverandørs personlige |
| 404 | no-profile | Profilen finnes ikke, er ikke aktiv eller har ikke nøkkelens leverandør |
| 404 | – | Ukjent adresse under /api/ |
| 405 | – | Metoden finnes ikke for adressen |
| 409 | id-taken | ID-en brukes av en annen leverandør |
| 409 | visibility-locked | visibility eller profile kan ikke endres på et objekt som finnes |
| 409 | in-use | Objektet er i bruk og kan ikke slettes. Svaret har lista usedBy (kap. 4) |
| 409 | live-channel | Kanalen sender NRK direkte og har ingen spillelister |
| 413 | too-large | For stort: JSON-kropp over grensen (1 MB for kanaler, spillelister og lysbildeserier, ellers 512 KB), zip-pakke over 200 MB, manifest.json over 1 MB, eller en serie som bruker mer enn 200 MB filer |
| 415 | unsupported-media-type | Lysbildeserier må sendes som application/json eller zip (application/zip, application/x-zip-compressed eller application/x-zip) |
| 500 | – | Feil på serveren (Intern feil på serveren). Prøv igjen litt senere |
Feil uten code (401, 404 for ukjent adresse, 405 og 500) har bare error.
Innholds-API-et har ingen grense for antall kall (kap. 19).
Bak en proxy kan en omstart av serveren gi 502 eller 503 i noen sekunder – prøv igjen.
Profilen får kanalene sine gjennom et kanaloppsett: plasser (kanalnumre) som hver peker på en kanal i biblioteket. Kanalen bestemmer hvilken spilleliste som går hvert sendedøgn, og spillelisten består av innslag av de tre typene. Lysbildeserier er egne objekter med sine mediefiler, så de kan brukes i mange spillelister.
| Objekt | Hva | Eies av |
|---|---|---|
| Profil | TV-brukeren: én beboer med brukernavn og boks-ID. Leverandører ser den bare som ref: p_ og 10 tilfeldige bokstaver og tall. Hver profil har høyst én leverandør. | Enkel-TV |
| Kanaloppsett | Antall kanaler og hvilken kanal som ligger på hver plass (kap. 10). | profilen |
| Standardoppsett | Kanalene alle profiler får på plasser de ikke har satt selv, og reserveprogrammet for tomme plasser. | felles |
| Kanal | Navn, beskrivelse, farge, emneord og programmering: hvilken spilleliste som går hvilke sendedøgn, eller en NRK-kanal direkte (kap. 9). | leverandøren |
| Spilleliste | Innslag i rekkefølge, av blandet type (kap. 5). | leverandøren |
| Innslag | Ett program: en YouTube-video, en lysbildeserie eller et NRK-program. Del av en spilleliste. | – |
| Lysbildeserie | Lysbilder med lag, lyd og musikk, som JSON eller zip-pakke (kap. 6–7). | leverandøren |
| Medium | En bilde-, lyd- eller videofil. Lagres én gang uansett hvor mange serier som bruker den (kap. 8). | serveren |
| Bio, notater, bilder | Bio skrevet av personalet, notater og bilder fra familien, og eierens samtykke (kap. 14). | profilen |
| Hendelse | Noe som skjedde på TV-en, i en samtale eller med kanaloppsettet – sendt av TV-en eller laget av serveren (kap. 13). | profilen |
"visibility": "shared" kan leses og brukes av alle leverandører og vises på alle profiler. "visibility": "personal" krever "profile": "p_…": objektet kan bare brukes på den profilen og er bare synlig for leverandøren som eier det. Profilen må ha nøkkelens leverandør som sin leverandør – ellers 404 no-profile.visibility og profile kan ikke endres på et objekt som finnes (409 visibility-locked). Slett og lag objektet på nytt.., - og _, og de begynner med bokstav eller tall. De er unike på hele serveren for hver type. Begynn gjerne med leverandørens navn, f.eks. enkeltv.ingrids-kanal.409 id-taken. Å slette en annen leverandørs felles objekt gir 403 not-owner; en annen leverandørs personlige objekt finnes ikke (404).409 in-use med en liste usedBy, f.eks. ["channel:allsang"] eller ["lineup:p_7Kq2mPx9Qa", "default-lineup"]. Andre leverandørers personlige objekter og profiler vises bare som type ("playlist", "lineup"), uten ID."changed": false; ellers øker versjonen med 1. Bare gjeldende versjon lagres. Hendelsene sier hvilken versjon som gikk (playlistVersion, slideshowVersion)."playlists": [] alle spillelistene fra en kanal. Vil du endre noe, hent dokumentet med GET, endre feltene og send hele svaret tilbake; ekstrafeltene i GET-svaret (id, version, provider, mine, createdAt, updatedAt, og duration og bytes for serier) ignoreres."" eller null) utelates. Det lagrede dokumentet kan hentes igjen med GET.length: en emoji teller som 2.En spilleliste er innslag i rekkefølge, og de tre typene – YouTube
NRK Lysbilder – kan blandes fritt. Spillelister lagres med
PUT /api/publish/playlists/{id}; svaret er { "id", "version", "changed", "items" }, der
items er antall innslag. En spilleliste vises først på TV-en når den er publisert til en kanal (kap. 9).
enkeltv-playlist/1| Felt | Type | Beskrivelse |
|---|---|---|
format * | tekst | "enkeltv-playlist/1" |
title * | tekst ≤ 120 | Navn for leverandøren og Enkel-TV. Vises ikke for beboeren |
visibility * | tekst | shared eller personal |
profile | p_… | Påkrevd for personal, ikke lov for shared |
items * | liste | 1–1000 innslag, se oversikten under |
Hvert innslag har noen felles felt og ett objekt med typens egne felt. Objektet har samme navn som kind:
et YouTube-innslag har "kind": "youtube" og objektet "youtube": { … }. Et objekt for en annen
type gir feil. * = påkrevd; alt annet er valgfritt.
| Felt | Type og grenser | Betyr |
|---|---|---|
| Felles for alle innslag | ||
id * | 1–80 tegn: bokstaver, tall, . _ : -, først bokstav eller tall | Leverandørens ID for programmet, unik i spillelisten. Bruk samme ID for samme innhold i alle spillelister – da kan hendelsene sammenlignes på tvers |
kind * | youtube · nrk · slideshow | Typen – bestemmer avspilleren |
title * | tekst ≤ 120 | Vises i kanalbanneret («Nå: …», «Neste kl. 19:30: …») |
duration * | heltall 1–86 400 s | Sekunder på tidslinjen. Påkrevd for youtube og nrk. For slideshow regnes lengden ut av serien; en oppgitt verdi ignoreres |
tags | ≤ 20 emneord à ≤ 40 tegn | F.eks. ["tog", "vestlandet"]. Like emneord slås sammen. Følger med i item.started |
availableFromavailableTo | YYYY-MM-DD, availableTo ≥ availableFrom | Første og siste sendedøgn innslaget er med (til og med). Utenfor vinduet hoppes innslaget over, og resten av lista rykker sammen |
YouTube "kind": "youtube" – objektet youtube | ||
videoId * | 11 tegn: bokstaver, tall, _ og - | YouTube-ID-en, f.eks. "ZlGyCfigmFg" fra youtube.com/watch?v=ZlGyCfigmFg. Videoen må tillate innbygging |
start | heltall ≥ 0 s, standard 0 | Hvor langt inn i videoen innslaget begynner. Med start og duration kan man vise en bit av en lang video – f.eks. 30 minutter fire timer inn i Bergensbanen |
NRK "kind": "nrk" – objektet nrk | ||
nrkId * | 4–40 tegn: bokstaver, tall, _ og -, først bokstav eller tall | NRK sin program-ID, f.eks. "FUHA01005272" – ID-en i adressen på tv.nrk.no. Det er alt som trengs for å spille av |
type | program · episode · film | live gir 400: NRK1–3 direkte er en egen kanaltype (kap. 9), ikke et innslag |
seriesTitle | tekst ≤ 120 | Serienavn for episoder |
season, episode | heltall 0–10 000 og 0–100 000 | Sesong og episode |
year | heltall 1800–2200 | Produksjonsår |
description | tekst ≤ 500 | Kort omtale, vises i plassholderen |
imageUrl | https, ≤ 2000 tegn | Programbilde – vises mens videoen laster, og i plassholderen |
subtitlesUrl | https, ≤ 2000 tegn | Undertekster (WebVTT), slås på som standard. Filen hentes med CORS, så den må sendes med Access-Control-Allow-Origin, ellers vises ikke undertekstene |
start | heltall ≥ 0 s, standard 0 | Hvor langt inn i programmet innslaget begynner |
Lysbilder "kind": "slideshow" – objektet slideshow | ||
ref * | ID-en til en lagret lysbildeserie | Serien lagres først (kap. 6–7) og må følge reglene for referanser (kap. 4): en felles spilleliste kan bare bruke felles serier. Innslagets lengde er seriens lengde |
GET /api/publish/playlists/{id} viser alltid seriens nåværende lengde som duration på
lysbildeinnslag. playUrl og format i nrk-objektet fra eldre dokumenter brukes ikke: de
ignoreres og fjernes når spillelisten lagres.
PUT /api/publish/playlists/enkeltv.ingrid-kveld-uke41
{
"format": "enkeltv-playlist/1",
"title": "Ingrid – kveld, uke 41",
"visibility": "personal",
"profile": "p_7Kq2mPx9Qa",
"items": [
{ "id": "yt.bergensbanen", "kind": "youtube", "title": "Bergensbanen",
"duration": 1800, "tags": ["tog", "vinter"],
"youtube": { "videoId": "d_S_13TWn1c", "start": 14400 } },
{ "id": "lysbilder.bergen-1950", "kind": "slideshow", "title": "Bergen på 1950-tallet",
"tags": ["bergen", "hjemsted"],
"slideshow": { "ref": "enkeltv.ingrid-bergen-1950" } },
{ "id": "yt.ZlGyCfigmFg", "kind": "youtube", "title": "Allsang: Liten fuggel",
"duration": 208, "tags": ["allsang"],
"youtube": { "videoId": "ZlGyCfigmFg" } },
{ "id": "nrk.fleksnes.s1e1", "kind": "nrk", "title": "Fleksnes",
"duration": 2196, "tags": ["humor", "70-tallet"],
"availableFrom": "2026-10-01", "availableTo": "2026-12-31",
"nrk": { "nrkId": "FUHA01005272", "type": "episode",
"seriesTitle": "Fleksnes", "season": 1, "episode": 1, "year": 1972 } }
]
}
TV-en spiller videoen i YouTubes innebygde spiller i fullskjerm, fra start pluss hvor langt sendingen har
kommet. Kan videoen ikke spilles, melder TV-en playback.error med kind media-missing
(fjernet eller privat), not-embeddable (eieren tillater ikke innbygging), unsupported (annen feil)
eller network (YouTube kunne ikke lastes), avslutter innslaget med reason: error og viser et rolig
pausebilde. Innslaget blir stående i tidslinjen, og TV-en prøver igjen senere (kap. 12). Leverandøren bør bytte det ut.
https://psapi.nrk.no/playback/manifest/program/{nrkId}) og spiller HLS-strømmen den får, fra
start pluss hvor langt sendingen har kommet. Etter en samtale hentes en fersk adresse hvis den gamle er mer
enn ett minutt gammel. Enkel-TV-serveren henter aldri noe fra NRK og er ikke mellomledd for strømmen."programming": { "type": "live", "nrkChannel": "nrk1" } (kap. 9). TV-en henter strømmen på samme måte
(…/playback/manifest/channel/nrk1) når kanalen stilles inn.duration er påkrevd, fordi tidslinjen regnes ut av den. Den står i NRK sitt manifest som
playable.duration (ISO 8601, f.eks. PT36M36S = 2196 s). Slutter programmet før tiden, vises
pausebildet resten av innslaget.playback.error én gang med kind
not-available (NRK sier at programmet ikke kan spilles nå, f.eks. utløpt eller geoblokkert),
media-missing (NRK kjenner ikke ID-en eller ga ingen adresse), network (fikk ikke kontakt med NRK,
eller strømmen stopper opp) eller unsupported (strømmen kan ikke spilles), med details. Ved
network prøver TV-en igjen i samme innslag, sjeldnere og sjeldnere. Innslaget avsluttes ikke med
reason: error, men sett tid telles ikke mens plassholderen står. Når programmet faktisk spiller, kommer
playback.started (kap. 13).Status for NRK: Avspillingen bruker NRK sitt uoffisielle API (PSAPI), og vi har ikke avtale
med NRK ennå (kap. 19). NRK sitt API svarer i dag bare nettsider på nrk.no og på maskinen selv (localhost). Til TV-boksene
er satt opp for det, kan NRK-innslag og direktekanaler vise plassholderen i stedet for programmet; i hendelsene ses det som
playback.error med kind: network og details «Får ikke kontakt med NRK (nett eller CORS) …».
{ "id": "nrk.fleksnes.1972.fysiske-fordeler", "kind": "nrk", "title": "Fleksnes: Fysiske fordeler",
"duration": 2196, "tags": ["humor", "70-tallet"],
"nrk": { "nrkId": "FUHA01005272", "type": "episode", "seriesTitle": "Fleksnes",
"season": 1, "episode": 1, "year": 1972,
"imageUrl": "https://gfx.nrk.no/W-sVDcYdu6rzfyUZ5GAk4ABRYRfA_2J6J_fjWiAYLNfw" } }
{ "id": "nrk.hurtigruten.2011.bergen", "kind": "nrk",
"title": "Hurtigruten minutt for minutt: Ved kai i Bergen", "duration": 1173,
"nrk": { "nrkId": "DVFJ67000111" } }
Det andre innslaget viser det minste som trengs: bare nrkId.
Et lysbildeinnslag er bare "slideshow": { "ref": "<seriens id>" }. Selve serien – lysbilder med lag,
lyd og musikk – lagres for seg, som JSON (kap. 6) eller zip-pakke med filene (kap. 7), og kan brukes i så mange
spillelister man vil. En ny versjon av serien gjelder i alle spillelistene som peker på den, fra neste gang innslaget
starter.
En lysbildeserie beskriver skjermbilder med lag – bilde, video, tekst og form – plassert fritt på et 16:9-lerret,
med lyd og musikk på faste tider. TV-en tegner nøyaktig det JSON-en beskriver. Leverandøren kan dermed bygge rike
skjermbilder uten å sende kode. Serien lagres med PUT /api/publish/slideshows/{id}, enten som zip-pakke med
filene (kap. 7) eller som ren JSON når alle filene er https://- eller sha256:-adresser.
x og y er øvre venstre hjørne, w og h bredde og høyde – alle i prosent av lerretet (0–100).size) er prosent av lerretets høyde: 6 er omtrent 65 piksler på en full-HD-TV. For eldre seere anbefaler vi minst 4,5 for hovedtekst.start, end, fadeIn, fadeOut) er sekunder fra lysbildet begynner, med desimaler. Er fadeIn + fadeOut lengre enn tiden laget vises, krympes begge forholdsmessig.credit) vises lite og alltid nede til høyre.enkeltv-slideshow/1Kolonnen «Standard» er verdien TV-en bruker når feltet mangler. Det lagrede dokumentet beholder
defaults; TV-en får dem flettet inn.
| Felt | Type | Beskrivelse |
|---|---|---|
format * | tekst | "enkeltv-slideshow/1" |
title * | tekst ≤ 120 | Seriens tittel |
language | tekst ≤ 10 | nb, nn, se … |
visibility * | tekst | shared eller personal (med profile). Serier med familiebilder må være personlige |
profile | p_… | Påkrevd for personal |
tags | liste | Høyst 20 emneord à 40 tegn |
music | objekt | Bakgrunnsmusikk for hele serien, se under |
defaults | objekt | { "text": { "style": {…} }, "transition": {…} } – standardverdier så de ikke må gjentas |
slides * | liste | 1–500 lysbilder. Seriens lengde er summen av lysbildenes duration |
| Felt | Type | Standard | Beskrivelse |
|---|---|---|---|
id * | tekst ≤ 80 | – | Unik i serien (samme tegn som innslags-ID), brukes i slide.shown |
duration * | 1–600 s | – | Hvor lenge lysbildet står, desimaler er lov |
background | #rrggbb | #000000 | Bakgrunn bak lagene |
transition | objekt | fade 1 s | { "type": "fade", "duration": 0–5 } (det nye lysbildet toner inn over det forrige) eller { "type": "cut" }. Overgangen er en del av lysbildets egen tid. Mangler feltet, brukes defaults.transition, ellers fade på 1 s. En fade uten duration varer alltid 1 s, også når defaults.transition har en annen lengde |
layers * | liste | – | 1–12 lag, se under |
audio | liste | ingen | 0–4 lydklipp, f.eks. opplesning og en lydeffekt |
credit | tekst ≤ 200 | ingen | Kreditering, nede til høyre. Ta den med når lisensen krever navngivelse |
| Felt | Type | Standard | Beskrivelse |
|---|---|---|---|
type * | tekst | – | image · video · text · shape |
x, y, w, h | 0–100 | 0, 0, 100, 100 | Plassering og størrelse. For tekst følger høyden teksten når h mangler |
start, end | sekunder | 0, lysbildets slutt | Når laget vises. start må være mindre enn lysbildets lengde; end må være etter start og ikke etter lysbildets slutt |
fadeIn, fadeOut | ≥ 0 s | 0 | Myk inn- og uttoning |
opacity | 0–1 | 1 | Gjennomsiktighet |
| Type | Egne felt (standard i parentes) |
|---|---|
image | src * (et bilde) · fit: cover (fyller, kan beskjæres) eller contain (hele bildet) (cover) · alt ≤ 300 tegn · motion: rolig zoom og panorering gjennom lagets tid, { "from": { "scale": 1, "x": 50, "y": 50 }, "to": { "scale": 1.15, "x": 40, "y": 55 } }, der x/y (0–100, standard 50) er punktet i bildet det zoomes mot og scale er 1–2 (standard 1). Både from og to må være med |
video | src * (MP4) · duration *: klippets lengde, 0,1–86 400 s · fit (cover) · trimStart: sekunder inn i klippet, mindre enn duration (0) · muted (true når lysbildet har audio, ellers false) · volume 0–1 (1) · loop (false). Uten loop blir siste bilde stående når klippet er ferdig |
text | text *: ren tekst ≤ 500 tegn, \n gir linjeskift (ingen HTML) · align: left · center · right (left) · valign: top · middle · bottom (top), og det virker bare når laget har h · style, se under |
shape | shape *: rect eller ellipse · fill og stroke: #rrggbb eller #rrggbbaa (ingen) · strokeWidth: 0–5 prosent av høyden (0) · radius: hjørner på rect, 0–50 prosent av høyden (0). Brukes til bakgrunner og til å peke ut noe i bildet |
style for tekstVerdiene flettes slik: TV-ens standard → defaults.text.style → lagets egen style.
| Felt | Type | Standard | Beskrivelse |
|---|---|---|---|
font | tekst | sans | sans eller serif – TV-ens egne skrifter, ingen nedlasting |
size | 1,5–20 | 5 | Prosent av lerretets høyde |
weight, italic | regular, false | regular eller bold; true/false | |
color, background | farge | hvit, ingen | #rrggbb eller #rrggbbaa (med gjennomsiktighet). Bakgrunnen dekker bare selve teksten (pluss padding), ikke hele laget. Vil du ha en flate over hele bredden, legger du et shape-lag under teksten |
padding, radius | 0–10 | 0, 0 | Luft rundt teksten og runde hjørner på bakgrunnen, i prosent av høyden |
lineHeight | 0,8–3 | 1,25 | Linjeavstand |
shadow | sant/usant | uten bakgrunn | Skygge bak teksten for lesbarhet. Standard er true når teksten ikke har bakgrunn |
| Felt | Type | Standard | Beskrivelse |
|---|---|---|---|
audio[] på lysbildet | |||
src * | fil | – | Lydfil: MP3, AAC, Ogg eller M4A (en MP4-fil godtas også) |
start | ≥ 0 s | 0 | Når lyden begynner |
duration * | 0,1–600 s | – | Lydfilens lengde. start + duration må være innenfor lysbildet (0,05 s slingring) |
volume | 0–1 | 1 | Volum |
duck | sant/usant | true | Senk musikken til duckTo mens klippet spiller |
transcript | ≤ 2000 tegn | ingen | Det som blir sagt. Vises ikke, men lagres og leveres med serien |
music for serien | |||
src *, duration * | fil, 0,1–86 400 s | – | Lydfil og lengde. Musikken går over alle lysbildene og begynner forfra når den er ferdig |
volume, duckTo | 0–1 | 0,25, 0,08 | Volum, og volum mens et lydklipp med duck spiller. Lyd fra et video-lag demper ikke musikken |
Filer (src, høyst 2000 tegn) oppgis på én av tre måter: en sti i zip-pakken som begynner med
media/ (media/torget-1954.jpg), en fil serveren allerede har (sha256: fulgt av 64
heksadesimale tegn, kap. 8), eller en https://-adresse hos leverandøren. Serveren sjekker at filen passer
til laget: et image-lag må peke på et bilde, et video-lag på en video (MP4, MOV eller 3GP – kap. 7), og lyd og musikk på
en lydfil eller MP4. https-adresser hentes ikke av serveren og sjekkes ikke: TV-en henter dem direkte fra
leverandørens server, så de må være åpne og i et format TV-en kan spille (kap. 7).
Serveren sjekker tidene, ikke lengden på selve filene: lag og lydklipp må ligge innenfor lysbildets
duration, og lengdene som er oppgitt for lyd og video, brukes til det. Lyd som likevel er lengre, kuttes når
lysbildet er over.
Lysbildet «torget» i eksempelet under: duration: 12, kryssfade inn på 1,2 s, opplesning fra 1 s (9,4 s lang), pekeren og «Fiskebodene» fra 5 til 10 s.
manifest.json i en pakke{
"format": "enkeltv-slideshow/1",
"title": "Bergen på 1950-tallet",
"language": "nb",
"visibility": "shared",
"tags": ["bergen", "1950-tallet", "hjemsted"],
"music": { "src": "media/musikk/morgenstemning.mp3", "duration": 245.0,
"volume": 0.25, "duckTo": 0.08 },
"defaults": {
"text": { "style": { "font": "sans", "size": 5.5, "color": "#ffffff",
"background": "#00000099", "padding": 1.2, "radius": 1 } },
"transition": { "type": "fade", "duration": 1.2 }
},
"slides": [
{
"id": "floyen", "duration": 10,
"layers": [
{ "type": "image", "src": "media/floyen-1961.jpg", "fit": "cover",
"alt": "Utsikt fra Fløyen over Vågen",
"motion": { "from": { "scale": 1.0, "x": 50, "y": 50 },
"to": { "scale": 1.1, "x": 55, "y": 60 } } },
{ "type": "text", "text": "Utsikten fra Fløyen", "x": 5, "y": 80, "w": 90,
"align": "center", "start": 0.5, "fadeIn": 0.6 }
],
"audio": [
{ "src": "media/tale/floyen.mp3", "start": 1.0, "duration": 7.8,
"transcript": "Vi begynner oppe på Fløyen, med utsikt over hele byen." }
],
"credit": "Jac Brun / Mittet & Co. / Nasjonalbiblioteket, 1961"
},
{
"id": "torget", "duration": 12,
"layers": [
{ "type": "image", "src": "media/torget-1954.jpg", "fit": "cover",
"motion": { "from": { "scale": 1.0, "x": 50, "y": 50 },
"to": { "scale": 1.15, "x": 40, "y": 55 } } },
{ "type": "shape", "shape": "ellipse", "x": 22, "y": 48, "w": 30, "h": 18,
"stroke": "#f2c48d", "strokeWidth": 0.6,
"start": 5, "end": 10, "fadeIn": 0.8, "fadeOut": 0.8 },
{ "type": "text", "text": "Fiskebodene", "x": 24, "y": 37, "w": 26,
"start": 5, "end": 10, "style": { "size": 3.5 } },
{ "type": "text", "text": "Torget i Bergen, 1954", "x": 5, "y": 80, "w": 90,
"align": "center", "start": 0.5, "fadeIn": 0.6,
"style": { "weight": "bold", "size": 6 } }
],
"audio": [
{ "src": "media/tale/torget.mp3", "start": 1.0, "duration": 9.4,
"transcript": "Dette er Torget i 1954. Fiskehandlerne står bak diskene." }
],
"credit": "Mittet & Co. / Nasjonalbiblioteket, 1954"
},
{
"id": "bryggen-film", "duration": 14, "transition": { "type": "cut" },
"layers": [
{ "type": "video", "src": "media/bryggen-1958.mp4", "duration": 14.0, "fit": "cover" },
{ "type": "text", "text": "Bryggen en vårdag", "x": 5, "y": 6, "w": 60,
"start": 2, "end": 9, "fadeIn": 0.6, "fadeOut": 0.6 }
]
}
]
}
En hel lysbildeserie kan sendes som én zip-fil: manifest.json med serien, og mediefilene den peker på. Serveren
sjekker pakken, lagrer filene og gir serien en versjon. Etterpå kan serien brukes i så mange spillelister man vil, bare ved
å peke på ID-en – uten ny opplasting.
enkeltv.bergen-1950.zip
├── manifest.json ← lysbildeserien (enkeltv-slideshow/1)
└── media/
├── floyen-1961.jpg
├── torget-1954.jpg
├── bryggen-1958.mp4
├── tale/floyen.mp3
├── tale/torget.mp3
├── musikk/morgenstemning.mp3
└── gammel-utgave.jpg ← ikke brukt: gir en advarsel
manifest.json ligger i roten av zip-filen (rekkefølgen i pakken spiller ingen rolle), er gyldig JSON i UTF-8 og høyst 1 MB. Filene manifestet peker på, ligger under media/, og stiene i manifestet er relative til roten. Er selve mappen pakket (mappe/manifest.json), sier feilen «pakk innholdet i mappen, ikke selve mappen»./ først, ingen stasjonsbokstav, ingen .. eller \, ingen tomme ledd eller .-ledd, og ingen symbolske lenker. Samme sti kan ikke finnes to ganger.warnings. Filer som Mac og Windows legger i pakken av seg selv (__MACOSX/, ._-filer, .DS_Store, Thumbs.db og desktop.ini), gir ingen advarsel.- og _ i filnavnene.400 invalid-slideshow med stien til feltet og filen.400 invalid-slideshow, 403 no-provider, 409 id-taken), så svaret kan komme før hele pakken er sendt.| Type | Formater serveren kjenner igjen | Anbefaling |
|---|---|---|
| Bilde | JPEG, PNG, WebP | 1920–3840 px bredt (større når bildet zoomes) |
| Lyd | MP3, AAC (ADTS), Ogg, M4A | Tale og musikk med jevnt lydnivå |
| Video | MP4 og andre filer i samme beholderformat (ISO-medier med ftyp), også MOV (QuickTime), M4V og 3GP. Alle leveres til TV-en som video/mp4 | MP4 med H.264 og AAC, 1080p |
HEIC-, HEIF- og AVIF-bilder (vanlig fra iPhone) er ukjent filtype og må gjøres om til JPEG før de legges i pakken. MOV og 3GP godtas, men serveren sjekker ikke kodeken i klippet: H.264 virker, mens HEVC (H.265), som iPhone tar opp med som standard, kan TV-en ikke spille sikkert. Gjør derfor helst videoen om til MP4 med H.264 og AAC.
sha256:-filer – kan være høyst 200 MB. Ellers 413 too-large, f.eks. Serien bruker 212 MB – grensen er 200 MB.POST /media/check, og pek på resten som sha256:….PUT /api/publish/slideshows/enkeltv.bergen-1950
Content-Type: application/zip
<zip-filen>
HTTP/1.1 200 OK
{ "id": "enkeltv.bergen-1950", "version": 1, "changed": true, "duration": 36, "slides": 3,
"bytes": 48213377, "media": { "new": 6, "reused": 0 },
"warnings": [ "media/gammel-utgave.jpg er ikke brukt i manifestet" ] }
duration er seriens lengde i sekunder, bytes summen av filene serien bruker, og media hvor mange filer som var nye og hvor mange serveren hadde fra før.GET /api/publish/slideshows/{id} gir det lagrede dokumentet med versjon, lengde og størrelse (eller 404). Da holder det å peke på den i spillelisten: "slideshow": { "ref": "enkeltv.bergen-1950" }."changed": false, samme versjon og "media": { "new": 0, "reused": N }. Spillelister peker alltid på gjeldende versjon; TV-en får den ved neste programskifte (kap. 11).https:// eller sha256:, kan sendes som application/json uten zip. media/-stier er bare lov i zip-pakker.POST /api/publish/media/check med en liste med høyst 1000 sha256-summer svarer hvilke filer serveren har. Manifestet kan peke på dem som sha256:…, så pakken bare trenger de nye filene.POST /api/publish/media/check
{ "sha256": [ "e1e5628ba30deef1d0a0e5c38e6d6b74aa32a21da631f7159a9b7ca78f02dbe6",
"23193579fb505275fb6ddd64b1e62e36d26db474958cda9c1c9659d6cb62be26" ] }
{ "have": [ "e1e5628ba30deef1d0a0e5c38e6d6b74aa32a21da631f7159a9b7ca78f02dbe6" ],
"missing": [ "23193579fb505275fb6ddd64b1e62e36d26db474958cda9c1c9659d6cb62be26" ] }
Bilder, lyd og video fra lysbildepakkene – og familiebildene fra mobilappen – ligger i serverens mediearkiv.
/media/<sha256>.<filtype>?e=<utløp>&s=<signatur>, og bare med gyldig signatur (HMAC-SHA256 med en hemmelighet som bare serveren har). Ugyldig eller utløpt adresse gir 403. Adressene kan ikke gjettes ut fra sha256-summen.GET og HEAD, riktig Content-Type, ETag (sha256-summen), Cache-Control: private, max-age=86400 og støtte for Range (ett område per forespørsel; 206, eller 416 for et ugyldig område; flere områder gir hele filen) og If-None-Match (304), så video og lyd kan spoles.photos:read og samtykke får bildets sha256 og kan bruke det som "src": "sha256:…"; i en felles serie eller en serie for en annen profil avvises det: … er et familiebilde og kan bare brukes i personlige serier for samme profil. Det gjelder også etter at familien har slettet bildet./photos jevnlig, og fjern bilder som er borte derfra, fra seriene.GET /api/publish/slideshows/{id} viser filene som sha256:… eller den opprinnelige https-adressen.En kanal er det beboeren opplever som «en TV-kanal»: et navn, et tema og en sending som går hele tiden. Kanaler lagres
med PUT /api/publish/channels/{id}. Samme kanal kan ligge på mange profiler (felles), eller lages for én
beboer (personlig).
Kanalen er fast, spillelistene skiftes. Kanalen har et fast navn – det beboeren ser når hen bytter kanal. Spillelisten er det som spilles den dagen, og leverandøren publiserer en ny så ofte den vil, helst én gang om dagen. Kommer det ingen ny, går den forrige videre til en ny kommer. En kanal kan lages før den har noen spilleliste; da er den tom, og plassen viser standardoppsettet, reserveprogrammet eller «Kommer snart».
enkeltv-channel/1| Felt | Type | Beskrivelse |
|---|---|---|
format * | tekst | "enkeltv-channel/1" |
name * | tekst ≤ 40 | Kanalnavnet, vises stort når man bytter kanal |
description | tekst ≤ 120 | Én linje under navnet i kanalbanneret |
color | #rrggbb | Aksentfarge i kanalbanneret og på pausebildet |
visibility * | tekst | shared eller personal |
profile | p_… | Profilens ref. Påkrevd for personal, ikke lov for shared |
tags | liste | Høyst 20 emneord à høyst 40 tegn. Like emneord slås sammen |
programming * | objekt | Hva som går på kanalen: "type": "loop" (spillelister, se under) eller "type": "live" (NRK direkte) |
"type": "loop" med en liste playlists (0–100 oppføringer). Hver oppføring er
{ playlistId, from?, to? }, der from og to er sendedøgn
(YYYY-MM-DD, til og med; to kan ikke være før from). Sendedøgn D varer fra D kl. 06:00
til D+1 kl. 06:00 norsk tid. Slik velger serveren dagens spilleliste:
from er etter D, eller to er før D, gjelder ikke.from. En oppføring uten from regnes som eldst.from, vinner den som står sist i lista.Slik kan neste ukes spilleliste legges inn på forhånd. Den tar over kl. 06:00 den dagen den gjelder fra (kap. 11).
"type": "schedule" (faste klokkeslett) avvises foreløpig med Daglige sendeplaner kommer senere – bruk type "loop".
PUT /api/publish/channels/sporet
{
"format": "enkeltv-channel/1",
"name": "Sporet",
"description": "Togreiser gjennom Norge",
"color": "#f2c48d",
"visibility": "shared",
"tags": ["tog", "slow-tv"],
"programming": { "type": "loop", "playlists": [ { "playlistId": "enkeltv.sporet" } ] }
}
PUT /api/publish/channels/enkeltv.ingrids-kanal
{
"format": "enkeltv-channel/1",
"name": "Ingrids kanal",
"description": "Tog, Bergen og gamle favoritter",
"color": "#e8a87c",
"visibility": "personal",
"profile": "p_7Kq2mPx9Qa",
"tags": ["personlig"],
"programming": {
"type": "loop",
"playlists": [
{ "playlistId": "enkeltv.ingrid-kveld" },
{ "playlistId": "enkeltv.ingrid-kveld-uke41", "from": "2026-10-05", "to": "2026-10-11" }
]
}
}
Her går enkeltv.ingrid-kveld-uke41 fra mandag 5. oktober kl. 06:00 til mandag 12. oktober kl. 06:00.
Før og etter gjelder enkeltv.ingrid-kveld, som ikke har datoer. Svaret er { "id", "version", "changed" }.
Den vanlige arbeidsflyten er å lagre dagens spilleliste og så publisere den til kanalen – uten å sende hele kanalen på
nytt. from er dagens sendedøgn hvis den utelates, og oppføringen har ingen sluttdato, så den gjelder til en
nyere publiseres. Publiseres det to ganger samme dag, erstatter den siste den første. Oppføringer som aldri kan gjelde
igjen (passert to, eller eldre enn dagens oppføring uten sluttdato), ryddes bort, så lista ikke vokser.
Bare leverandøren som eier kanalen, kan publisere til den (ellers 403 not-owner). Spillelisten må finnes og
følge reglene for referanser (kap. 4): en felles kanal kan bare få felles spillelister.
PUT /api/publish/playlists/enkeltv.sporet-2026-10-06 # dagens spilleliste POST /api/publish/channels/sporet/playlists { "playlistId": "enkeltv.sporet-2026-10-06" } # gjelder fra i dag kl. 06:00 POST /api/publish/channels/sporet/playlists { "playlistId": "enkeltv.sporet-jul", "from": "2026-12-24", "to": "2026-12-26" }
Kroppen er { playlistId, from?, to? }, med from og to som sendedøgn
(YYYY-MM-DD). Svaret er det samme som for PUT /channels/{id}: { "id", "version", "changed" }.
Feilene viser til feltene i forespørselen, f.eks. playlistId viser til spillelisten «x», som ikke finnes eller
to kan ikke være før from. En kanal kan ha høyst 100 spillelister som gjelder i dag eller senere; flere gir
400 invalid-channel. Vil du fjerne eller flytte oppføringer, hent kanalen med GET, endre
programming.playlists og lagre den med PUT.
En direktekanal viser en NRK-kanal direkte hele døgnet. Programmeringen er "type": "live" med
nrkChannel *: nrk1, nrk2 eller nrk3. Kanalen har ingen
spillelister og ingen tidslinje: POST /channels/{id}/playlists gir 409 med code: "live-channel".
TV-en henter strømmen hos NRK (https://psapi.nrk.no/playback/manifest/channel/nrk1) når kanalen stilles inn, og
spiller direktesendingen (kap. 5). Faller strømmen ut, viser TV-en «Sendingen kommer straks tilbake» og prøver igjen av
seg selv, først etter 15 sekunder og så sjeldnere, høyst hvert 5. minutt. Finnes det allerede felles direktekanaler for
NRK1–3 (se GET /channels), bruk dem i kanaloppsettet i stedet for å lage nye.
PUT /api/publish/channels/nrk1
{
"format": "enkeltv-channel/1",
"name": "NRK1",
"description": "NRK1 direkte",
"color": "#e05a4f",
"visibility": "shared",
"tags": ["nrk", "direkte"],
"programming": { "type": "live", "nrkChannel": "nrk1" }
}
Direkte NRK er ikke et innslag: et NRK-innslag med "type": "live" gir 400 (kap. 5).
I GET /channels har hver kanal feltet type (loop eller live).
Kanaloppsettet bestemmer hva beboeren finner når hen blar med pil opp/ned. Det har et antall kanaler
(channelCount, 1–99) og en kanal for plassene profilen har valgt selv. Fjernkontrollen går rundt:
0 → 1 → … → channelCount → 0, og 0 er alltid klokkeskjermen. Har profilen ikke et eget antall, gjelder
standardoppsettets (10 hvis standardoppsettet ikke har et antall).
For hver plass fra 1 til channelCount velger serveren den første kilden som har innhold i dagens sendedøgn:
| # | source | Forklaring |
|---|---|---|
| 1 | profile | Profilens egen plass – en felles kanal, eller en personlig kanal for denne profilen. Kanalen kan få et eget navn på denne profilen. |
| 2 | default | Plassen i standardoppsettet (bare felles kanaler). |
| 3 | fallback | Reserveprogrammet: én felles kanal som fyller tomme plasser. |
| 4 | builtin | Ingenting: TV-en viser et rolig, innebygd «Kommer snart»-bilde (channelId: null). |
«Har innhold» betyr at kanalen finnes, at programmeringen har en spilleliste for dagens sendedøgn (kap. 9), og at spillelisten har minst ett innslag som er tilgjengelig denne datoen. Lysbildeinnslag der serien er slettet, regnes ikke med. En direktekanal (kap. 9) har alltid innhold. En kanal uten innhold i dag gjør at plassen faller videre til neste kilde.
enkeltv-lineup/1| Felt | Type | Beskrivelse |
|---|---|---|
format | tekst | "enkeltv-lineup/1". Påkrevd i PUT; valgfritt i PATCH, men må da være riktig |
channelCount | 1–99 | Antall kanaler på profilen. null (eller utelatt i PUT) betyr «følg standardoppsettet». Utelatt i PATCH: uendret |
slots | objekt | Plassnummer "1"–"99" → kanal-ID, eller { "channel": "allsang", "name": "Sangstund" } for et eget navn (≤ 40 tegn) på denne profilen. I PATCH fjerner null plassen. Påkrevd i PUT. Plasser over channelCount er lov: de lagres, men vises ikke |
Kanalen må finnes og være felles eller personlig for denne profilen – ellers 400 invalid-lineup, f.eks.
slots.3 viser til kanalen «x», som ikke finnes. En felles kanal kan være laget av hvilken som helst leverandør.
| Felt | Beskrivelse |
|---|---|
format | "enkeltv-lineup/1" |
profile | Profilens ref |
channelCount / ownChannelCount | Antallet TV-en viser, og profilens eget tall (null = følger standardoppsettet) |
slots | Plassene profilen har valgt selv, sortert |
resolved | Hva som faktisk vises på hver plass i dag: { slot, channelId, name, source } |
updatedAt, updatedBy | Siste endring, og hvem: { "type": "provider" | "staff", "name": … } |
PATCH /api/publish/profiles/p_7Kq2mPx9Qa/lineup (endrer bare plassene som er nevnt)
{ "slots": { "7": "kysten", "8": { "channel": "allsang", "name": "Sangstund" }, "15": null } }
GET /api/publish/profiles/p_7Kq2mPx9Qa/lineup
{
"format": "enkeltv-lineup/1",
"profile": "p_7Kq2mPx9Qa",
"channelCount": 15,
"ownChannelCount": 15,
"slots": {
"6": "enkeltv.ingrids-kanal",
"7": "kysten",
"8": { "channel": "allsang", "name": "Sangstund" },
"14": "gammeldans"
},
"resolved": [
{"slot": 1, "channelId": "nrk1", "name": "NRK1", "source": "default"},
{"slot": 2, "channelId": "nrk2", "name": "NRK2", "source": "default"},
{"slot": 3, "channelId": "nrk3", "name": "NRK3", "source": "default"},
{"slot": 4, "channelId": "sporet", "name": "Sporet", "source": "default"},
{"slot": 5, "channelId": "fjell-og-fjord", "name": "Fjell og fjord", "source": "default"},
{"slot": 6, "channelId": "enkeltv.ingrids-kanal", "name": "Ingrids kanal", "source": "profile"},
{"slot": 7, "channelId": "kysten", "name": "Kysten", "source": "profile"},
{"slot": 8, "channelId": "allsang", "name": "Sangstund", "source": "profile"},
{"slot": 9, "channelId": "norge-for-i-tiden", "name": "Norge før i tiden", "source": "default"},
{"slot": 10, "channelId": "klassisk", "name": "Klassisk", "source": "default"},
{"slot": 11, "channelId": "dyr-og-fugler", "name": "Dyr og fugler", "source": "default"},
{"slot": 12, "channelId": "bilder-fra-bergen", "name": "Bilder fra Bergen", "source": "default"},
{"slot": 13, "channelId": "nrk", "name": "NRK-arkivet", "source": "default"},
{"slot": 14, "channelId": "gammeldans", "name": "Gammeldans", "source": "profile"},
{"slot": 15, "channelId": "sporet", "name": "Sporet", "source": "fallback"}
],
"updatedAt": "2026-10-05T08:12:00.000Z",
"updatedBy": { "type": "provider", "name": "Enkel-TV" }
}
lineups:write: PUT erstatter hele oppsettet, PATCH endrer bare plassene og feltene som er med.Den siste lagringen gjelder. Hver endring lagrer hvem som gjorde den, og gir hendelsen lineup.changed med
plassene som ble endret (kap. 13). En lagring som ikke endrer noe, gir ingen hendelse.
Standardoppsettet har samme format, pluss fallback (reserveprogrammet: en felles kanal-ID eller
null). Det kan bare inneholde felles kanaler, leses med alle nøkler og endres med PUT og
rettigheten lineups:default. channelCount gjelder profiler som ikke har satt sitt eget antall;
utelates det i PUT, blir det 10. Plasser med null hoppes over, og en plass kan ha eget navn
({ "channel", "name" }) som i profilens oppsett – men fallback må være en ren kanal-ID.
Svaret på GET og PUT har feltene format, channelCount, slots,
fallback, updatedAt og updatedBy. Er standardoppsettet aldri lagret, er det
{ "format": "enkeltv-lineup/1", "channelCount": 10, "slots": {}, "fallback": null, "updatedAt": null, "updatedBy": null }.
PUT /api/publish/lineups/default
{
"format": "enkeltv-lineup/1",
"channelCount": 13,
"slots": { "1": "nrk1", "2": "nrk2", "3": "nrk3", "4": "sporet", "5": "fjell-og-fjord",
"6": "kysten", "7": "gamle-slagere", "8": "gammeldans", "9": "norge-for-i-tiden",
"10": "klassisk", "11": "dyr-og-fugler", "12": "bilder-fra-bergen", "13": "nrk" },
"fallback": "sporet"
}
Med dette standardoppsettet får alle profiler direktekanalene NRK1–3 (kap. 9) på plass 1–3, med mindre
profilen har lagt egne kanaler på de plassene. Standardoppsettet endres bare av Enkel-TVs egen innholdstjeneste
(lineups:default).
Hver kanal har én tidslinje som går hele tiden, også når ingen ser på. Tidslinjen er ren regning uten lagret tilstand: serveren og TV-en bruker den samme regnemåten og kommer alltid fram til det samme. Alle profiler som har samme felles kanal, ser derfor det samme samtidig. En personlig kanal har bare én seer. Direktekanaler (kap. 9) har ingen tidslinje: de viser NRK-sendingen slik den går.
Sendedøgnet med dato D begynner D kl. 06:00 og slutter D+1 kl. 06:00, norsk tid (Europe/Oslo). Mellom
midnatt og kl. 06:00 hører natten til gårsdagens sendedøgn. Starttidspunktet regnes ut med tidssonen, så døgnet er
25 timer når klokka stilles tilbake om høsten (sendedøgnet lørdag 24. oktober 2026 varer til søndag kl. 06:00 vintertid)
og 23 timer når den stilles fram om våren (sendedøgnet lørdag 27. mars 2027).
dagStart = D kl. 06:00 norsk tid (som UTC-tidspunkt) dagSlutt = D+1 kl. 06:00 L = summen av duration for dagens innslag (sekunder) runde = gulv((t − dagStart) / L) t = tidspunktet vi spør om pos = t − dagStart − runde · L hvor langt inn i løkkerunden innslag = det første innslaget som slutter etter pos offset = pos − innslagets start
duration bestemmer tidslinjen, ikke når mediet faktisk slutter. For lysbildeinnslag er lengden alltid seriens lengde (summen av lysbildene).from/to i kanalens programmering, for sendedøgnets dato (kap. 9).availableFrom og availableTo på et innslag er også sendedøgn. Utenfor vinduet er innslaget ikke med i dagens liste, og resten av lista rykker sammen. Et innslag som er med, er med hele sendedøgnet.serverTime i hver polling og bruker serverens tid. En måling brukes bare når pollingen tok høyst 3 s; ellers beholdes forrige avvik.Leverandøren ser resultatet med GET /api/publish/channels/{id}/schedule?date=YYYY-MM-DD – alle innslag i
sendedøgnet, runde for runde (kap. 16).
TV-en spør serveren hvert 2. sekund om noe er endret. Endres noe i biblioteket, standardoppsettet eller profilens kanaloppsett, henter TV-en kanalene på nytt – en endring når altså TV-en innen omtrent 2 sekunder. Det samme skjer når et nytt sendedøgn begynner, og ved midnatt UTC (da fornyes adressene til mediefilene, kap. 8). Er TV-en på en kanal, gjør den så dette:
| Situasjon | TV-en … |
|---|---|
| Et nytt sendedøgn har begynt | … stiller inn kanalen på nytt med en gang, på direkte, med det nye døgnets spilleliste. Finnes plassen ikke lenger, går den til plass 0. |
| Plassen finnes ikke lenger (færre kanaler) | … går til klokkeskjermen (plass 0). |
| Plassen har fått en annen kanal, eller kanalen har fått eller mistet innhold | … stiller inn plassen på nytt med en gang, på direkte. |
Innslaget som spiller, finnes ikke lenger (ingen innslag med samme id) | … avslutter det (reason: replaced) og starter det som går nå etter den nye lista. |
| Alt annet – f.eks. ny versjon av spillelisten eller serien | … spiller innslaget ferdig. Ved slutten regnes neste innslag ut av den nye lista ved det klokkeslettet – det kan være midt i et innslag, fordi hele tidslinjen regnes fra kl. 06:00 med de nye lengdene. Navn, farge og beskrivelse oppdateres med en gang. |
Når en pårørende ringer, fryser TV-en avspillingen der den står – YouTube, NRK og lysbilder, med lyd, video og
animasjoner – fra det begynner å ringe til TV-en er tilbake fra samtaleskjermen. Etterpå fortsetter innslaget akkurat der det stoppet. Kanalen
ligger da like mye etter direkte som pausen varte, og neste innslag starter tilsvarende senere; klokkeslettene i
banneret er forskjøvet like mye. Bytter man kanal, er man tilbake på direkte og forsinkelsen er borte. Fjernkontrollen
bytter ikke kanal mens samtalen pågår. Pausen gir hendelsene playback.paused og playback.resumed
(med pausedSeconds).
TV-en starter innslaget som går nå, så langt inn som sendingen har kommet (offset). Innhopp mindre enn 2
sekunder etter starten regnes som fra start (joined: start), ellers joined: middle.
| Innslag | Kommer man inn midt i et innslag … |
|---|---|
| YouTube | … starter videoen på start + offset sekunder. |
| NRK opptak | … henter TV-en avspillingsadressen hos NRK og starter på start + offset, pluss tiden hentingen tok. |
| NRK direktekanal | … spilles direktesendingen som den er, uten å hoppe. |
| Lysbilder | … vises lysbildet som går nå, med lag, toning og zoom slik de er på det tidspunktet. Lydklipp som begynte mer enn 0,25 s tidligere, hoppes over, så en stemme aldri starter midt i en setning; senere klipp kommer på sin tid. Musikken starter på offset modulo musikkens lengde. Videolag hopper til riktig sted. |
TV-appen logger inn med brukernavn og boks-ID og starter på klokkeskjermen (plass 0). Den henter profilens kanaler og dagens spillelister fra serveren, og kanalvelgeren eier tiden: den finner innslaget som går, starter riktig avspiller midt i det, planlegger slutten og går videre. Avspillerne bytter aldri innslag selv.
| Avspiller | Hvordan |
|---|---|
| YouTube | YouTubes innebygde spiller i fullskjerm, uten knapper, forslag eller annen YouTube-visning. Miniatyrbildet vises bak spilleren mens videoen laster. |
| Lysbilder | TV-appens egen tegner. Den bygger skjermbildet av lagene i JSON-en på et 16:9-lerret med svarte kanter (kap. 6), og laster bildene til neste lysbilde i forkant. |
| NRK | Henter avspillingsadressen hos NRK rett før avspilling (kap. 5), og for direktekanaler strømmen til kanalen (kap. 9). Spiller HLS-strømmen i et <video>-element med hls.js, med undertekster fra subtitlesUrl. Går det ikke å spille, vises plassholderen (se under). |
sound.muted. Første tastetrykk slår på lyden i alle avspillere, også <video>, og gir sound.unlocked.remote.pressed (opp, ned eller annet), også under samtaler – men ikke gjentak når en tast holdes inne, eller trykk sammen med Alt, Ctrl eller Cmd.channel.left med reason: off, tv.started og channel.entered med via: startup på samme plass.TV-en og appene bruker egne, interne endepunkter som ikke er en del av leverandør-API-et (kap. 18).
Serveren tar aldri innslag ut av tidslinjen av seg selv. TV-en viser noe rolig – aldri en svart skjerm eller en
feilmelding – melder feilen som playback.error (kap. 13), og tidslinjen går videre som før. Feil som kan gå
over av seg selv, prøver TV-en igjen, på riktig sted i tidslinjen:
| Feil | TV-en … |
|---|---|
| YouTube nettverksfeil (YouTube kan ikke lastes, eller videoen står stille i 60 s) | … viser pausebildet med kanalnavn, klokke og «Neste kl. …» og avslutter innslaget med reason: error. Prøver igjen etter omtrent 30 sekunder, så sjeldnere, høyst hvert 5. minutt – og med en gang nettet er tilbake eller en samtale er over. |
| YouTube andre feil (fjernet, privat, ikke tillatt innbygging) | … som over, men prøver igjen etter 5 minutter, så sjeldnere, høyst hvert 30. minutt. Hvert nytt forsøk gir en ny item.started (joined: middle), og en ny item.ended med reason: error hvis det feiler igjen. |
| NRK program | … viser plassholderen med tittel, serie og episode, beskrivelse og programbilde («Programmet kan ikke vises akkurat nå»). Nettverksfeil prøves igjen i samme innslag (ingen ny item.started), etter omtrent 30 sekunder og så sjeldnere, og playback.error meldes én gang per brudd. media-missing og not-available står resten av innslaget. |
| NRK direktekanal | … viser «Sendingen kommer straks tilbake» og prøver igjen etter 15 sekunder, så sjeldnere, høyst hvert 5. minutt. |
| Lysbilder med en fil som mangler | … skjuler bildet eller hopper over lydklippet, og serien fortsetter. Hver fil meldes én gang per innslag som media-missing med src og slideId (musikken uten slideId). |
| Ukjent innslagstype | … (fra en nyere server enn TV-appen) viser pausebildet resten av innslaget og melder kind: unsupported. |
| Mediet slutter før tiden | … (videoen er kortere enn duration) viser pausebildet til innslagets tid er ute. Er mediet lengre, avbrytes det når neste innslag begynner. |
details i playback.error kan være stalled (avspillingen sto stille:
NRK 45 s, YouTube 60 s), «Får ikke kontakt med NRK (nett eller CORS): …», «NRK svarte ikke» (NRK svarte ikke innen 15 s),
feilkoden fra hls.js eller beskjeden fra NRK.
TV-en melder hva som skjer på skjermen: hvilke kanaler og innslag som ble vist, hvor lenge, når man byttet, og når en samtale avbrøt. Serveren melder selv samtaler, endringer i kanaloppsettet og når TV-en kommer på nett. Hendelsene hører til profilen, og alle lagres. Leverandøren henter dem på to måter:
| Kall | Gir |
|---|---|
GET /profiles/{ref}/history | Historikken for én profil: visninger (fra kanalbytte til kanalbytte) med innslagene som ble sett, hvor lenge, og avbrudd av samtaler. Det enkleste stedet å begynne. |
GET /events | Rå hendelser for alle egne profiler, i den rekkefølgen serveren tok imot dem, med en markør så du kan hente bare nye. |
receivedAt (mottatt) og profilens ref. Samme id to ganger for samme profil lagres bare én gang. at er etter serverens klokke; hendelser som ligger mer enn en time fram i tid, avvises.enkeltv-event/1| Felt | Beskrivelse |
|---|---|
id | Unik per hendelse (uuid), laget der hendelsen oppstod. 8–64 tegn |
type | Hendelsestypen, se tabellen |
at | Når det skjedde (ISO 8601), etter serverens klokke |
receivedAt | Når serveren tok imot hendelsen |
profile | Profilens pseudonym (p_…) |
viewId | Knytter sammen alt i én visning, fra channel.entered til channel.left |
slot, channelId | Kanalnummeret på profilen og kanalen i biblioteket. Klokkeskjermen er plass 0 med channelId: null |
playlistId, playlistVersion | Hvilken spilleliste og versjon som gikk |
itemId, kind | Innslaget og typen |
data | Felt som er spesielle for hendelsestypen |
Felt som ikke gjelder, er null. Serverens hendelser og tv.started, sound.unlocked og remote.pressed har ingen visningsfelt.
| Type | Når | data |
|---|---|---|
| Fra TV-en | ||
tv.started | TV-appen har logget inn | appVersion, screen (f.eks. "1920x1080") |
channel.entered | En plass stilles inn | fromSlot (null ved oppstart), via: startup · remote · lineup-changed · day-changed; source: profile · default · fallback · builtin · clock (plass 0) |
channel.left | Plassen forlates | seconds, toSlot (null ved off), reason: remote · lineup-changed · day-changed · off (utlogging, siden lukkes eller lastes på nytt) |
item.started | Et innslag begynner på skjermen | offsetSeconds (hvor langt inn), joined: start · middle, title, tags |
item.ended | Innslaget slutter eller forlates | watchedSeconds (uten pauser), itemSeconds, completed (spilt til slutt uten feil), reason: finished · channel-change · off · error · replaced |
slide.shown | Et lysbilde er vist ferdig | slideshowId, slideshowVersion, slideId, seconds, audioPlayed |
playback.paused | En samtale ringer mens en kanal vises (ikke på klokkeskjermen, plass 0) | reason: call |
playback.resumed | Samtalen er over og TV-en er tilbake på venteskjermen (vanligvis noen sekunder etter call.ended) | reason: call, pausedSeconds |
playback.started | Mediet spiller faktisk: første bilde etter lasting (foreløpig bare NRK). Kommer igjen når en direktekanal har startet på nytt etter et utfall | engine: hls.js · native-hls · file; live (sann/usann), startupMs (ms fra lasting til spilling), mediaSeconds (lengden på opptaket, ikke for direkte) |
playback.stalled | Avspillingen stoppet opp for å laste og fortsatte igjen (foreløpig bare NRK). Bare opphold på minst 1 s, høyst 20 per innslag | seconds, positionSeconds |
playback.error | Noe kunne ikke spilles | kind: media-missing · not-embeddable · unsupported · network · not-available (NRK sier at programmet ikke kan spilles nå, f.eks. utløpt eller geoblokkert); src, slideId og details (tekst ≤ 200 tegn, f.eks. feilkoden fra hls.js eller NRK) når de er kjent |
sound.muted | Nettleseren nektet lyd | – |
sound.unlocked | Første tastetrykk slo på lyden | – |
remote.pressed | Tastetrykk | key: up · down · other |
| Fra serveren | ||
tv.online | TV-en har kontakt med serveren igjen etter minst 10 s uten | offlineSeconds (null første gang) |
call.incoming | En pårørende ringer | callId |
call.answered | TV-en svarte | callId, mode: auto · manual |
call.ended | Samtalen er over | callId, status: completed · missed · cancelled · declined · failed · dropped; seconds (samtaletid) |
lineup.changed | Profilens kanaloppsett ble endret | by: provider · staff, slots (plassene som ble endret), channelCount |
Leverandører får aldri vite hvem som ringte – bare at og hvor lenge, fordi samtaler forklarer avbrudd.
Bilder fra «titt inn» er aldri hendelser. Det finnes ingen egen hendelse for at TV-en går av nett; hullet ses som
tv.online med offlineSeconds. Samtaler som er i gang, overlever en omstart av serveren; et anrop
som ringer akkurat da, blir missed uten call.ended.
tv.started har appVersion = de 12 første tegnene i versjonen av TV-appen, så du
ser når TV-en har fått ny programvare. Etter en omlasting (kap. 12) har channel.entered med
via: startup plassen TV-en sto på før, ikke 0.
Direktekanaler (kap. 9) sender de vanlige innslagshendelsene, med kind: "nrk",
itemId live.nrk1 (osv.) og playlistId og playlistVersion null.
item.started har title = kanalnavnet og tags: ["direkte"], og item.ended har
itemSeconds: null.
| Tid | Type | Utdrag |
|---|---|---|
| 17:10:02 | channel.entered | plass 6 «Ingrids kanal», via: remote, fromSlot: 5, source: profile |
| 17:10:03 | item.started | yt.bergensbanen, offsetSeconds: 600, joined: middle |
| 17:30:00 | item.ended | watchedSeconds: 1197, completed: true, reason: finished |
| 17:30:00 | item.started | lysbilder.bergen-1950, joined: start |
| 17:30:14 | slide.shown | torget, seconds: 14, audioPlayed: true |
| 17:30:14 | item.ended | watchedSeconds: 14, completed: true |
| 17:50:41 | call.incoming → playback.paused | samtalen ringer, alt fryses |
| 18:03:20 | call.ended → playback.resumed | pausedSeconds: 759 – fortsetter der det stoppet |
| 18:05:12 | channel.left | seconds: 3310, toSlot: 7, reason: remote |
{
"id": "5f0c9a7e-2b1d-4c43-9a51-7d0e3b6f2a18",
"type": "item.ended",
"at": "2026-10-05T15:30:14.204Z",
"receivedAt": "2026-10-05T15:31:02.517Z",
"profile": "p_7Kq2mPx9Qa",
"viewId": "0b6e3f4a-91c2-4d7e-8f55-2a9c1e7d4b03",
"slot": 6, "channelId": "enkeltv.ingrids-kanal",
"playlistId": "enkeltv.ingrid-kveld-uke41", "playlistVersion": 3,
"itemId": "lysbilder.bergen-1950", "kind": "slideshow",
"data": { "watchedSeconds": 14, "itemSeconds": 14, "completed": true, "reason": "finished" }
}
GET /profiles/{ref}/history?from=…&to=…from og to er ISO 8601-tidspunkter (standard: de siste 24 timene, høyst 31 dager per kall).
Hver visning går fra channel.entered til channel.left og har innslagene med sett tid
(watchedSeconds, uten pauser) og completed, og avbrudd av samtaler (interruptions).
{
"profile": "p_7Kq2mPx9Qa",
"from": "2026-10-05T00:00:00.000Z",
"to": "2026-10-06T00:00:00.000Z",
"views": [
{ "viewId": "0b6e3f4a-91c2-4d7e-8f55-2a9c1e7d4b03", "slot": 6,
"channelId": "enkeltv.ingrids-kanal",
"from": "2026-10-05T15:10:02.000Z", "to": "2026-10-05T16:05:12.000Z", "seconds": 3310,
"items": [
{ "itemId": "yt.bergensbanen", "kind": "youtube", "title": "Bergensbanen",
"watchedSeconds": 1197, "completed": true },
{ "itemId": "lysbilder.bergen-1950", "kind": "slideshow", "title": "Bergen på 1950-tallet",
"watchedSeconds": 14, "completed": true }
],
"interruptions": [ { "type": "call", "from": "2026-10-05T15:50:41.000Z", "seconds": 759 } ] }
]
}
to: null, og seconds teller bare til siste kontakt med boksen. Innslag som ennå ikke er ferdige i en slik visning, har watchedSeconds: 0.channel.left), slutter visningen ved siste kontakt med serveren: der hullet begynner før neste tv.online (med offlineSeconds), eller ved lastSeenAt hvis boksen fortsatt er av. Innslag uten item.ended får anslått sett tid fram dit.from, er med fra første hendelse i vinduet.GET /eventsParametre: since (ISO-tidspunkt: mottatt fra og med), after (markøren next fra
forrige svar), types (kommaseparert liste med hendelsestyper), profile (én ref),
limit (standard 500, høyst 5000). Hendelsene kommer i den rekkefølgen serveren tok imot dem. Lagre
next og send den som after neste gang – da mister du ingen og får ingen to ganger. Er
hasMore sann, er det flere å hente nå. Er det ingen nye, er next den samme som du sendte.
GET /api/publish/events?since=2026-10-05T00:00:00Z&types=item.ended,channel.left&limit=500
{ "events": [ … ], "next": "184213", "hasMore": false }
GET /api/publish/events?after=184213
{ "events": [ … ], "next": "184377", "hasMore": false }
Tidspunkter i adressen (since, from, to): bruk Z
(2026-10-05T04:00:00Z), eller kod + som %2B (2026-10-05T06:00:00%2B02:00).
Et ukodet + foran tidssonen, som blir til mellomrom, godtas også.
events:read ser leverandøren alle hendelser for sine egne profiler – også kanalbytter til andre leverandørers felles kanaler, samtaler og påslåing. Hendelsene følger profilen: får en profil ny leverandør, ser den nye leverandøren også eldre hendelser, og den forrige ser ingen.events:all ser man hendelser for alle profiler. Den rettigheten er for Enkel-TVs egen analyse.Godt innhold for en person med demens tar utgangspunkt i livet personen har levd. Leverandøren intervjuer de pårørende utenfor systemet. I systemet bygges profilen opp av personalet og familien, og leverandøren kan lese den – bare når eieren av boksen har samtykket.
| Del | Hvem | Hvor | Innhold |
|---|---|---|---|
| Bio | Personalet | Admin | Faste felt og en kort, nøytral livshistorie fra kartleggingen med de pårørende. Fanen «Bio» under «Innhold og profil» på TV-siden |
| Notater | Familien | Mobilappen | Korte opplysninger når noe har skjedd, f.eks. hvordan personen reagerte på et program. «Fortell om …» på boksen |
| Bilder | Familien | Mobilappen | Bilder med bildetekst, år og sted – f.eks. til personlige lysbildeserier |
| Samtykke | Eieren | Mobilappen | Hva leverandøren får se: bio, notater og bilder, hver for seg |
share)For hvert notat og bilde velger den som sender, hvem som kan bruke det. Skjermen viser en kort veiledning om å skrive nøytrale opplysninger, og bildene gjøres mindre før de sendes.
Skriv nøytrale opplysninger som kan hjelpe oss å finne innhold Ingrid liker – minner, interesser eller noe som har skjedd. Ikke skriv diagnoser, medisiner eller navn og opplysninger om andre.
share | Betyr |
|---|---|
provider | «Innholdsleverandøren (også deres KI)» – forhåndsvalgt i appen. Leverandøren får det når eieren har samtykket til kategorien (notater eller bilder). Til da ser bare personalet det |
staff | «Bare personalet». Aldri leverandøren, og aldri KI |
Bildet skaleres i telefonen til høyst 2048 piksler på den lengste siden og lagres som JPEG, så posisjon og
kamerainformasjon (EXIF) ikke blir med. Den som sendte et bidrag, og eieren, kan slette det i appen; personalet kan slette
alle bidrag i admin («Fra familien»). Et slettet bidrag forsvinner fra /notes og /photos for
leverandøren; et bilde som alt er brukt i en lysbildeserie, blir stående i serien til leverandøren fjerner det (kap. 8).
403 no-consent for den kategorien, uansett hva det enkelte bidraget er merket med.Toppledelsen velger leverandør på TV-siden i admin. Den forrige leverandøren mister med en gang tilgangen til profilens
kanaloppsett, hendelser og profildata (en nøkkel med events:all kan fortsatt lese kanaloppsett, «nå», hendelser og historikk, men ikke bio, notater og bilder). Den kan verken lage nye eller endre eksisterende personlige objekter for profilen, bare slette dem som ikke er i bruk. Personlige kanaler,
spillelister og serier den forrige leverandøren har laget, blir liggende og eies fortsatt av den; plasser i kanaloppsettet
som peker på dem, vises til noen endrer dem. En ny profil har ingen leverandør før toppledelsen velger en; til da er den
ikke med i GET /profiles, og alle kall for den gir 404 no-profile.
enkeltv-profile/1Alle felt er valgfrie. Personalet lagrer hele bioen på én gang i admin; ukjente felt fjernes og tomme lister utelates.
| Felt | Type | Eksempel og formål |
|---|---|---|
callName | tekst ≤ 60 | «Ingrid» – hva en fortellerstemme kan kalle personen. Bare fornavn eller kallenavn |
birthYear | 1900–2030 | 1938 – gir hvilke tiår som var barndom og ungdom |
language, dialect | tekst ≤ 10, ≤ 60 | nb og «bergensk» |
places | liste | «Bergen (vokste opp)», «Florø» |
work | liste | «sykepleier» |
interests | liste | «tog», «hagearbeid», «kaffe på trappa» |
music | liste | «gammeldans», «salmer», «allsang» |
avoid | liste | Tema som kan uroe: «krig», «sykehus». Leverandøren skal respektere dette |
adaptations | liste | large-text · slow-speech · subtitles · calm-pace – tilpasninger, ikke diagnoser |
summary | tekst ≤ 1000 | Kort, nøytral livshistorie. Ingen diagnoser, medisiner eller navn på andre |
Listene (places, work, interests, music, avoid) har høyst 30
oppføringer à 80 tegn, uten like oppføringer.
GET /api/publish/profiles/p_7Kq2mPx9Qa/bio
{
"format": "enkeltv-profile/1",
"profile": "p_7Kq2mPx9Qa",
"updatedAt": "2026-09-24T09:14:00.000Z",
"bio": {
"callName": "Ingrid", "birthYear": 1938, "language": "nb", "dialect": "bergensk",
"places": ["Bergen (vokste opp)", "Florø"], "work": ["sykepleier"],
"interests": ["tog", "hagearbeid", "kaffe på trappa"],
"music": ["gammeldans", "salmer", "allsang"], "avoid": ["krig", "sykehus"],
"adaptations": ["large-text", "slow-speech"],
"summary": "Vokste opp i Bergen og arbeidet i mange år som sykepleier i Florø. …"
}
}
GET /api/publish/profiles/p_7Kq2mPx9Qa/notes?after=0
{ "notes": [
{ "id": 1, "by": "family", "at": "2026-09-28T18:40:00.000Z",
"text": "Mamma ble oldemor forrige uke og snakker mye om babyen. …" }
] }
GET /api/publish/profiles/p_7Kq2mPx9Qa/photos
{ "photos": [
{ "id": 4, "caption": "Ingrid i hagen på Florø", "year": 1965, "place": "Florø",
"width": 2048, "height": 1365, "at": "2026-10-03T19:02:00.000Z",
"sha256": "d7a13d148eb05ea057024bef468d20e3987a8d0f91e1452050930c319dc6e6c3",
"url": "/media/d7a13d148eb05ea057024bef468d20e3987a8d0f91e1452050930c319dc6e6c3.jpg?e=…&s=…" }
] }
provider, i stigende id-rekkefølge. Lagre høyeste id og bruk den som after neste gang for å få bare nye notater. by er family eller staff – aldri et navn.provider. url er en signert adresse på serveren (kap. 8).Bare det at noen bruker en tjeneste for personer med demens, kan være en helseopplysning (GDPR art. 9). Seervaner, bio, notater og bilder er derfor sensitive, også når de er pseudonyme.
ref: p_ og 10 tilfeldige tegn. Koblingen til personen finnes bare på serveren. ref endres ikke når boksen byttes.Grunnadresse: https://<server>/api/publish, og nøkkelen sjekkes med
GET https://<server>/api/publish. Én skråstrek til slutt godtas på alle adresser
(/api/publish/ er det samme som /api/publish), men to gir 404. Alle kall krever API-nøkkel (kap. 3). Kropper er JSON: høyst
1 MB for kanaler, spillelister og lysbildeserier, og 512 KB for andre kall. Lysbildepakker (zip) kan være høyst 200 MB.
Dette er hele leverandør-API-et; alle feilkodene står i kap. 3.
| Kall | Rettighet | Gjør |
|---|---|---|
| Grunnleggende – adressene under er relative til grunnadressen | ||
GET /api/publish | – | Nøkkelen: leverandør, navn, rettigheter, formater, grenser og sendedøgn |
GET /profiles | – | Egne profiler (alle med events:all): ref, på/av, sist sett, antall kanaler, samtykke |
| Kanaloppsett | ||
GET /profiles/{ref}/lineup | lineups:write | Les kanaloppsettet (kap. 10) |
PUT /profiles/{ref}/lineup | lineups:write | Erstatt hele oppsettet (format og slots påkrevd) |
PATCH /profiles/{ref}/lineup | lineups:write | Endre enkeltplasser eller antallet |
GET /profiles/{ref}/now | lineups:write | Hva som går på hver plass akkurat nå |
GET /lineups/default | – | Standardoppsettet og reserveprogrammet |
PUT /lineups/default | lineups:default | Erstatt standardoppsettet |
Bibliotek – {type} er channels, playlists eller slideshows | ||
GET /{type} | – | Felles objekter og egne personlige, som sammendrag |
GET /{type}/{id} | – | Det lagrede dokumentet, med versjon, leverandør og tidspunkter |
PUT /{type}/{id} | library:write | Lag eller endre. slideshows tar application/zip eller application/json |
DELETE /{type}/{id} | library:write | Slett eget objekt som ikke er i bruk → { "ok": true } |
POST /channels/{id}/playlists | library:write | Publiser en spilleliste til egen kanal: { playlistId, from?, to? }, standard fra i dag og til en nyere kommer (kap. 9). Direktekanaler gir 409 live-channel |
GET /channels/{id}/schedule | – | Sendeplanen for et sendedøgn (?date=YYYY-MM-DD, standard i dag; kap. 11) |
POST /media/check | library:write | Hvilke av høyst 1000 filer serveren har (kap. 7) |
| Innsikt | ||
GET /events | events:read / all | Hendelser i den rekkefølgen serveren tok imot dem, bladd med markør (kap. 13) |
GET /profiles/{ref}/history | events:read / all | Historikken: visninger med innslag og avbrudd – lettere å lese enn rå hendelser (kap. 13) |
| Profil (bare egne profiler, med samtykke) | ||
GET /profiles/{ref}/bio | profiles:read | Bioen (kap. 14) |
GET /profiles/{ref}/notes | profiles:read | Notater merket provider; ?after=<id> gir bare nyere |
GET /profiles/{ref}/photos | photos:read | Familiebilder merket provider, med signerte adresser |
Lagring av kanaler, spillelister og lysbildeserier med PUT er idempotent: samme innhold to ganger gir samme versjon og "changed": false. Et kanaloppsett (PUT/PATCH, også standardoppsettet) uten endringer lagres ikke og gir ingen lineup.changed. Svaret er da oppsettet slik det var, uten changed.
GET /api/publish og GET /api/publish/profiles{
"provider": { "id": 1, "name": "Enkel-TV" },
"key": "Produksjon, oktober 2026",
"scopes": ["library:write", "lineups:write", "events:read"],
"formats": { "lineup": "enkeltv-lineup/1", "channel": "enkeltv-channel/1",
"playlist": "enkeltv-playlist/1", "slideshow": "enkeltv-slideshow/1",
"event": "enkeltv-event/1", "profile": "enkeltv-profile/1" },
"limits": { "jsonBytes": 1048576, "slideshowBytes": 209715200 },
"broadcastDay": { "startsAt": "06:00", "timeZone": "Europe/Oslo" }
}
[
{ "ref": "p_7Kq2mPx9Qa", "online": true, "lastSeenAt": "2026-10-05T16:52:01.000Z",
"channelCount": 15, "consent": { "bio": true, "notes": true, "photos": true } },
{ "ref": "p_R2v9xTq4Lm", "online": false, "lastSeenAt": "2026-10-04T19:10:44.000Z",
"channelCount": 13, "consent": { "bio": false, "notes": false, "photos": false } }
]
online betyr at TV-en har hatt kontakt med serveren de siste 10 sekundene, og lastSeenAt er siste kontakt. Biblioteklistene gir sammendrag med
id, visibility, profile (bare for egne personlige), version, provider,
mine og updatedAt, pluss name, description, color, tags og
type (loop eller live) (kanaler), title og antall items (spillelister), eller title, duration,
slides og bytes (serier).
GET /profiles/{ref}/now{
"profile": "p_7Kq2mPx9Qa",
"at": "2026-10-05T15:31:20.000Z",
"showing": { "slot": 6, "since": "2026-10-05T15:10:02.000Z" },
"slots": [
{ "slot": 1, "channelId": "nrk1", "name": "NRK1", "source": "default",
"live": { "nrkChannel": "nrk1" },
"kind": null, "itemId": null, "title": null, "startedAt": null, "endsAt": null },
{ "slot": 4, "channelId": "sporet", "name": "Sporet", "source": "default", "live": null,
"kind": "youtube", "itemId": "yt.mv47Hexb4jY", "title": "Nordlandsbanen om vinteren",
"startedAt": "2026-10-05T14:05:51.000Z", "endsAt": "2026-10-05T16:20:12.000Z" },
{ "slot": 6, "channelId": "enkeltv.ingrids-kanal", "name": "Ingrids kanal", "source": "profile",
"live": null, "kind": "youtube", "itemId": "yt.ZlGyCfigmFg", "title": "Allsang: Liten fuggel",
"startedAt": "2026-10-05T15:30:14.000Z", "endsAt": "2026-10-05T15:33:42.000Z" }
]
}
Alle plasser er med. «Nå» er direkte – uten forsinkelse etter samtaler – og endsAt er aldri
etter sendedøgnets slutt. For en direktekanal er live { "nrkChannel": … } og kind,
itemId, title, startedAt og endsAt null; ellers er
live null. showing (plassen som vises nå) er bare med når nøkkelen også har
events:read eller events:all og TV-en er på; ellers null.
GET /channels/{id}/schedule?date=2026-10-05{
"channelId": "enkeltv.ingrids-kanal",
"date": "2026-10-05",
"start": "2026-10-05T04:00:00.000Z",
"end": "2026-10-06T04:00:00.000Z",
"live": null,
"playlistId": "enkeltv.ingrid-kveld-uke41",
"playlistVersion": 1,
"entries": [
{ "index": 0, "itemId": "yt.bergensbanen", "kind": "youtube", "title": "Bergensbanen",
"duration": 1800,
"startsAt": "2026-10-05T04:00:00.000Z", "endsAt": "2026-10-05T04:30:00.000Z" },
{ "index": 1, "itemId": "lysbilder.bergen-1950", "kind": "slideshow",
"title": "Bergen på 1950-tallet", "duration": 14,
"startsAt": "2026-10-05T04:30:00.000Z", "endsAt": "2026-10-05T04:30:14.000Z" },
…
],
"truncated": false
}
Alle løkkerunder i sendedøgnet, høyst 5000 oppføringer (truncated: true hvis det er flere).
index er plassen i spillelisten. Det siste innslaget får endsAt = end, siden det avbrytes
når sendedøgnet slutter. Har dagens spilleliste ingen innslag som er tilgjengelige den dagen, er entries tom,
men playlistId er satt. Har kanalen ingen spilleliste den dagen, er entries tom og
playlistId: null. For en direktekanal er live { "nrkChannel": "nrk1" } (osv.),
entries tom og playlistId: null; ellers er live null.
DELETE /api/publish/playlists/enkeltv.allsang
HTTP/1.1 409 Conflict
{ "error": "Spillelisten «enkeltv.allsang» er i bruk og kan ikke slettes (channel:allsang)",
"code": "in-use", "usedBy": ["channel:allsang"] }
Ingrid (profil p_7Kq2mPx9Qa i eksempelet) har leverandøren Enkel-TV. Hun har allerede den personlige
kanalen enkeltv.ingrids-kanal på plass 6, med spillelisten enkeltv.ingrid-kveld. Slik lager
leverandøren ukens spilleliste og forbedrer den, bare med API-et.
| # | Steg | Kall |
|---|---|---|
| 1 | Finn profilene og se hva eieren har samtykket til | GET /profiles |
| 2 | Les bioen og notatene: Bergen, tog og gammeldans, unngå krig og sykehus, og et notat om at hun blir glad av gamle familiebilder | GET /profiles/p_7Kq2mPx9Qa/bio, /notes |
| 3 | Lag en personlig lysbildeserie med gamle Bergen-bilder og rolig opplesning, og last den opp som pakke | PUT /slideshows/enkeltv.ingrid-bergen-1950 |
| 4 | Lag ukens spilleliste: en halvtime av Bergensbanen, lysbildeserien, allsang og en episode av Fleksnes (kap. 5) | PUT /playlists/enkeltv.ingrid-kveld-uke41 |
| 5 | Publiser spillelisten til kanalen for uke 41: { "playlistId": "enkeltv.ingrid-kveld-uke41", "from": "2026-10-05", "to": "2026-10-11" } (kap. 9) | POST /channels/enkeltv.ingrids-kanal/playlists |
| 6 | Sjekk sendeplanen for mandag | GET /channels/…/schedule?date=2026-10-05 |
| 7 | Sørg for at kanalen ligger på plass 6 | PATCH /profiles/p_7Kq2mPx9Qa/lineup |
| 8 | Mandag kl. 06:00 begynner spillelisten for uke 41 med første innslag. Ingrids TV har hentet den nye versjonen innen et par sekunder etter lagringen | – |
| 9 | Dagen etter: se hva hun så. Lysbildene ble sett til slutt; Fleksnes ble forlatt etter tre minutter | GET /profiles/p_7Kq2mPx9Qa/history |
| 10 | Hent nye notater fra familien siden sist | GET /profiles/p_7Kq2mPx9Qa/notes?after=1 |
| 11 | Lag neste ukes spilleliste med mer allsang og uten Fleksnes, og publiser den til kanalen fra mandag 12. oktober | PUT /playlists/…-uke42, POST /channels/…/playlists |
Manifestet i pakken fra steg 3 er personlig for Ingrid. En personlig serie er den eneste typen som kan bruke
familiebildene hennes ("src": "sha256:…" fra GET /profiles/{ref}/photos, kap. 14; reglene står i kap. 8):
{
"format": "enkeltv-slideshow/1",
"title": "Bergen på 1950-tallet – for Ingrid",
"language": "nb",
"visibility": "personal",
"profile": "p_7Kq2mPx9Qa",
"defaults": { "text": { "style": { "size": 6, "background": "#00000099", "padding": 1.2 } } },
"slides": [
{ "id": "torget", "duration": 14,
"layers": [
{ "type": "image", "src": "media/torget-1954.jpg",
"motion": { "from": { "scale": 1 }, "to": { "scale": 1.12, "x": 42, "y": 55 } } },
{ "type": "text", "text": "Du vokste opp i Bergen, Ingrid.", "x": 5, "y": 78, "w": 90,
"align": "center", "start": 1, "fadeIn": 1 }
],
"audio": [ { "src": "media/tale/torget.mp3", "start": 1.5, "duration": 9.8 } ],
"credit": "Mittet & Co. / Nasjonalbiblioteket, 1954" }
]
}
curl -X PUT https://<server>/api/publish/slideshows/enkeltv.ingrid-bergen-1950 \
-H "Authorization: Bearer etv_…" -H "Content-Type: application/zip" \
--data-binary @enkeltv.ingrid-bergen-1950.zip
curl -X PUT https://<server>/api/publish/playlists/enkeltv.ingrid-kveld-uke41 \
-H "Authorization: Bearer etv_…" -H "Content-Type: application/json" \
--data-binary @enkeltv.ingrid-kveld-uke41.json
curl -X PATCH https://<server>/api/publish/profiles/p_7Kq2mPx9Qa/lineup \
-H "Authorization: Bearer etv_…" -H "Content-Type: application/json" \
-d '{ "slots": { "6": "enkeltv.ingrids-kanal" } }'
Leverandøren trenger aldri å vite hvem Ingrid er. Den ser bare p_7Kq2mPx9Qa, det eieren har samtykket til, og
hva som ble sett – og serveren trenger aldri å vite hvordan innholdet ble valgt.
Løftet: Feltene, formatene og feilkodene i dette dokumentet endres ikke. Nye, valgfrie felt,
nye hendelsestyper og nye feilkoder for nye situasjoner kan komme. Skriv klienten slik at den tåler felt og
hendelsestyper den ikke kjenner, og ser på HTTP-status og code – ikke på teksten i error.
enkeltv-playlist/1, og serveren godtar bare navnene i dette dokumentet. GET /api/publish viser alltid formatene serveren bruker./2). Da tar serveren imot både den gamle og den nye versjonen i en overgangsperiode, og leverandørene får beskjed i god tid./api/publish er leverandør-API-et. TV-en, mobilappen for pårørende og admin for personalet bruker egne, interne endepunkter med innlogging. De er ikke en del av leverandør-API-et, kan endres sammen med appene og godtar ikke API-nøkler.Dokumentasjonen ligger på serveren, alltid i samme versjon som API-et: som nettside på https://<server>/docs/
og som PDF på https://<server>/docs/enkeltv-api.pdf.
Dette er bevisst ikke bygget ennå. Modellen er laget slik at det kan komme til uten at formatene over må endres.
| Hva | Status i dag |
|---|---|
| Daglige sendeplaner | "programming": { "type": "schedule" } med faste klokkeslett avvises med 400. Kanaler bruker loop med én spilleliste per sendedøgn, eller live for NRK direkte. |
| NRK-avtale | Avspillingen bruker NRK sitt uoffisielle API (PSAPI), som ikke er dokumentert for tredjeparter og kan endres uten varsel. Avtale med NRK mangler fortsatt og må på plass før NRK-innhold brukes i et produkt vi tar betalt for. |
| Familien velger kanaler | Kanaloppsettet endres av leverandøren og av personalet i admin, ikke i mobilappen. |
| Grenser for antall kall | API-et har ingen slike grenser i dag. Kommer det, blir det 429 med en egen code. |
| Varsler (webhooks) | Serveren varsler ikke leverandøren. Hent nye hendelser med GET /events?after=… så ofte det trengs. |