This is a small local prototype for an audio-only live HLS stream that schedules HLS interstitials over in-band broadcast ad audio.
The main playlist is a sliding live playlist at /live.m3u8. It loops content segments and inserts two broadcast ad segments in a row at each ad break: broadcast-ad-1.ts followed by broadcast-ad-2.ts. The server emits the CLASS="com.apple.hls.interstitial" daterange before playback reaches each break. The interstitial tag includes:
CLASS="com.apple.hls.interstitial"X-ASSET-LISTpointing to/interstitial-assets.json?interstitialId=...&duration=..., which resolves the ad group into per-asset.m3u8playlists forinterstitial-1.tsandinterstitial-2.tsPLANNED-DURATIONequal to the two-segment interstitial durationX-PLAYOUT-LIMITequal to the two-segment interstitial durationX-SNAP="OUT,IN"so interstitial playback snaps out of and back into the primary streamX-TIMELINE-OCCUPIES="RANGE",X-TIMELINE-STYLE="HIGHLIGHT", andX-CONTENT-MAY-VARY="YES"
Optional prefetch EXT-X-DATERANGE tags can be enabled with PREFETCH_DATERANGES=1. These tags are emitted before the segment immediately preceding each break and use X-PREFETCH-DURATION and X-PREFETCH-ID to mirror the marker shape used by production live streams.
npm run generate
npm startRuntime config is read from config.json by default:
{
"prefetchDateRanges": false,
"interstitialAdCountPattern": [1, 2]
}interstitialAdCountPattern controls how many interstitial ads are returned for each break. [1, 2] alternates one-ad and two-ad breaks while keeping each break's PLANNED-DURATION, asset-list duration, and X-PLAYOUT-LIMIT at the full two-ad duration. Use [2] for the original two-ad behavior on every break.
To use a different config file:
CONFIG_PATH=/path/to/config.json npm startEnvironment variables still override the config file for quick local testing:
PREFETCH_DATERANGES=1 INTERSTITIAL_AD_COUNT_PATTERN=1,2 npm startOpen:
http://127.0.0.1:8765/live.m3u8
For the standard Android emulator, use the host-loopback address:
http://10.0.2.2:8765/live.m3u8
When the Android device or emulator is configured to use Charles Proxy on the Mac, use the Mac's LAN IP instead. Do not use 10.0.2.2 through Charles, because Charles resolves that address from the Mac side.
Example:
http://192.168.68.128:8765/live.m3u8
There is also a minimal preview page:
http://127.0.0.1:8765/
- Media generation uses local
sayandffmpeg. - All media is audio-only AAC in MPEG-TS containers.
- The live playlist advances by wall clock time and keeps a 16-segment window, currently about 96.6 seconds. The ad cadence puts at least three ad breaks in the live window after startup.
- Upcoming interstitials are advertised four segments before the break, which is enough lead time for players that resolve asset lists about three target durations ahead.
- Broadcast ad assets are grouped into one ad break, so supported players should replace
broadcast-ad-1.tsandbroadcast-ad-2.tswith the.m3u8playlists returned by the asset-list endpoint. - Generated segments speak their chunk name first, then use a unique sine tone for the rest of the chunk.