Question

I'm making a .NET Spotify client in C#, using LibspotifyDotNet to wrap around the libspotify library, using Libspotify version 12.1.51.

I'm getting some strange behaviour when loading the session playlistcontainer for the first time (i.e with no pre-existing settings location). The app checks libspotify's function to check that the playlistcontainer is loaded, which returns true, and then from this determines it's safe to go grab up all of the playlists. When it requests the number of playlists in the playlistcontainer, it gets 0 so doesn't load anything. A while after this, I get the callback saying the playlist is loaded, which does mean the app gets the playlists fine on subsequent runs, but not on the first one. Am I better off setting an additional 'isPlaylistReallyLoaded' flag upon this callback? Or is there something wrong here? I'll give my debugger output and then some of the relevant methods.

Debug output

libspotify> 09:27:28.211 I [user_cache:135] UserCache::initiateGetUsers() will query for 1 users
libspotify> 09:27:28.231 I [ap:1752] Connecting to AP ap.gslb.spotify.com:4070
The thread 'Win32 Thread' (0x172c) has exited with code 0 (0x0).
libspotify> 09:27:28.264 E [c:/Users/spotify-buildagent/BuildAgent/work/1e0ce8a77adfb2dc/client/core/network/proxy_resolver_win32.cpp:215] WinHttpGetProxyForUrl failed
libspotify> 09:27:28.337 I [offline-mgr:2084] Storage has been cleaned
The thread 'Win32 Thread' (0x16f8) has exited with code 0 (0x0).
Itterating over loaded playlists
libspotify> 09:27:44.155 E [ap:1694] AP Socket Error: Undefined Error 0x4E20 (20000)
libspotify> 09:27:45.381 E [ap:3915] Connection error:  117
libspotify> 09:27:54.065 I [ap:1752] Connecting to AP ap.gslb.spotify.com:4070
0 playlists found
libspotify> 09:27:56.290 I [ap:1226] Connected to AP: 78.31.12.21:4070
libspotify> 09:28:03.035 I [user_cache:135] UserCache::initiateGetUsers() will query for 1 users
libspotify> 09:28:03.177 I [user_cache:135] UserCache::initiateGetUsers() will query for 100 users
libspotify> 09:28:03.271 W [core/playlist/playlist.h:45] Adding observer while updating
libspotify> 09:28:03.297 W [core/playlist/playlist.h:45] Adding observer while updating
libspotify> 09:28:03.325 W [core/playlist/playlist.h:45] Adding observer while updating
libspotify> 09:28:03.353 W [core/playlist/playlist.h:45] Adding observer while updating
playlist_added at position 0
playlist_added at position 1
playlist_added at position 2
playlist_added at position 3
playlist_added at position 4
playlist_added at position 5
playlist_added at position 6
playlist_added at position 7
playlist_added at position 8
container_loaded
libspotify> 09:28:03.644 W [core/playlist/playlist.h:45] Adding observer while updating

Where the line that says "Iterating over playlists" is where I've checked the loaded state of the playlist and found true and am going to enter the GetAllPlaylists() method. The line that says 0 playlists found is output after that loop has run, showing how many playlists were returned when checking sp_playlistcontainer_num_playlists. The line stating 'container_loaded' is in response to the container_loaded callback on the playlistcontainer.

Methods at play

// this is the method that is called upon login to get the user's playlists
public static List<PlaylistContainer.PlaylistInfo> GetAllSessionPlaylists()
{
    waitFor(delegate
    {
        return PlaylistContainer.GetSessionContainer().IsLoaded
            && PlaylistContainer.GetSessionContainer().PlaylistsAreLoaded;
    }, REQUEST_TIMEOUT);
    return PlaylistContainer.GetSessionContainer().GetAllPlaylists();
}

Within the PlaylistContainer model class

public static PlaylistContainer GetSessionContainer()
{
    if (_sessionContainer == null) {
        if (Session.GetSessionPtr() == IntPtr.Zero)
            throw new InvalidOperationException("No valid session.");
        _sessionContainer = new PlaylistContainer(libspotify.sp_session_playlistcontainer(Session.GetSessionPtr()));
    }
    return _sessionContainer; 
}

public bool IsLoaded {
    get {
        return libspotify.sp_playlistcontainer_is_loaded(_containerPtr);
   }
}

public bool PlaylistsAreLoaded {
    get {
        if (!this.IsLoaded)
            return false;
        int count = libspotify.sp_playlistcontainer_num_playlists(_containerPtr);
        for (int i = 0; i < count; i++) {                    
            if(libspotify.sp_playlistcontainer_playlist_type(_containerPtr, i) == libspotify.sp_playlist_type.SP_PLAYLIST_TYPE_PLAYLIST) {
                using (Playlist p = Playlist.Get(libspotify.sp_playlistcontainer_playlist(_containerPtr, i))) {
                    if (!p.IsLoaded)
                        return false;
                }
            }
        }
        return true;
    }
}

public List<PlaylistInfo> GetAllPlaylists() {
    if (!GetSessionContainer().IsLoaded)
        throw new InvalidOperationException("Container is not loaded.");
    List<PlaylistInfo> playlists = new List<PlaylistInfo>();
    Logger.WriteDebug("Itterating over loaded playlists");
    int count = libspotify.sp_playlistcontainer_num_playlists(_containerPtr);
    for (int i = 0; i < count; i++) {                
        if (libspotify.sp_playlistcontainer_playlist_type(_containerPtr, i) == libspotify.sp_playlist_type.SP_PLAYLIST_TYPE_PLAYLIST) {
            IntPtr playlistPtr = libspotify.sp_playlistcontainer_playlist(_containerPtr, i);
            playlists.Add(new PlaylistInfo() {
                Pointer = playlistPtr,
                PlaylistType = libspotify.sp_playlist_type.SP_PLAYLIST_TYPE_PLAYLIST,
                ContainerPtr = _containerPtr,
                Name = Functions.PtrToString(libspotify.sp_playlist_name(playlistPtr))
            });
        }
    }
    Logger.WriteDebug("{0} playlists found", count);
    return playlists;
}

I've borrowed a lot of my API interaction code from the Jamcast plugin (the only example out there really) and fixed problems as I've found them. But coming at this as a novice in Spotify's API, it seemed like a good way to start. I'm gradually rewriting it little by little. So as an additional question, is it worth me completely rewriting out the Jamcast stuff and starting over before I get it out there?

I know there's a lot there already, but if you need more info, let me know. I appreciate any help that you can give this libspotify noob.

Was it helpful?

Solution

A few notes:

1) libSpotify isn't really supposed to be used in this way — loading all the playlists at once is a pretty terrible idea. I have several hundred playlists on my account, a couple with 10,000 tracks in it. If I log into your app with my account and you try to load all the playlists into RAM like this, you'll likely run into RAM issues pretty quick, especially on a mobile device.

2) The playlist container and the playlists themselves are completely separate in terms of loading. When the container is loaded, all that means is that it knows the folder names and playlist URIs of all the playlists in the list. Nothing more.

3) As your log shows, playlist containers aren't fully loaded until the container_loaded callback is fired. I think that sp_playlistcontainer_is_loaded only returns false when the container is in the process of loading. So, as soon as you log in it'll read the list from cache and set _is_loaded to true. Then, it'll start receiving updates to the list in deltas from the cached list (as you can see in your log, receiving those playlist_added callbacks. Finally, it will fire the container_loaded callback.

4) libspotify is very asynchronous. The reason you're seeing them loaded straight away on your second run is because libspotify caches stuff for fast loading next time. To receive the playlists correctly on the first run, you need to wait until you get the various callbacks telling you that stuff has loaded.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top