androidkotlinspotify

Android: Spotify API preview_url for tracks is suddenly being returned as null


I'm using the Spotify API copyright free 30 sec preview of songs in my app, and suddenly it stopped working and all tracks returned have a null preview_url.

I tried specifying different markets -like "US" and "ES"- in the query to no avail, the exact same occurs.

The most weird thing is that it was working perfectly until now, it stopped working some days ago, so everything points to a Spotify issue/new restriction.

Below is my Kotlin code to get a track:

fun getTrack(token: String?, spotifyIdTrack: String?, market: String) {
    val api = SpotifyApi()
    var accessToken = ""
    val objJSON = awJson.str2JSON(token)
    if (objJSON != null) {
        try {
            accessToken =
                if (objJSON.has("access_token")) objJSON.getString("access_token") else ""
        } catch (e: JSONException) {
            // TODO: log?
        }
        if (accessToken.isNotEmpty()) {
            api.setAccessToken(accessToken)
            val spotify = api.service

            // Use MutableMap<String, Any> for the query parameters
            val queryMap = mutableMapOf<String, Any>()
            queryMap["market"] = market

            spotify.getTrack(spotifyIdTrack, queryMap, object : Callback<Track?> {
                override fun success(track: Track?, response: Response) {
                    listener!!.onTrackGet(track)
                }

                override fun failure(error: RetrofitError) {
                    // TODO: log?
                }
            })
        } else {
            // TODO: log?
        }
    }
}

This is frustrating, because it ruins the user experience in my app.

Of course I checked similar SO and GitHub posts and threads, but no one has a satisfactory response to my issue.

I checked the "available_markets" field for many of the responses and it is also being returned as null, what gives sense to the fact preview_url is null, but what I also think it's not normal, considering it was working.

Anything else I can try?


Solution

  • This is an ugly workaround, but now I definitely know we won't find a better solution (because this is how the Spotify API currently works):

    Spotify includes the preview_url in the embed data in the html of the track page. This means that if you get the html for the page there will be one script containing a json with all track information, including an "audioPreview" node.

    I'm fetching the html for the track url (using the id and the following code) and then I use JsonPath library from jayway to get the audioPreview node value.

    fun fetchPreviewUrl(trackId: String): String? {
        val embedUrl = "https://open.spotify.com/embed/track/$trackId"
        val client = OkHttpClient()
    
        val request = Request.Builder()
            .url(embedUrl)
            .build()
    
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) {
                println("Failed to fetch embed page: ${response.code}")
                return null
            }
    
            val html = response.body?.string() ?: return null
            val document = Jsoup.parse(html)
            val scriptElements = document.getElementsByTag("script")
    
            for (element in scriptElements) {
                val scriptContent = element.html()
                if (scriptContent.isNotEmpty()){
                    /*val jsonObject = JSONObject(scriptContent)
                    val audioPreview1 = findNodeValue1(jsonObject, "audioPreview")*/
                    return findNodeValueWithJsonPath(scriptContent, "audioPreview")
                }
            }
        }
        return null
    }
    
    private fun findNodeValueWithJsonPath(jsonString: String, targetNode: String): String? {
        return try {
            // Construct the JsonPath query
            val query = "$..$targetNode.url"
            println("Using JsonPath Query: $query") // Debug query
    
            // Perform the query
            // Handle cases where the result is a list (e.g., multiple matches)
            when (val result = JsonPath.read<Any>(jsonString, query)) {
                is List<*> -> {
                    if (result.isNotEmpty()) result[0].toString() else null // Return the first match
                }
                is String -> result // Directly return the string if it's not a list
                else -> null
            }
        } catch (e: Exception) {
            println("Error: ${e.message}")
            null
        }
    }
    

    And this is the implementation needed for gradle:

    implementation 'com.jayway.jsonpath:json-path:2.8.0'
    

    This way you will get the audioPreview that you can use the exact way as the preview_url, but this is very ugly.

    I'll use it to get by until I'm ready to replace Spotify.