I am working on a Kotlin app in Android Studio. I'm trying to stream audios from SoundCloud.com using their API and my custom ExoPlayer. My issue is coming from trying to actually using the ExoPlayer.
Here's the link to the SoundCloud API Guide and their API Explorer for reference. I have a Client_ID, am able to return an Access Token, find the correct Track, and pull the streaming urls for that track. I am able to confirm from the track meta-data that it is publicly accessible and externally streamable. The streaming urls come in these formats:
"http_mp3_128_url": "https://cf-media.sndcdn.com/",
"hls_mp3_128_url": "https://cf-media.sndcdn.com/",
"hls_opus_64_url": "https://cf-media.sndcdn.com/",
"preview_mp3_128_url": "https://cf-media.sndcdn.co
Using the "http_mp3_128_url" and the value of its string, I can enter that string into my web browser which opens a page and streams the audio perfectly. *Note: The url only lasts around an hour due to the expiration of the access token. I am able to confirm it is still a 'good' link when getting this ExoPlayer error.
Whenever I try to create a MediaSource with that string and put it into my ExoPlayer, I get a Source Error code. As far as I can tell from the error log, my ExoPlayer is trying to use a FileDataSource and returns open failed: ENOENT (No such file or directory)
I've tried openly declaring the MediaSource as a ProgressiveMediaSource and DefaultDataSource, and I've also tried just making a MediaItem and adding that to the player. No luck and no change in the error I am getting.
ExoPlayer Setup:
private lateinit var player: ExoPlayer
private lateinit var playerView: PlayerView
private fun setupPlayer(){
player = ExoPlayer.Builder(this).build()
playerView = findViewById(R.id.gm_player)
playerView.player = player
player.addListener(this)
}
The Logcat output:
ExoPlayerImplInternal Playback error
androidx.media3.exoplayer.ExoPlaybackException: Source error
at androidx.media3.exoplayer.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:684)
at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:656)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:214)
at android.os.HandlerThread.run(HandlerThread.java:67)
Caused by: androidx.media3.datasource.FileDataSource$FileDataSourceException: java.io.FileNotFoundException: : open failed: ENOENT (No such file or directory)
at androidx.media3.datasource.FileDataSource.openLocalFile(FileDataSource.java:205)
at androidx.media3.datasource.FileDataSource.open(FileDataSource.java:116)
at androidx.media3.datasource.DefaultDataSource.open(DefaultDataSource.java:272)
at androidx.media3.datasource.StatsDataSource.open(StatsDataSource.java:86)
at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1006)
at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:414)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
Caused by: java.io.FileNotFoundException: : open failed: ENOENT (No such file or directory)
at libcore.io.IoBridge.open(IoBridge.java:496)
at java.io.RandomAccessFile.<init>(RandomAccessFile.java:289)
at java.io.RandomAccessFile.<init>(RandomAccessFile.java:152)
at androidx.media3.datasource.FileDataSource.openLocalFile(FileDataSource.java:186)
at androidx.media3.datasource.FileDataSource.open(FileDataSource.java:116)
at androidx.media3.datasource.DefaultDataSource.open(DefaultDataSource.java:272)
at androidx.media3.datasource.StatsDataSource.open(StatsDataSource.java:86)
at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1006)
at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:414)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory)
at libcore.io.Linux.open(Native Method)
at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:252)
at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7255)
at libcore.io.IoBridge.open(IoBridge.java:482)
at java.io.RandomAccessFile.<init>(RandomAccessFile.java:289)
at java.io.RandomAccessFile.<init>(RandomAccessFile.java:152)
at androidx.media3.datasource.FileDataSource.openLocalFile(FileDataSource.java:186)
at androidx.media3.datasource.FileDataSource.open(FileDataSource.java:116)
at androidx.media3.datasource.DefaultDataSource.open(DefaultDataSource.java:272)
at androidx.media3.datasource.StatsDataSource.open(StatsDataSource.java:86)
at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1006)
at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:414)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
Does anyone have any experience with ExoPlayer or media sources/streaming who may be able to help me understand what's going on and how to fix it?
Attempt: Creating and assigning ProgressiveMediaSource with streaming url "http_mp3_128_url":
val dataSource = DefaultHttpDataSource.Factory()
val medSource : MediaSource = ProgressiveMediaSource.Factory(dataSource)
.createMediaSource(fromUri(streamUrl))
player.setMediaSource(medSource)
player.prepare()
player.playWhenReady
player.play()
Attempt: Using HlsMediaSource with "http_mp3_128_url", "hls_mp3_128_url", and "hls_opus_64_url":
val dataSource = DefaultHttpDataSource.Factory()
val medSource : MediaSource = HlsMediaSource.Factory(dataSource)
.createMediaSource(fromUri(streamUrl))
player.setMediaSource(medSource)
Attempt: Creating and assigning a MediaItem directly:
var medItem = MediaItem.Builder()
.setUri(streamUrl)
.setMimeType(MimeTypes.AUDIO_MPEG)
.build()
player.setMediaItem(medItem)
player.prepare()
player.playWhenReady
player.play()
Declared in the AndroidManifest:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"/>
Thank you!
Update - PROBLEM SOLVED!
As a caveat, I think I had multiple problems that contributed to my original issue, including:
Here is my final code.
From main():
runBlocking {
launch {
delay(2000)
streamMusic()
}
getStreamingTrack()
}
Here is where I was retrieving the streaming urls. This is the method that returns the 4 url formats mentioned in the original post.
private fun getStreamingTrack() {
GlobalScope.launch(Dispatchers.IO){
val trackUrl = URL("https://api.soundcloud.com/tracks/${trackId}/streams")
var trackConn = trackUrl.openConnection() as HttpsURLConnection
trackConn.requestMethod = "GET"
trackConn.setRequestProperty("accept", "application/json; charset=utf-8")
trackConn.setRequestProperty("Authorization", "OAuth $accessToken")
trackConn.doInput = true
trackConn.doOutput = false
// Check if the connection is successful
val responseCode = trackConn.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
val response = trackConn.inputStream.bufferedReader().readText()
val jsonObject = JSONTokener(response).nextValue() as JSONObject
httpStreamUrl = jsonObject.getString("http_mp3_128_url")
// Uncomment line below if need to print parsed string to the log
//Log.i("STREAM URL : ", myUrl)
// Shouldn't need these, but these are the 3 other urls returned from SoundCloud
/*myHls1 = jsonObject.getString("hls_mp3_128_url")
val myHls2 = jsonObject.getString("hls_opus_64_url")
val myPrev = jsonObject.getString("preview_mp3_128_url")*/
trackConn.disconnect()
} else {
Log.e("Track Streaming Error", responseCode.toString())
Log.e("ERROR MESSAGE : ", trackConn.responseMessage)
}
}
}
This method declares the ProgressiveMediaSource
private fun streamMusic() {
val dataSourceFactory = DefaultHttpDataSource.Factory()
.setAllowCrossProtocolRedirects(true)
val progressiveMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(fromUri(httpStreamUrl))
player.setMediaSource(progressiveMediaSource)
player.prepare()
player.play()
}
Hope this helps anyone else who may be having trouble!