Skip to main content

Overview

Current Version: 7.1.0(GitHub)

Prefer to start from working code?

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:

  • INTERNET and ACCESS_NETWORK_STATE — streaming and connectivity monitoring
  • FOREGROUND_SERVICE and FOREGROUND_SERVICE_MEDIA_PLAYBACK — used by FeedPlayerService, which runs as a foreground service with foregroundServiceType="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):

StateMeaning
UNINITIALIZEDthe server has not responded yet, so it is not yet known whether music is available
UNAVAILABLEno streaming stations are available to this client and no offline music is available; the player is useless and should be discarded
AVAILABLE_OFFLINE_ONLYonly locally downloaded offline music is available for playback
READY_TO_PLAYthe player is idle and ready to begin playback
WAITING_FOR_ITEMthe player is waiting for the server to provide the next song
PLAYINGthe player is actively playing a song
PAUSEDplayback is paused
STALLEDaudio 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:

ListenerCalled when...
StateListenerthe player's state changes (ready, playing, paused, stalled, etc.)
PlayListener.onPlayStarteda new song begins playback — refresh any displayed song metadata here
PlayListener.onProgressUpdateplayback time elapses — drive progress bars from this
PlayListener.onSkipStatusChangedthe skippability of the current song changes
PlayListener.onPlayerErrora playback error occurs (the SDK will generally try the next song; surface the error so it isn't silent)
StationChangedListenerthe active station changes
LikeStatusChangeListenerthe like/dislike status of a song changes
OutOfMusicListenerthe 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.

warning

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.

Reference docs

Find Full Android reference docs here