På eteren: Norwegian internet radio and podcasts for the Nokia 8110 4G running KaiOS 2.5 / GerdaOS.
Eter is a small packaged KaiOS app built for a 240x320 screen and a keypad. It plays NRK, the P4 group, Bauer's stations and a few others, plus any podcast, and keeps playing with the screen off. It opens on a start screen: 1 Radio or 2 Podcasts (your last choice is preselected).
- 32 Norwegian stations, all plain MP3 over HTTPS (no HLS or DRM, which this Gecko 48 engine can't handle)
- Favourites: star any station, and it shows up at the top of the list on number keys 1-9
- Background playback on the
contentaudio channel, so the screen can turn off - Automatic reconnect with backoff when 4G drops, switching to a fallback stream URL where there is one
- In-app volume on Left/Right with its own bar in the bottom panel, remembered between launches
- Remembers your favourites and the last station you played
- Top in Norway: Apple's live podcast chart for Norway (Forklart, Spårtsklubben, AVHØRT, ...)
- Search any podcast by name, and follow shows. Followed shows come first, with their latest episode.
- Episodes show date, length and where you are:
47 min left,Played,Downloaded,Downloading 34% - Resumes where you stopped (saved every 10 seconds, on pause and on leaving), and marks an episode played near its end
- Skip back 15 s / ahead 30 s, pause, and speed 1x / 1.25x / 1.5x / 2x, from any podcast screen
- Downloads for offline listening, to the phone's storage. See Downloads.
- The now bar shows the position (
10:30 / 57:03) with a progress line
Start screen: Up / Down and Enter, or 1 Radio / 2 Podcasts. SoftLeft stops playback, Back closes the app. In Radio and Podcasts, Back goes up a level, and back to the start screen from the top.
Radio
| Key | Action |
|---|---|
| Up / Down | Move through the list |
| Enter | Play the selected station, or stop it if it's already playing |
| 1-9 | Play favourite 1-9 |
| SoftRight | Add or remove the selected station as a favourite |
| SoftLeft | Stop |
| Left / Right | Volume down / up (in-app, 10% steps) |
| 0 | Jump to the top of the list |
Podcasts
| Key | Action |
|---|---|
| Up / Down, Enter | Pick a show, Top in Norway, Search or Downloads; on an episode, play / pause it |
| SoftRight | On a show: follow / unfollow. On an episode: download, cancel, resume or delete the download |
| SoftLeft | In a show: follow / unfollow it |
| 5 | Pause / resume the playing episode |
| 4 / 6 | Back 15 s / ahead 30 s |
* |
Speed: 1x, 1.25x, 1.5x, 2x (remembered) |
# |
Mark the selected episode as played / not played |
| Left / Right | Volume |
| Search: type, then Enter | Search (T9 works); Down / Up and Enter to open a result, SoftRight to follow it |
To keep listening while using the phone, press the red End key. The app stays open in the background.
Default favourites: NRK P1, NRK P3, P5 Hits, Radio Rock, P4 Norge, NRK mP3.
| Group | Stations |
|---|---|
| NRK | P1, P1+, P2, P3, mP3, P13, P3 Musikk, Nyheter, Sport, Klassisk, Jazz, Folkemusikk, Super, Sápmi |
| P4-gruppen | P4 Norge, P5 Hits, P6 Rock, P7 Klem, P8 Pop, P9 Retro, P10 Country, P11 Dance, P12 Hitmix |
| Bauer | Radio Rock, Radio Norge, NRJ Norge, Topp 40, P24-7 Mix, Norsk Pop, Radio Vinyl |
| Other | Radio Nova, Radio Tromsø Hits |
The list is in app/js/stations.js. Stream URLs were found with
Radio Browser and checked to return audio/mpeg with valid MP3
frames (October 2026). Broadcasters do move streams around. If one stops working, update its url
(and optionally its alt fallback).
- Top in Norway:
itunes.apple.com/no/rss/toppodcasts(the chart's ids), then oneitunes.apple.com/lookupcall for their RSS feed URLs. Cached for 12 hours. Apple's newer chart endpoint (rss.marketingtools.apple.com) answered 502 when this was built. - Search:
itunes.apple.com/searchin the Norwegian store. No API key is needed for either. - Episodes: each show's own RSS feed, cached for 30 minutes (and shown from the cache when offline).
Feeds are scanned with regular expressions instead of
DOMParser, so the parser runs under node tests and stops after the newest 50 episodes. Forklart's feed is 1.6 MB with 600 episodes.
Requests use systemXHR, because most podcast hosts don't send CORS headers, and they identify as
eter/1.1 (podcasts; https://github.com/hanzhe/eter).
The hosts checked (NRK, Apple, Acast, Omny, Buzzsprout, Libsyn, Megaphone, Podtrac, Simplecast, SoundCloud, Smartpod, Schibsted's feeds) all chain to roots in the phone's 2016 built-in list, except Let's Encrypt's ISRG Root X1 (anchor.fm, Schibsted). That root was already added to this GerdaOS phone's certificate database, so those work here. A phone without it can't load those feeds; see Kuling's notes for how a root is added.
Episodes are 80-200 MB (NRK publishes at 192 kbit/s), too big to fetch in one request on a phone with
512 MB of RAM. So downloads.js fetches 2 MB HTTP Range requests and appends each to the file with
DeviceStorage.appendNamed:
- Files go to
eter/<episode id>.mp3in thesdcardstorage. On a phone without a card that is internal storage (2.3 GB free on this one). - Free space is checked first, with 20 MB to spare.
- After the first chunk it asks the redirected URL directly, which saves a hop per chunk. On errors it goes back to the feed's URL, since redirect targets are often signed and short-lived. It retries 3 times, then stops; SoftRight resumes.
- An interrupted download, for example when the app is closed, carries on from the file's size the next time Eter starts.
- One download at a time, while Eter is open (in front or in the background), with a CPU wake lock.
- A server that ignores Range requests can still deliver files up to 4 MB in one piece. Bigger ones are refused with "Server does not allow downloads in parts".
- Nokia 8110 4G (or another KaiOS 2.5 phone) with root adb. Sideloading writes to
/data/local/webappsand the permissions database. adb, Python 3, Node 18+ (tests only),zip
npm test # unit tests
npm run build # -> dist/application.zip (+ icons)
npm run install-phone # push, register, grant permissions, restart the phone UI
npm run uninstall-phone # remove it againinstall.py reads the app id and type from app/manifest.webapp. It backs up webapps.json and
permissions.sqlite on the phone (*.bak-<timestamp>) before changing them. Then it restarts b2g,
which reloads the phone UI in about 20-30 seconds.
Declaring a permission in manifest.webapp grants nothing on its own. Gecko normally writes grants
into /data/local/permissions.sqlite at install time, and a hand-registered app skips that step. So
the script inserts the rows while b2g is stopped. Eter needs:
audio-channel-content: background playbacksystemXHR: podcast feeds and downloads from any hostdevice-storage:sdcardwithreadwriteaccess: downloaded episodes. Gecko stores this as three rows,device-storage:sdcard-read,-writeand-create, which is how the phone's own apps have it.install.pyexpands device-storage access levels this way and still refuses other access-level permissions rather than guess at them.
In-app volume scales within the phone's system volume, so 100% in Eter is as loud as the system volume allows. Set the system volume high once and adjust in the app.
app/
manifest.webapp
index.html
style/app.css
js/model.js pure: favourites, list rows, selection, retry timing, volume
js/stations.js the station list
js/feed.js pure: RSS feeds, iTunes chart/search answers, episode ids, time/size text
js/library.js pure: followed shows, listening progress
js/net.js XHR (mozSystem) for text, JSON and byte ranges
js/downloads.js chunked downloads to DeviceStorage (storage and network injected, so it's tested)
js/poddata.js feeds, chart and search, with caching
js/player.js <audio> on the content channel, wake lock, reconnect, pause/seek/speed for episodes
js/ui.js rendering shared by all screens
js/radio.js the radio screen
js/podcasts.js the podcast screens
js/main.js start screen, now bar, volume, keys
icons/ generated by scripts/make_icons.py
scripts/ build, icons, install, uninstall
tests/ node --test; podcast tests use real feeds and iTunes answers in tests/fixtures/
All app code is ES5 (no let/const, arrow functions or async), because the phone runs Gecko 48.
- No "now playing" song titles yet. NRK publishes them, so that's the natural next feature.
- Radio streams use data: a 128 kbit/s stream is about 58 MB per hour.
- Podcasts are audio only. Video episodes are skipped.
- Downloads run while Eter is open. Closing the app pauses them until it is opened again.
- No episode descriptions or show notes, to keep the 240x320 screen for the list.
- NRK's own podcast feeds list only their latest few episodes; the NRK Radio app has the full archive.
MIT