spotify / android-sdk

Spotify SDK for Android
https://developer.spotify.com/documentation/android/
Apache License 2.0
462 stars 119 forks source link

How to detect when a song ends #43

Open rufogongora opened 5 years ago

rufogongora commented 5 years ago

Is there a way we can detect when a song ends?

I'm trying to create some sort of custom queue, as I couldn't find any way to modify the queue, any pointers as to how this can be achieved?

Thanks

Areyana commented 5 years ago

Maybe we can just subscribe to player state and check when we'll get new track with new track id or name.

rufogongora commented 5 years ago

Maybe we can just subscribe to player state and check when we'll get new track with new track id or name.

The problem is I would like to play something before the next track starts

Areyana commented 5 years ago

Maybe we can just subscribe to player state and check when we'll get new track with new track id or name.

The problem is I would like to play something before the next track starts

I can suggest you to research example repo of app-remote. In this example you can find TrackProgress bar, that we can use for our needs.

Brockify commented 5 years ago

I'm really curious, is there any solution to this? Was hoping it was going to be relatively straight forward by subscribing to a state, but I'm starting to see it isn't that easy huh? 👎

paulgoetze commented 5 years ago

There is a similar issue for the web-playback-sdk: https://github.com/spotify/web-playback-sdk/issues/35

A track seems to be ended if the position is 0 and if it is paused. However this can also appear in the beginning of a track, so you need to check whether it’s not this starting-state. For me it works when using an intermediate trackWasStarted variable and checking this one in addition to the flags provided by the SDK (Kotlin code):

private var trackWasStarted = false

// get the player state
spotifyAppRemote.playerApi.subscribeToPlayerState().setEventCallback {
    handleTrackEnded(it)
}

// ...

private fun handleTrackEnded(playerState: PlayerState) {
    setTrackWasStarted(playerState)

    val isPaused = playerState.isPaused
    val position = playerState.playbackPosition
    val hasEnded = trackWasStarted && isPaused && position == 0L

    if (hasEnded) {
        trackWasStarted = false
        // ... do whatever you want to do if track ended
    }
}

private fun setTrackWasStarted(playerState: PlayerState) {
    val position = playerState.playbackPosition
    val duration = playerState.track.duration
    val isPlaying = !playerState.isPaused

    if (!trackWasStarted && position > 0 && duration > 0 && isPlaying) {
        trackWasStarted = true
    }
}
Kilemonn commented 5 years ago

I agree with @paulgoetze, an event is generated "most" times with the song that has ended along with the position set to 0. But after extensive testing on my end there are scenarios where this specific event is never fired when auto play is enabled on Spotify and the player will move to the next auto-play song, without a specific event telling me that the previous song has end.

Have you come across this issue aswell @paulgoetze, or has your solution provided a suitable work around for your needs? Also during your tests has auto-play been enabled or disabled in the spotify app?

paulgoetze commented 5 years ago

@KyleGonzalez I also had issues when auto-play was enabled (see #90) and did not find a work-around for this. Also, I haven't tried whether the event is reliably generated when queued tracks end.

mdelolmo commented 5 years ago

Maybe we can just subscribe to player state and check when we'll get new track with new track id or name.

The problem is I would like to play something before the next track starts

Hi, there's no specific functionality for this, but have you tried listening to Player State and checking track.duration - playbackPosition? If that difference is below certain threshold (1 sec?), you can probably achieve that functionality.

JoshTechLee commented 4 years ago

Maybe we can just subscribe to player state and check when we'll get new track with new track id or name.

The problem is I would like to play something before the next track starts

Hi, there's no specific functionality for this, but have you tried listening to Player State and checking track.duration - playbackPosition? If that difference is below certain threshold (1 sec?), you can probably achieve that functionality.

but how would one go about doing this without a timer running in parallel to sync playback position? cause it seems pretty janky to do it that way...

niehusst commented 4 years ago

I found a workaround for getting app-remote to do what you want at the end of a song, based on paulgoetze suggestion above. (granted it is a bit messy, and sometimes you'll hear another song play for a blip before the correct action kicks in)

spotifyAppRemote.playerApi.subscribeToPlayerState().setEventCallback { // it is PlayerState!
    setTrackWasStarted(it) // this func from paulgoetze above

    val isPaused = it.isPaused
    val position = it.playbackPosition
    val hasEnded = trackWasStarted && isPaused && position == 0L
    val songPlayingIsNotRight = it.track?.uri != /* expectedSong.uri or null if you want to stop */

    if (hasEnded) {
        trackWasStarted = false
        // update val for the expected song
        val nextSong = /* expectedSong.uri or null if you want to stop */
        if (nextSong == null) {
            // pause before Spotify autoplay starts a random song
            spotifyAppRemote.playerApi.pause()
        } else {
            spotifyAppRemote.playerApi.play( /* expectedSong.uri */ )
        }
    } else if(songPlayingIsNotRight && !it.isPaused) {
        /* Sometimes Spotify misses the end-of-song event, or something goes wrong with
         * playing the next song and Spotify starts playing a random song from autoplay.
         * To remedy this, we're just going to hammer app-remote w/ the correct command
         * until it gets it right.
         */
        val correctCurrSong = /* expectedSong.uri or null if you want to stop */
        if (correctCurrSong == null) {
            // pause the currently playing Spotify autoplay random song
            spotifyAppRemote.playerApi.pause()
        } else {
            spotifyAppRemote.playerApi.play(correctCurrSong.uri)
        }
    }
}
// ...
private fun setTrackWasStarted(playerState: PlayerState) {
    val position = playerState.playbackPosition
    val duration = playerState.track.duration
    val isPlaying = !playerState.isPaused

    if (!trackWasStarted && position > 0 && duration > 0 && isPlaying) {
        trackWasStarted = true
    }
}

This is not an exact substitute for reliable end-of-song event generation, but hopefully it will help someone struggling with this issue.