Properly generate the Video stream url, without actually making a request!

This commit is contained in:
tzugen 2021-06-14 20:31:53 +02:00
parent 6a370696cd
commit 57d740af12
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
4 changed files with 36 additions and 99 deletions

View File

@ -1,54 +0,0 @@
package org.moire.ultrasonic.api.subsonic
import okhttp3.mockwebserver.MockResponse
import org.amshove.kluent.`should be equal to`
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_6_0
import org.moire.ultrasonic.api.subsonic.interceptors.toHexBytes
import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule
/**
* Integration test for [getStreamUrl] method.
*/
class GetStreamUrlTest {
@JvmField @Rule val mockWebServerRule = MockWebServerRule()
val id = "boom"
private lateinit var client: SubsonicAPIClient
private lateinit var expectedUrl: String
@Before
fun setUp() {
val config = SubsonicClientConfiguration(
mockWebServerRule.mockWebServer.url("/").toString(),
USERNAME,
PASSWORD,
V1_6_0,
CLIENT_ID
)
client = SubsonicAPIClient(config)
val baseExpectedUrl = mockWebServerRule.mockWebServer.url("").toString()
expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&format=raw&u=$USERNAME" +
"&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}"
}
@Test
fun `Should return valid stream url`() {
mockWebServerRule.enqueueResponse("ping_ok.json")
val streamUrl = client.api.getStreamUrl(id)
streamUrl `should be equal to` expectedUrl
}
@Test
fun `Should still return stream url if connection failed`() {
mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(500))
val streamUrl = client.api.getStreamUrl(id)
streamUrl `should be equal to` expectedUrl
}
}

View File

@ -83,19 +83,3 @@ fun StreamResponse.throwOnFailure(): StreamResponse {
}
return this
}
/**
* Gets a stream url.
*
* Calling this method do actual connection to the backend, though not downloading all content.
*
* Consider do not use this method, but [SubsonicAPIDefinition.stream] call.
*/
fun SubsonicAPIDefinition.getStreamUrl(id: String): String {
val response = this.stream(id, format = "raw").execute()
val url = response.raw().request().url().toString()
if (response.isSuccessful) {
response.body()?.close()
}
return url
}

View File

@ -62,7 +62,7 @@ class SubsonicAPIClient(
onProtocolChange(field)
}
private val okHttpClient = baseOkClient.newBuilder()
val okHttpClient: OkHttpClient = baseOkClient.newBuilder()
.readTimeout(READ_TIMEOUT, MILLISECONDS)
.apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
.addInterceptor { chain ->
@ -83,7 +83,7 @@ class SubsonicAPIClient(
// Create the Retrofit instance, and register a special converter factory
// It will update our protocol version to the correct version, once we made a successful call
private val retrofit = Retrofit.Builder()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("${config.baseUrl}/rest/")
.client(okHttpClient)
.addConverterFactory(

View File

@ -11,12 +11,11 @@ import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import okhttp3.Protocol
import okhttp3.Response
import okhttp3.ResponseBody
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.getStreamUrl
import org.moire.ultrasonic.api.subsonic.models.AlbumListType.Companion.fromName
import org.moire.ultrasonic.api.subsonic.models.JukeboxAction
import org.moire.ultrasonic.api.subsonic.throwOnFailure
@ -53,7 +52,7 @@ import timber.log.Timber
*/
@Suppress("LargeClass")
open class RESTMusicService(
subsonicAPIClient: SubsonicAPIClient,
val subsonicAPIClient: SubsonicAPIClient,
private val fileStorage: PermanentFileStorage,
private val activeServerProvider: ActiveServerProvider
) : MusicService {
@ -479,35 +478,43 @@ open class RESTMusicService(
return Pair(response.stream!!, partial)
}
/**
* We currently don't handle video playback in the app, but just create an Intent which video
* players can respond to. For this intent we need the full URL of the stream, including the
* authentication params. This is a bit tricky, because we want to avoid actually executing the
* call because that could take a long time.
*/
@Throws(Exception::class)
override fun getVideoUrl(
id: String
): String {
// TODO This method should not exists as video should be loaded using stream method
// Previous method implementation uses assumption that video will be available
// by videoPlayer.view?id=<id>&maxBitRate=500&autoplay=true, but this url is not
// official Subsonic API call.
val expectedResult = arrayOfNulls<String>(1)
expectedResult[0] = null
// Create a new modified okhttp client to intercept the URL
val builder = subsonicAPIClient.okHttpClient.newBuilder()
val latch = CountDownLatch(1)
Thread(
{
expectedResult[0] = API.getStreamUrl(id)
latch.countDown()
},
"Get-Video-Url"
).start()
// Getting the stream can take a long time on some servers
latch.await(1, TimeUnit.MINUTES)
if (expectedResult[0] == null) {
throw TimeoutException("Server didn't respond in time")
builder.addInterceptor { chain ->
// Returns a dummy response
Response.Builder()
.code(100)
.body(ResponseBody.create(null, ""))
.protocol(Protocol.HTTP_2)
.message("Empty response")
.request(chain.request())
.build()
}
return expectedResult[0]!!
// Create a new Okhttp client
val client = builder.build()
// Get the request from Retrofit, but don't execute it!
val request = API.stream(id, format = "raw").request()
// Create a new call with the request, and execute ist on our custom client
val response = client.newCall(request).execute()
// The complete url :)
val url = response.request().url()
return url.toString()
}
@Throws(Exception::class)