Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hitenpatel/songprint/llms.txt

Use this file to discover all available pages before exploring further.

RecognitionClient is a thin HTTP wrapper that posts a signature produced by SignatureGenerator to the public Shazam discovery endpoint and returns the raw JSON response body as a String. The class has no third-party dependencies — it uses only java.net.HttpURLConnection from the JDK — and deliberately performs no JSON parsing itself so you can use whichever JSON library your project already has.

Making a recognition call

The full pipeline is three steps: decode and resample the raw PCM, generate the signature, then post it to the endpoint.
import io.github.hitenpatel.songprint.Pcm
import io.github.hitenpatel.songprint.RecognitionClient
import io.github.hitenpatel.songprint.SignatureGenerator

// 1. Decode 16-bit little-endian PCM bytes into a ShortArray.
val samples: ShortArray = Pcm.bytesToShorts(rawPcmBytes)

// 2. Resample to 16kHz mono (from 44100 Hz stereo in this example).
val mono16k: ShortArray = Pcm.resampleToMono16k(samples, sampleRate = 44100, channels = 2)

// 3. Generate the binary Shazam-compatible signature.
val signature: ByteArray = SignatureGenerator().generateSignature(mono16k)

// 4. POST to the discovery endpoint; returns raw JSON or null.
val json: String? = RecognitionClient().recognise(signature, mono16k.size)
recognise accepts the signature ByteArray and the number of 16kHz mono samples the signature was generated from. The sample count is used to compute the duration (in milliseconds) sent to the endpoint.

The JSON response

The response is the raw JSON body returned by the Shazam endpoint. No reshaping or validation is performed. When a match is found the response contains a track object; when there is no match the matches array is empty. Key fields to extract:
FieldDescription
matchesArray of match objects. Empty means no song was recognised.
track.titleSong title.
track.subtitleArtist name.
track.images.coverartURL to the album cover art image.
track.share.hrefApple Music / Shazam link for the track.
A minimal parse with org.json (available on Android without extra dependencies):
import org.json.JSONObject

if (json != null) {
    val root = JSONObject(json)
    val matches = root.optJSONArray("matches")
    if (matches != null && matches.length() > 0) {
        val track = root.optJSONObject("track")
        val title    = track?.optString("title")
        val artist   = track?.optString("subtitle")
        val coverArt = track?.optJSONObject("images")?.optString("coverart")
        val link     = track?.optJSONObject("share")?.optString("href")
        // use title, artist, coverArt, link …
    }
}

Null returns

recognise returns null only when the HTTP response code is anything other than 200 — for example a network error, rate-limiting, or an endpoint change. A 200 response with an empty matches array still returns the full JSON string, not null. Your code should inspect matches.length() to distinguish a clean no-match from a transport failure.

Constructor options

RecognitionClient can be constructed with custom endpoint and timeout values:
val client = RecognitionClient(
    endpoint         = "https://amp.shazam.com/discovery/v5/en/GB/android/-/tag",
    connectTimeoutMs = 10_000,
    readTimeoutMs    = 10_000,
)
ParameterDefaultDescription
endpointhttps://amp.shazam.com/discovery/v5/en/GB/android/-/tagThe Shazam discovery URL.
connectTimeoutMs10000TCP connect timeout in milliseconds.
readTimeoutMs10000Socket read timeout in milliseconds.
The default constructor RecognitionClient() uses all three defaults.

Caveats

The discovery endpoint used by RecognitionClient is unofficial and undocumented. It has been stable for years and is used by SongRec and many similar tools, but it can change or disappear at any time without notice. It is not suitable for high-volume or commercial use, and rate limiting applies. Do not build anything load-bearing on it.

Running on a background thread

recognise makes a blocking network call using HttpURLConnection. On Android it will throw a NetworkOnMainThreadException if called from the main thread. Always dispatch it to a background thread — for example using Dispatchers.IO in a coroutine:
val json: String? = withContext(Dispatchers.IO) {
    RecognitionClient().recognise(signature, mono16k.size)
}

Build docs developers (and LLMs) love