A (mobile) Firefox extension to extract streaming media URLs (podcasts, radio stations, live streams) and send to HTTP API endpoint(s).
Platform: Works on desktop Firefox and mobile Firefox Nightly
- 🔍 Page Stream Detection - Detects HLS, DASH, MP3, AAC, OGG, RTMP, RTSP, Icecast, Shoutcast from the current page.
- 📡 Configurable Endpoints - Define multiple API endpoints with template placeholders, export & import bluprints from files or sites
- 🌐 Two API call Modes - "Open in Tab" (GET via navigation or POST/PUT/DELETE via form submission - bypasses CORS), "Call API" (
fetchHTTP request with full control: custom headers, body templates, programmatic response handling) - 📋 Copy URLs - Quick copy stream URLs to clipboard.
- 🔔 Badge Notifications - Shows number of detected streams on the extension icon.
- Mobile Firefox Nightly Support - Extension works also on mobile Firefox Nightly.
- Clone or download this repository (folder slug:
stream-call) - Install deps (TypeScript build):
npm install
npm run build- Open Firefox and navigate to
about:debugging#/runtime/this-firefox - Click "Load Temporary Add-on"
- Navigate to the extension folder and select the
manifest.jsonfile (expects built assets indist/) - Generate icons by opening
icons/generate-icons.htmlin a browser and downloading them (only needed if you change the icon)
Build and package (includes dist + manifest + icons):
npm run build
zip -r stream-call.zip manifest.json dist icons -x "icons/generate-icons.html"Then submit to Firefox Add-ons.
- Click the Stream call icon in your Firefox toolbar
- Click the "⚙️ Options" button
- Define one or more API endpoints as a JSON array
- Click "💾 Save Settings"
- Click "🧪 Test API" to verify the connection
- id (required): Unique identifier
- name (required): Display name shown in popup
- endpointTemplate (required): API URL (supports placeholders)
- method (optional): HTTP method (defaults to POST)
- headers (optional): Custom headers object
- bodyTemplate (optional): Request body template (supports placeholders)
- includePageInfo (optional): Include page URL/title in context (defaults to false)
The payload sent to your API endpoint depends on your endpoint configuration:
GET&HEADrequests lack body.- If you use
bodyTemplate, the extension sends that template with placeholders replaced. - If you omit
bodyTemplatethe request an empty body is sent (exceptGET/HEAD).
{{streamUrl}}- The detected stream URL{{pageUrl}}- The webpage URL where the stream was found{{pageTitle}}- The webpage title{{timestamp}}- Current timestamp in ISO format
Placeholders are case-insensitive and support 2 jinja-like filters eg. {{streamUrl | url }}:
url- URL-encoded stream URLjson- JSON-encoded value
- Navigate to any website with streaming media (e.g., online radio, podcast player).
- The extension will automatically detect streams and on its icon a badge will appear showing the number of detected streams.
- Click the extension icon to view all detected streams.
- Select the desired API Endpoint (pre-configured above).
- Choose action:
- 📤 Call API - Send HTTP request (fetch) with full control over headers/body. Receives programmatic response, shows success/error in log.
- 🌐 Open in Tab - Open URL in new browser tab. GET/HEAD uses simple navigation, POST/PUT/DELETE uses form submission to bypass CORS and sen. Useful for HTML-returning services where you want to see the rendered page.
- HLS - HTTP Live Streaming (.m3u8)
- DASH - Dynamic Adaptive Streaming over HTTP (.mpd)
- HTTP Audio - Direct audio files (MP3, AAC, OGG, FLAC, WAV, M4A, WMA)
- RTMP - Real-Time Messaging Protocol
- RTSP - Real-Time Streaming Protocol
- Icecast/Shoutcast - Internet radio streaming protocols
- Playlist formats - M3U, PLS, ASX
stream-call/
├── manifest.json # Extension manifest
├── tsconfig.json # TypeScript configuration
├── package.json # Node.js dependencies & scripts
├── README.md # This file
├── CHANGES.md # Version history
├── LICENSE.txt # GPL-3.0 license
├── MOBILE_TESTING.md # Mobile Firefox testing guide
├── popup-pane.html # Popup UI
├── options-pane.html # Options page UI
├── hover-pane.html # In-page overlay UI
├── src/ # TypeScript sources
│ ├── broker.ts # Broker service worker (utility)
│ ├── page.ts # Content script for stream detection (utility)
│ ├── popup-pane.ts # Popup pane logic
│ ├── options-pane.ts # Options pane logic
│ ├── hover-pane.ts # Hover pane logic
│ ├── endpoint.ts # Endpoint config & API calling (utility)
│ ├── detect.ts # Stream detection patterns (utility)
│ ├── logger.ts # Unified Logger class (utility)
│ ├── types.ts # Type definitions (utility)
│ ├── components-ui.ts # Shared UI component builders (UI)
│ ├── logger-ui.ts # Logger UI renderers (UI)
│ ├── debounce.ts # Debounce utility
│ └── COMMIT_MSG.md # Commit message template
├── dist/ # Build output (generated by tsc + copy)
├── icons/ # Extension icons
│ ├── icon-16.png
│ ├── icon-32.png
│ ├── icon-48.png
│ ├── icon-128.png
│ └── generate-icons.html # Icon generator (not in package)
├── tests/ # Test suite
│ ├── unit/ # Unit tests (node --test + tsx)
│ └── integration/ # Integration tests (web-ext)
├── notes/ # Design & planning docs
└── .github/
└── copilot-instructions.md
- Install deps and run the TypeScript tests:
npm run install npm run test - Covers placeholder interpolation, missing-key handling, and
url/jsonfilters.
- Open
about:debugging#/runtime/this-firefox - Load the extension
- Visit test sites like:
- BBC iPlayer Radio
- TuneIn Radio
- Any podcast website
- YouTube (some streams may be detected)
The extension architecture revolves around six core concepts that work together in a message-driven flow:
| Concept | 🎯 What | 📍 Where | 🔧 Purpose |
|---|---|---|---|
| Detection Patterns | Regex for stream URLs | STREAM_PATTERNS in detect.ts |
• Match streaming media URLs • Built-in, not user-configurable • Tested via content.test.ts |
| Streams | Detected URLs + metadata + classification | StreamInfo in broker.ts |
• Store detected media per tab • Typed as HLS, DASH, MP3, RTMP, etc. • Include page context + timestamp |
| API Endpoints | User-configured HTTP targets | storage.sync.apiEndpoints, config.ts |
• Webhooks/APIs for detected streams • Support templating • Fully customizable |
| Interpolation Templates | Placeholder strings | Endpoint/body templates | • Dynamic value insertion • {{streamUrl}}, {{pageUrl}}, {{pageTitle}}, {{timestamp}} |
| Execution Contexts | Isolated JavaScript environments | Page context vs Extension context | • Content script runs in page context • Broker/popup run in extension context • Cannot share variables/functions • Communication only via messages |
| Runtime Messages | Cross-component IPC | RuntimeMessage type |
• STREAM_DETECTED (page→bg), GET_STREAMS (popup→bg)• CALL_API (hover→bg), OPEN_IN_TAB (hover→bg), PING |
- What: Regular expression patterns (
STREAM_PATTERNS) that match known streaming media URLs - Where: Defined in
src/detect.ts(separate frompage.tsfor modularity, reuse, and stateless testability) - Purpose: Page script uses them to identify stream URLs (HLS, DASH, MP3, RTMP, Icecast, etc.)
- Examples:
/\.(m3u8|mpd)/i,/rtmp:/,/icecast|shoutcast/i - Not configurable by users — built-in to the extension
- Testing: Validated via
tests/unit/content.test.ts(detection patterns and stream type classification)
- What: Detected media URLs with metadata (
StreamInfo) and classification labels - Where:
StreamInfotype insrc/broker.ts;getStreamType()insrc/detect.ts - Purpose: Store detected streaming resources with type (HLS, DASH, HTTP Audio, RTMP, Icecast/Shoutcast), page context, and timestamp
- Examples:
{ url: "https://example.com/live.m3u8", type: "HLS", pageUrl: "...", timestamp: 1234567890 }
- What: HTTP targets where detected stream URLs are sent
- Where: Configured in options page, stored as JSON in
browser.storage.sync.apiEndpoints - Structure: Name, URL template, HTTP method, headers, optional body template
- Purpose: Each endpoint is a webhook/API target the user defines (e.g., their own webhook, httpbin for testing)
- Examples:
{ "name": "My API", "endpointTemplate": "https://api.example.com/stream", "method": "POST", "bodyTemplate": "{\"url\":\"{{streamUrl}}\",\"timestamp\":\"{{timestamp}}\"}" } - Fully customizable by users in the options page
- What: Strings in
endpointTemplateandbodyTemplatethat contain placeholders like{{streamUrl}} - Where: Defined as endpoint field values; processed by
src/template.ts - Purpose: Allow dynamic values (stream URL, page title, timestamp) to be inserted at API call time
- Available placeholders:
{{streamUrl}},{{pageUrl}},{{pageTitle}},{{timestamp}} - Error handling: "Interpolation error" occurs when a placeholder is undefined or malformed
- Examples:
- Endpoint template:
https://api.example.com/notify?url={{streamUrl}} - Body template:
{"stream":"{{streamUrl}}","detected":"{{timestamp}}"}
- Endpoint template:
- What: Isolated JavaScript environments where extension code runs
- Two contexts:
- Page Context (
page.ts): Runs inside the webpage DOM, has access to page resources (images, media, scripts) but isolated memory from extension - Extension Context (
broker.ts,popup.ts,options.ts): Runs in browser's extension sandbox, has access tobrowser.*APIs, storage, and network requests
- Page Context (
- Why it matters: Content scripts cannot directly call functions in broker/popup or access their variables — they are in separate JavaScript worlds
- Root cause of messages: This isolation is why
browser.runtime.sendMessage()exists — it's the only way to pass data between contexts - Security benefit: Page scripts cannot access extension internals (API keys, stored endpoints, etc.)
- Common pitfall: Trying to
importshared utilities in both contexts requires careful module design (e.g.,detect.tsexports pure functions usable in both)
- What: Cross-component communication protocol via
browser.runtime.sendMessage() - Where:
RuntimeMessagetype insrc/broker.ts - Purpose: Message-passing between page script (page context), broker worker, and popup (extension context)
- Message Types:
STREAM_DETECTED(content → broker): Reports newly detected stream URL with typeGET_STREAMS(popup → broker): Requests all streams for current tabCALL_API(hover-panel → broker): Triggers API call with stream data (popup/options call directly)PING(popup → broker): Health check to verify broker worker is alive
The extension uses a message-driven architecture via browser.runtime.sendMessage():
PAGE CONTEXT (webpage) EXTENSION CONTEXT (browser)
┌────────────────────────────────────┐ ┌────────────────────────────────────┐
│ │ │ │
│ ┌─────────────┐ STREAM_DETECTED │ │ ┌────────────────┐ GET_STREAMS │
│ │ Content ├───────────────────┼──┼─>│ Broker │<──────────┐ │
│ │ Script │ │ │ │ (broker.ts) │ │ │
│ │ (page.ts) │ │ │ │ endpoint.ts) │ │ │
│ └─────────────┘ │ │ └────────┬───────┘ │ │
│ ▲ │ │ │ │ │
│ │ Detects streams via │ │ │ Stores streams │ │
│ │ STREAM_PATTERNS │ │ │ per tab (max 200) │ │
│ │ (detect.ts) │ │ │ │ │
│ ┌────┴─────┐ │ │ │ CALL_API │ │
│ │ Webpage │ │ │ │ (user triggered) │ │
│ │ DOM │ │ │ ▼ │ │
│ └──────────┘ │ │ ┌─────────────────┐ ┌──────┴──┐ │
│ │ │ │ API Endpoint │ │ Popup │ │
│ │ │ │ (user-configured│ │ (popup │ │
│ │ │ │ via options) │ │ .ts) │ │
│ │ │ └─────────────────┘ └─────────┘ │
└────────────────────────────────────┘ └────────────────────────────────────┘
Isolated from extension APIs Has browser.* API access
Can access page DOM/resources Cannot access page DOM directly
STREAM_DETECTED(content → broker): Reports a newly detected stream URL with its typeGET_STREAMS(popup → broker): Requests all streams for the current tabCALL_API(hover-panel → broker): Triggers API call from page context (popup/options callcallEndpointAPI()directly)OPEN_IN_TAB(hover-panel → broker): Opens endpoint in new tab from page context (popup/options callopenEndpointInTab()directly)PING(popup → broker): Health check to verify broker worker is alive
Logger provides audit trail (ring buffer) + UI status (slot-based):
API: logger.error(slot, msg), logger.infoFlash(timeout, slot, msg)
~65 total logging calls across all components
1 legacy console call (WIP hover-panel.ts stub)
| Category | Occurrences | Purpose |
|---|---|---|
| endpoint | 19 | Endpoint operations (config/validation/save/delete/tab-open/api-call) |
| apicall | 10 | API call operations (HTTP requests/responses/testing) |
| storage | 9 | Storage operations (load/save/reset/export/import/initialization) |
| popup | 7 | Popup component operations (initialization/refresh/UI actions) |
| page | 6 | Page script operations (stream detection/player detection/UI injection) |
| broker | 6 | Broker worker operations (stream management/tab lifecycle/initialization) |
| messaging | 5 | Cross-component message passing (page↔broker via browser.runtime.sendMessage) |
| stat | 3 | General status/progress messages |
| interpolation | 2 | Template placeholder interpolation |
| clipboard | 2 | Clipboard copy operations |
Stream call:
- Only sends data to your configured API endpoint
- Does not track browsing history, neither collect or transmit data to 3rd parties
- Stores configuration locally in Firefox sync storage
This extension is my scratch reply for the itch of by Chromecast not working in my home's WiiM Ultra music system, coupled with my curiosity for Claude's vibe coding, TypoScript, and my liberation talk.
Claude's inspiration list:
- stream-recorder - For stream detection techniques
- stream-bypass - For handling various streaming protocols
The extension requires the following permissions:
storage- To save API configurationactiveTab- To access the current tab informationwebRequest- To monitor network requests for streams<all_urls>- To detect streams on any website
GPLv3 License - See LICENSE file for details.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
- AI vibe-coding endorsed only with elaborate commit message (and notes/*).
For issues, questions, or suggestions:
- Open an issue on GitHub
- Check existing issues for solutions
Made for the ❤️ music, by the gift of Sonnet 4.5.