Likes, skips
A listener can do three things to the song that is playing: skip it, like it, dislike it. None of them is a local UI action. Each is a call to Feed.fm addressed by play id, and the server decides what actually happens.
This page assumes you have read Station playback
for the loop these calls fit into. Every command below was run against
https://feed.fm/api/v3 with the demo/demo test credentials and shows what
actually came back. They continue the CLIENT_ID and PLAY_ID shell variables
set on the earlier pages.
Which controls to show
can_skip and can_like come back on POST /play/{id}/start. Both are
advisory and tell a client which controls to show for this play. They vary
from one play to the next depending on how the track is licensed for
this listener. Certain types of licensing can forbid liking and skipping
outright, so some listeners may never see these controls.
Working out when a like or skip will be allowed is the server's job, not the client's. Two rules are what a client follows:
- Do not offer like or dislike on a play whose
can_likeis false. - Do not skip a song without permission.
can_skip: falsemeans do not offer the control.can_skip: trueis not permission either, only a hint the control is worth showing. Permission comes fromPOST /play/{id}/skip, and that call can still say no.
Skipping
Skip is a request, not a command
POST /play/{id}/skip returning HTTP 200 does not mean the skip was granted.
skipDenied (code 7) and playNotActive (code 12) both arrive as HTTP 200
with success: false. Check success, and when it is false, keep playing
the current song.
A granted skip is the bare acknowledgement:
curl -sS -u demo:demo -X POST "https://feed.fm/api/v3/play/$PLAY_ID/skip" \
-H 'Content-Type: application/json' -d "{\"client_id\":\"$CLIENT_ID\"}"
{ "success": true }
Here is a real skipDenied response, from calling skip on a play that had
already been invalidated:
{
"success": false,
"error": {
"code": 7,
"message": "You may not skip a song you aren't playing",
"status": 200
}
}
Two rules follow from this. Do not stop audio until the skip comes back
successful, and do not disable the current song's controls while you wait.
A client that tears down playback optimistically and then reads success: false has nothing left to fall back to.
seconds is optional and reports how far the listener got before asking:
curl -sS -u demo:demo -X POST "https://feed.fm/api/v3/play/$PLAY_ID/skip" \
-H 'Content-Type: application/json' \
-d "{\"client_id\":\"$CLIENT_ID\",\"seconds\":42}"
A granted skip ends the play. Reserve the next song with POST /play and do
not also report the skipped one as complete: that call fails with
playbackComplete (code 21).
Skipping without permission
Skipping a song the server refused is a licensing violation, not a UI shortcut. Clients that appear to play songs faster than their durations allow trigger review, and the app credentials can be revoked. This is the reason the skip call exists at all rather than the client simply moving on.
Liking and disliking
Likes and dislikes are used to guide music selection and evaluate performance.
# like
curl -sS -u demo:demo -X POST "https://feed.fm/api/v3/play/$PLAY_ID/like" \
-H 'Content-Type: application/json' -d "{\"client_id\":\"$CLIENT_ID\"}"
# dislike
curl -sS -u demo:demo -X POST "https://feed.fm/api/v3/play/$PLAY_ID/dislike" \
-H 'Content-Type: application/json' -d "{\"client_id\":\"$CLIENT_ID\"}"
# remove a like, returning the song to neutral
curl -sS -u demo:demo -X DELETE "https://feed.fm/api/v3/play/$PLAY_ID/like" \
-H 'Content-Type: application/json' -d "{\"client_id\":\"$CLIENT_ID\"}"
All three answer the same way:
{ "success": true }
They are forgiving. Liking twice is a no-op, a like replaces an existing
dislike, a dislike replaces an existing like, and removing a like that was
never there still succeeds. An unknown play id is the only common failure,
and it is a normal missingObject (code 17) with HTTP 404.
A dislike does not skip the song
Disliking records the opinion and nothing else. Audio keeps playing. If your
UI implies the song will change, send POST /play/{id}/skip as well and
handle the refusal, because that skip can be denied like any other.
A like outlives the play
The like is stored against the client and the song. Later plays of the same song for the same client carry it back on the audio file:
{
"play": {
"audio_file": {
"id": "123262",
"track": { "id": "15360576", "title": "All the Time" },
"liked": true
}
}
}
liked is present only when the song is liked. It is never sent as false, so
test for the key, not for its value. A song the listener disliked looks exactly
like one they never touched.
The method override inverts a like
Clients that cannot issue DELETE may POST with an
X-HTTP-Method-Override: DELETE header. On /play/{id}/like, where both verbs
exist and mean opposite things, that changes what the call does. The header is
read before routing, so the request is dispatched to DELETE and the POST
handler never runs:
# This REMOVES the like. It does not record one.
curl -sS -u demo:demo -X POST "https://feed.fm/api/v3/play/$PLAY_ID/like" \
-H 'X-HTTP-Method-Override: DELETE' \
-H 'Content-Type: application/json' -d "{\"client_id\":\"$CLIENT_ID\"}"
{ "success": true }
The response is indistinguishable from a recorded like, so a client that sets the header globally will silently unlike every song the listener likes. Send it on this path only when removal is what you want.