Embedding Guru Vani Radio
The stream is a plain, public MP3 feed over HTTPS. There is no API key, no account and no geoblocking, so a website, a native app or an Alexa skill can play it directly. These notes collect the endpoints, the format and the platform rules in one place.
← Back to the listener pageThe stream at a glance
| Live stream | https://radio.jaikhyapaparampara.com/listen/guru_vani_radio/radio.mp3 |
|---|---|
| Format | MP3, 64 kbps CBR, 44.1 kHz, stereo, continuous |
| Transport | Icecast 2 served by AzuraCast; audio/mpeg; HTTP 206 responses; Accept-Ranges: bytes |
| TLS | Let's Encrypt certificate, auto-renewed; HTTPS only; HSTS enabled |
| Access | Public and unauthenticated; no ads; no listener cap |
| CORS | Stream reflects the request Origin; Access-Control-Allow-Credentials: true; ICY headers exposed |
| ICY metadata | Yes. Send Icy-MetaData: 1 and the response adds icy-metaint: 16000; the current talk title follows every 16,000 bytes |
| Playlists | /public/guru_vani_radio/playlist.m3u and /public/guru_vani_radio/playlist.pls |
| Public player | /public/guru_vani_radio — a full AzuraCast player page; it sets no X-Frame-Options, so it can be iframed |
| Now playing JSON | /api/nowplaying/guru_vani_radio — public, Access-Control-Allow-Origin: *, cached 15 s |
| Not available | HLS and AAC mounts (both 404); on-demand downloads; requests list |
| Content | 150 recorded talks, ordered oldest to newest and looped; talk dates are in the artist field as YYYYMMDD |
The station shortcode used across the AzuraCast URLs is
guru_vani_radio. Everything above is served from
radio.jaikhyapaparampara.com, a separate host from the website.
Embed it in a web page
The smallest possible embed is a plain audio element. The site's own player does more (current talk, progress, history) by polling the now-playing endpoint every 15 seconds.
<audio controls preload="none"
src="https://radio.jaikhyapaparampara.com/listen/guru_vani_radio/radio.mp3">
</audio>
If you want the ready-made AzuraCast interface instead, the public player
can be framed — it sends no X-Frame-Options and no
frame-ancestors policy:
<iframe
src="https://radio.jaikhyapaparampara.com/public/guru_vani_radio"
title="Guru Vani Radio"
width="100%" height="520" style="border:0"
loading="lazy"></iframe>
Two rules apply in every browser. Playback must start from a user gesture
(a tap or click), and if you call play() from JavaScript the
returned promise can reject with NotAllowedError — handle
it and ask for a tap. The site's own player does exactly that.
Android apps
The recommended path is Jetpack Media3 (ExoPlayer) with a
MediaSessionService for background playback. Progressive MP3
plays directly from the URL, and Media3 handles Icecast metadata itself:
it asks for Icy-MetaData: 1, strips the interleaved metadata,
and merges the current talk title into player.mediaMetadata.
// Gradle: media3-exoplayer and media3-session
// Manifest: INTERNET, FOREGROUND_SERVICE, FOREGROUND_SERVICE_MEDIA_PLAYBACK
private const val STREAM = "https://radio.jaikhyapaparampara.com/listen/guru_vani_radio/radio.mp3"
class PlaybackService : MediaSessionService() {
private var session: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = ExoPlayer.Builder(this)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.build(),
true,
)
.setHandleAudioBecomingNoisy(true) // pause when headphones unplug
.setWakeMode(C.WAKE_MODE_NETWORK) // keep streaming with screen off
.build()
.apply {
setMediaItem(MediaItem.fromUri(STREAM))
prepare()
playWhenReady = true
}
session = MediaSession.Builder(this, player).build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = session
override fun onDestroy() {
session?.run { player.release(); release() }
session = null
super.onDestroy()
}
}
Declare the service with android:foregroundServiceType="mediaPlayback"
and an intent filter for androidx.media3.session.MediaSessionService,
then connect from your activity with a MediaController. Media
notifications from a media session are exempt from the Android 13+
notification permission.
Options at a glance
| Approach | Effort | Notes |
|---|---|---|
| Media3 ExoPlayer + MediaSessionService | 1–3 days | Recommended: notification, lock screen, audio focus, ICY metadata |
| WebView with the public player | Hours | Playback dies with the app; no lock-screen controls |
Intent.ACTION_VIEW to VLC or the browser | Minutes | Simplest of all; leaves your app and loses your branding |
Platform MediaPlayer | ~1 day | Plays the stream, but no ICY metadata or MediaSession integration |
A live stream never ends, so do not rely on the player finishing an item.
Recover from changes and outages (Wi-Fi to cellular, long background
pauses) by re-preparing the media item with a capped backoff — 1, 2, 5,
10 seconds — and resetting it on success. Add
com.google.android.gms.car.application metadata and a
MediaLibraryService browse tree if you want Android Auto.
iPhone and iPad apps
AVPlayer with an audio session is the standard approach. Continuous MP3 over HTTPS works in practice, but Apple does not document a live Icecast stream as a supported input, so treat it as empirical: test on the current iOS release and always ship reconnect logic. AVPlayer also does not expose ICY metadata, so poll the now-playing JSON for the current talk.
// Info.plist: UIBackgroundModes = ["audio"]
import AVFoundation
import MediaPlayer
final class RadioPlayer {
private let player = AVPlayer()
private let stream = URL(
string: "https://radio.jaikhyapaparampara.com/listen/guru_vani_radio/radio.mp3"
)!
func start() throws {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playback, mode: .default)
try session.setActive(true) // keeps playing when locked
let item = AVPlayerItem(url: stream)
item.automaticallyWaitsToMinimizeStalling = false // start live audio now
player.replaceCurrentItem(with: item)
player.play()
MPNowPlayingInfoCenter.default().nowPlayingInfo = [
MPMediaItemPropertyTitle: "Guru Vani Radio",
MPMediaItemPropertyArtist: "Jai Khyapa Parampara",
MPNowPlayingInfoPropertyIsLiveStream: true
]
}
}
Add MPRemoteCommandCenter handlers for play and pause so the
lock screen and headphones work, and handle
AVAudioSession.interruptionNotification plus route changes
(unplugged headphones should pause, not blast the speaker). Observe
AVPlayerItem.status == .failed,
playbackStalledNotification and
AVPlayerItemFailedToPlayToEndTime for reconnection.
Options at a glance
| Approach | Effort | Notes |
|---|---|---|
| AVPlayer + AVAudioSession + Now Playing | 1–3 days | Recommended: background audio, lock screen, small binary |
| MobileVLCKit | 2–4 days | Tolerates ICY and flaky networks; large binary and LGPL dynamic-linking duties |
| WKWebView with the public player | Hours | Background audio and lock screen unreliable |
UIApplication.shared.open to Safari | Minutes | Simplest; exits your app |
CarPlay needs Apple's com.apple.developer.carplay-audio
entitlement, which is requested from Apple and can take weeks. A pure
stream wrapper also risks App Store rejection under
guideline 4.2
(minimum functionality) unless it adds real value: now playing, history,
favourites, sharing, offline notes.
Autoplay, background audio and the silent switch
Audible autoplay is blocked by policy. On iOS it cannot be enabled at all; on Android Chrome it is allowed only after the visitor has interacted with the site or installed it. No header, flag or script changes that, so the correct design is a prominent tap-to-play button with honest failure handling — exactly what the listener page does.
iPhone and iPad
- There is no autoplay setting. iOS has no Auto-Play preference — not in Settings > Apps > Safari, and not in the per-site aA menu. The familiar “Allow All Auto-Play” / “Stop Media with Sound” / “Never Auto-Play” choice exists only in Safari on macOS. Do not send iPhone users looking for it.
- Call
play()synchronously inside aclick,touchendorkeydownhandler. Fetching anything first and callingplay()afterwards can lose the gesture and reject withNotAllowedError. - Keep one persistent
<audio>element. The first tap unlocks that element for the session, so reconnects andsrcchanges work later; a new element or a fresh page load needs a new tap. Adding the page to the Home Screen does not remove the first tap. - The muted-autoplay trick does not work. WebKit's exception covers muted
<video>only;<audio muted autoplay>still needs a gesture, and unmuting without one pauses playback — which is pointless for a live radio stream anyway. - Media Session. After the first tap, iOS Safari 15+ supports the Media Session API: lock-screen artwork, title and play/pause. Use it; skip
setPositionStatefor a live stream. - Background audio. A plain
<audio>in a Safari tab keeps playing when the screen locks or the user switches apps; closing the tab stops it. A Home Screen web app behaves similarly, but test it on each iOS release, and remember that timers may be throttled while the screen is locked — do not rely on JavaScript to reconnect in the background. - Silent switch. An HTML5
<audio>element plays on the media channel and is audible even when the Ring/Silent switch is on. Only Web Audio is treated as ambient and muted by that switch — if you add Web Audio, setnavigator.audioSession.type = 'playback'(Safari 16.4+). - Low Power Mode. WebKit tightens media behaviour while it is on; a tap still works, but nothing should be expected to start on its own.
- In-app browsers. Instagram, Facebook, WhatsApp, TikTok and Gmail open links in their own WebView. They also require a tap, and some interfere with streaming or audio routing. Detect the common user agents and show an “Open in Safari” hint.
- Failure handling.
play()returns a promise. TreatNotAllowedErroras “ask for another tap”,AbortErroras harmless, and anything else as a network or format problem worth retrying.
Android
- Chrome always allows muted autoplay, and may allow audible autoplay once the visitor has interacted with the site or installed it to the home screen. Chrome's Media Engagement Index is a desktop feature, not Android.
- Silent or vibrate mode does not mute media — media volume is separate — but Chrome's per-site sound setting can still block audio.
- Use the same tap-first design; it is the only behaviour you can rely on across browsers and WebViews.
The pattern that works
const audio = document.querySelector('#radio-audio');
document.querySelector('#radio-toggle').addEventListener('click', () => {
audio.play().catch((error) => {
if (error.name === 'NotAllowedError') {
// Gesture lost or blocked: keep the button visible and ask for a tap.
} else if (error.name !== 'AbortError') {
// Network or format problem: show a retry state.
}
});
});
Keep the same element and re-call play() after reconnection or
when the page becomes visible again; only fall back to a button prompt if
the promise rejects.
One codebase: React Native, Flutter, WebView
These all wrap the native players described above, so they inherit the same rules — a foreground service on Android, the audio background mode on iOS, and a user gesture on both.
- React Native Track Player. Add the stream as a track with
type: TrackType.Default. Playback uses ExoPlayer or AVPlayer underneath and you get a notification, lock-screen controls and background playback. - Flutter
just_audio. Plays the URL directly. Pair it withjust_audio_background(oraudio_service) for the notification and background service. - Capacitor, Cordova or a plain WebView. The quickest route, but background audio is unreliable on iOS and playback stops when the app is killed. Fine for an internal or demo app, not for the store.
Practical notes
- Metadata. The station's current talk is in
/api/nowplaying/guru_vani_radio:now_playing.song.titleholds the title,now_playing.song.artistthe recording date asYYYYMMDD, andnow_playing.duration/elapsedpower a progress bar. Poll it every 15–30 seconds; it is cached for 15 seconds. - Raw ICY clients. Send
Icy-MetaData: 1and the response addsicy-metaint: 16000. Read the stream as 16,000-byte audio blocks, each followed by one length byte and the metadata block containingStreamTitle. That is how VLC, foobar2000 and ExoPlayer show the current talk. - Data and battery. 64 kbps works out to roughly 28.8 MB an hour, or about 230 MB over eight hours. Offer a Wi-Fi-only switch.
- Reconnects. Treat it as live: never seek, and when the connection drops re-open the URL with a capped backoff rather than waiting for a natural end.
- CORS. The stream reflects any request
Originand exposes the ICY response headers. A plain<audio src>needs no CORS attributes at all; do not addcrossoriginunless you genuinely need Web Audio analysis. - HLS is not available today — the endpoints return 404. Enabling it in AzuraCast would give first-class HLS on both platforms, at the cost of 6–30 seconds of extra latency. Progressive MP3 is the low-latency option.
- Attribution. Link back to the listener page, keep the talks free to hear, and write to us before publishing an app or skill.
- Test checklist. Lock-screen controls, headphones unplugged, Wi-Fi to cellular, eight hours in the background, iPhone silent switch, and a cold start after the app was killed.
Amazon Echo / Alexa
Alexa plays audio only from sources it understands: a directory it has integrated, or an Alexa skill. A bare stream URL cannot be spoken to a device. The realistic options, in order of effort:
| Option | Voice? | Effort / cost | Notes |
|---|---|---|---|
| Bluetooth from a phone | No | None, $0 | Works on current Echo models today; the phone stays the player. Steps are on the listener page. |
| myTuner Radio directory | Partial | Free submission, ~30 min plus review | myTuner accepts station registrations and its Alexa skill plays catalogue stations by name (“Alexa, ask myTuner Radio to play …”). Listing is not guaranteed. |
| TuneIn directory | Best | Free self-service outside the US; US listings go through TuneIn On Air (paid) | If listed, “Alexa, play Guru Vani Radio” works with no listener setup. TuneIn has historically frozen new additions, so acceptance is uncertain. |
| Custom Alexa skill | Yes | 2–5 developer days, $0 with an Alexa-hosted skill | The only path fully under the station's control: “Alexa, open Guru Vani Radio”. Public release requires Amazon certification. |
| Radio Browser (third party) | Clunky | €2/month, ~15 min | A consumer skill that accepts arbitrary URLs, including this one, after a one-time web setup. Not a station-side solution. |
What a custom skill would need
- An HTTPS endpoint on port 443 with a trusted certificate — the stream's Let's Encrypt certificate already satisfies this.
- MP3 between 16 and 384 kbps — 64 kbps is comfortably inside the range.
- The AudioPlayer interface, plus
AMAZON.PauseIntentandAMAZON.ResumeIntent; a live stream never finishes, soPlaybackNearlyFinishedis not used. shouldEndSession: trueon the playback response, or Alexa stops the stream to listen for more speech.- An Amazon developer account. An Alexa-hosted skill needs no AWS account and does not charge for hosting.
- Beta testing works on devices registered to the developer account; everyone else needs the skill published and certified.