Overview
Current Version: 7.1.0(GitHub)
You can go straight to the demo Android app — a reference integration showing SDK setup, playback, and lock-screen support.
The Android SDK is written in Kotlin and supports Media3. The Android SDK is kept up to date with all our latest features and handles integration with Android Notifications for lock screen playback control.
Installation
The SDK is available as an .aar via Maven Central or from our GitHub Android SDK Release Page. To minimally get music playback started in your application, add the following to your build.gradle file (see the release page for the latest version):
implementation 'fm.feed.android:player-sdk:7.1.0'
The SDK depends on AndroidX Media3 ExoPlayer for audio playback, as we've found that to work best in multiple environments. The SDK is published as a single artifact built on Media3, so there is no need to match the SDK to a specific ExoPlayer version.
Requirements
The SDK requires a minimum SDK version of 23 (Android 6.0). The following permissions are declared in the SDK's manifest and merged into your app automatically — no manual manifest changes are needed:
INTERNETandACCESS_NETWORK_STATE— streaming and connectivity monitoringFOREGROUND_SERVICEandFOREGROUND_SERVICE_MEDIA_PLAYBACK— used byFeedPlayerService, which runs as a foreground service withforegroundServiceType="mediaPlayback"for background playback and lock screen controls
Concepts
The SDK centers around two classes: FeedAudioPlayer and FeedPlayerService. FeedAudioPlayer presents a fully functional music player that can play and stream music from the Feed.fm servers. FeedPlayerService is an optional class that can be used to retrieve and manage a singleton FeedAudioPlayer instance that is tied to an Android Foreground Service and Notification so that music can continue while your app is backgrounded and music can be controlled from the lock screen and media controls. If you do not need a service we recommend creating FeedAudioPlayer directly using its builder instead of using FeedPlayerService.
Because virtually all functionality in the FeedAudioPlayer takes place asynchronously, clients may register implementations of the various listener interfaces to receive notification of player events.
The FeedAudioPlayer is initialized with a token and secret before playback becomes available. The FeedAudioPlayer uses those credentials to contact the Feed.fm server, confirm that the client is in a region licensed for music playback, and retrieve the list of stations that the user may pull music from.
The FeedPlayerService takes care of managing lock screen integration, notifications, audio ducking, and playback controls.
Getting started
The following call should be made to initialize FeedAudioPlayer and begin communication with the service. While you can call the constructors for FeedAudioPlayer we recommend using the builder.
FeedAudioPlayer.Builder(applicationContext, "demo", "demo")
.setAvailabilityListener(object : AvailabilityListener {
override fun onPlayerAvailable(player: FeedAudioPlayer) {
// player is now ready, save its reference for later use
mPlayer = player
// get the station list
val list = player.stationList
// set the correct station
player.setActiveStation(list.first(), false)
// call play
player.play()
}
override fun onPlayerUnavailable(e: Exception) {
// Player is not available: the user is not in a licensed location
// or the Feed.fm servers could not be reached.
}
})
.build()
Since this is a crucial call to initialize the SDK, if it fails due to network error the player will be useless, therefore we recommend either retrying the call in case of a failure or setting FeedAudioPlayer.autoNetworkRetry = true to enable automatic retrying. When set to true, the SDK will make up to 100 attempts to retry connection to the network, waiting longer and longer in between attempts. If after 100 attempts the SDK can no longer reach a network connection, it will stop trying and a new FeedAudioPlayer needs to be initialized.
Your token and secret values are provided to you when you sign up with Feed.fm, and will give your app access to your custom stations. Until you have them, you can use the "demo" string above, or one of the strings listed in the Testing credentials section below.
FeedPlayerService
If clients desire, they can instead create a FeedAudioPlayer singleton instance using FeedPlayerService using one of its public constructors. This will allow use of Android Foreground Service and Notification so that music can continue while your app is backgrounded and music can be controlled from the lock screen and media controls. This can be useful if your app does not have a service of its own and expects to be backgrounded but needs the music to keep going.
Initialize the service in your Application.onCreate(), either with a token and secret directly
or by passing in a configured FeedAudioPlayer.Builder:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
FeedPlayerService.initialize(applicationContext, "demo", "demo")
// ... or, to customize the player, pass a Builder instead:
// val builder = FeedAudioPlayer.Builder(applicationContext, "demo", "demo")
// FeedPlayerService.initialize(builder)
}
}
To access the player singleton use an availability listener.
FeedPlayerService.getInstance(object : AvailabilityListener {
override fun onPlayerAvailable(player: FeedAudioPlayer) {
// the player is now ready, save its reference for later use
}
override fun onPlayerUnavailable(e: Exception) {
// no music is available to this client
}
})
Testing credentials
There are a number of sample credentials you can use to assist in testing your app out. Use one of the following strings as both your token and secret to get the desired behavior:
demo 3 simple stations with no skip limits.
counting one station with short audio clips of a voice speaking a number. Useful for testing song transitions.
badgeo this simulates the response from the server when the client is not in a region licensed play music.
Controlling playback
Once the player is available, playback is controlled with simple methods on the FeedAudioPlayer:
player.play() // begin or resume playback in the active station
player.play(station, withCrossfade = false) // make a station active and start playing it
player.pause() // pause playback
player.skip() // ask to skip the current song
player.like() // mark the current song as liked
player.dislike() // mark the current song as disliked
player.unlike() // remove a like or dislike
Skips must be granted by the Feed.fm servers (users are limited in the number of songs they may
skip per hour in some stations). Check player.canSkip() to decide whether to enable a skip
button, and pass a SkipListener to skip() to learn whether an individual skip request
succeeded or was denied.
While music is playing, the current song is exposed as player.currentPlay. Its audioFile
property carries the metadata to display — track.title, artist.name, release.title, and
durationInSeconds — along with isLiked and isDisliked booleans that reflect any feedback the
user has given. player.currentPlaybackTime returns the elapsed playback time of the current song,
and player.state returns the current State.
Player state and events
At any moment the player is in one of a fixed set of states, exposed as player.state
(a State enum value):
| State | Meaning |
|---|---|
UNINITIALIZED | the server has not responded yet, so it is not yet known whether music is available |
UNAVAILABLE | no streaming stations are available to this client and no offline music is available; the player is useless and should be discarded |
AVAILABLE_OFFLINE_ONLY | only locally downloaded offline music is available for playback |
READY_TO_PLAY | the player is idle and ready to begin playback |
WAITING_FOR_ITEM | the player is waiting for the server to provide the next song |
PLAYING | the player is actively playing a song |
PAUSED | playback is paused |
STALLED | audio data did not arrive in time and the player is buffering |
Rather than being polled, the player announces every change in its state through listener interfaces. Register implementations for the events you care about:
| Listener | Called when... |
|---|---|
StateListener | the player's state changes (ready, playing, paused, stalled, etc.) |
PlayListener.onPlayStarted | a new song begins playback — refresh any displayed song metadata here |
PlayListener.onProgressUpdate | playback time elapses — drive progress bars from this |
PlayListener.onSkipStatusChanged | the skippability of the current song changes |
PlayListener.onPlayerError | a playback error occurs (the SDK will generally try the next song; surface the error so it isn't silent) |
StationChangedListener | the active station changes |
LikeStatusChangeListener | the like/dislike status of a song changes |
OutOfMusicListener | the active station has run out of music to play |
val playListener = object : PlayListener {
override fun onPlayStarted(play: Play?) {
val file = play?.audioFile
titleView.text = file?.track?.title ?: ""
artistView.text = file?.artist?.name ?: ""
}
override fun onProgressUpdate(play: Play, elapsedTime: Float, duration: Float) {
progressBar.progress = if (duration > 0) elapsedTime / duration else 0f
}
override fun onSkipStatusChanged(status: Boolean) {
skipButton.isEnabled = status
}
override fun onPlayerError(error: FeedFMError) {
Log.e(TAG, "playback error: $error")
}
}
player.addPlayListener(playListener)
Each addXXXListener method has a matching removeXXXListener — unregister your listeners when
the observing component goes away (for example, in a ViewModel's onCleared()) so the player does
not hold a reference to it.
Stations and station metadata
After the player becomes available, player.stationList holds Station objects representing a
subset of the music stations available to the client, and player.activeStation is the station music
is currently drawn from. To present a station picker, render the list and play the user's choice:
for (station in player.stationList) {
Log.i(TAG, station.name)
}
// make a station active and start playback in it
player.stationList.firstOrNull { it.name == "Station One" }?.let { station ->
player.play(station, withCrossfade = false)
}
Every station has a name and an options map containing any custom station-level metadata
configured for your account — for example, a subheader description line or a
background_image_url pointing to station artwork. Contact your Customer Success Manager to attach
custom metadata to your stations.
Because stationList holds only a subset of the stations available to your account, the SDK also
provides station search functions that query the full set of stations on the Feed.fm servers.
searchForAndSetActiveStation finds a station by type and attribute filters, makes it the active
station, and optionally begins buffering audio for immediate playback — all in a single network
request. See the Station Search recipe for details.
Station identifiers (such as tempId) are not guaranteed to be stable across application restarts,
so do not persist them (for instance, to remember the user's last station). You should use a station's
name or some player.option value, which can be searched for with
player.stationList.getStationWithOption("my-id", savedMyId).
Background playback and the lock screen
When the player is created through FeedPlayerService, the SDK runs a foreground
media-playback service while music plays and posts a media-style notification with the current
song's metadata, artwork, and remote play/pause/skip controls — the same controls appear on the
lock screen. The SDK's manifest merges in the FOREGROUND_SERVICE* permissions and the service
declaration automatically.
The one thing the SDK cannot do for you is ask the user's permission to show the notification. On
Android 13+ (API 33), POST_NOTIFICATIONS is a runtime permission that the host app must declare
in its manifest:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
and request at runtime, for example from your launch activity:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) !=
PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQUEST_CODE)
}
If the user declines, playback is unaffected — only the notification and lock-screen controls are suppressed.
Logging
The SDK logs its activity to Logcat under fm.feed.* tags, which is helpful when diagnosing why
music isn't starting. The minimum level recorded is controlled with FMLog.logLevel (from the
fm.feed.android.playersdk.logs package):
FMLog.logLevel = LogLevel.NONE // silence SDK logging in production builds
Available levels are LogLevel.VERBOSE, DEBUG (the default), WARN, INFO, ERROR, ASSERT,
and NONE.
Demo apps
A fully functional demo app is available on GitHub: Android SDK Demo, a reference integration — a small, readable Jetpack Compose app that lists the available stations, plays/pauses/skips tracks, likes/dislikes the current song, and shows how the media notification, lock-screen controls, artwork, and background audio are wired up.