Merge pull request #525 from tzugen/fix-video-url
Properly generate the Video stream url, without actually making a request
This commit is contained in:
commit
2d4c773344
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -83,19 +83,3 @@ fun StreamResponse.throwOnFailure(): StreamResponse {
|
||||||
}
|
}
|
||||||
return this
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class SubsonicAPIClient(
|
||||||
onProtocolChange(field)
|
onProtocolChange(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val okHttpClient = baseOkClient.newBuilder()
|
val okHttpClient: OkHttpClient = baseOkClient.newBuilder()
|
||||||
.readTimeout(READ_TIMEOUT, MILLISECONDS)
|
.readTimeout(READ_TIMEOUT, MILLISECONDS)
|
||||||
.apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
|
.apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
|
||||||
.addInterceptor { chain ->
|
.addInterceptor { chain ->
|
||||||
|
@ -83,7 +83,7 @@ class SubsonicAPIClient(
|
||||||
|
|
||||||
// Create the Retrofit instance, and register a special converter factory
|
// 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
|
// 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/")
|
.baseUrl("${config.baseUrl}/rest/")
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
.addConverterFactory(
|
.addConverterFactory(
|
||||||
|
|
|
@ -11,12 +11,11 @@ import java.io.File
|
||||||
import java.io.FileWriter
|
import java.io.FileWriter
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.concurrent.CountDownLatch
|
import okhttp3.Protocol
|
||||||
import java.util.concurrent.TimeUnit
|
import okhttp3.Response
|
||||||
import java.util.concurrent.TimeoutException
|
import okhttp3.ResponseBody
|
||||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException
|
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
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.AlbumListType.Companion.fromName
|
||||||
import org.moire.ultrasonic.api.subsonic.models.JukeboxAction
|
import org.moire.ultrasonic.api.subsonic.models.JukeboxAction
|
||||||
import org.moire.ultrasonic.api.subsonic.throwOnFailure
|
import org.moire.ultrasonic.api.subsonic.throwOnFailure
|
||||||
|
@ -53,7 +52,7 @@ import timber.log.Timber
|
||||||
*/
|
*/
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
open class RESTMusicService(
|
open class RESTMusicService(
|
||||||
subsonicAPIClient: SubsonicAPIClient,
|
val subsonicAPIClient: SubsonicAPIClient,
|
||||||
private val fileStorage: PermanentFileStorage,
|
private val fileStorage: PermanentFileStorage,
|
||||||
private val activeServerProvider: ActiveServerProvider
|
private val activeServerProvider: ActiveServerProvider
|
||||||
) : MusicService {
|
) : MusicService {
|
||||||
|
@ -479,35 +478,43 @@ open class RESTMusicService(
|
||||||
return Pair(response.stream!!, partial)
|
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)
|
@Throws(Exception::class)
|
||||||
override fun getVideoUrl(
|
override fun getVideoUrl(
|
||||||
id: String
|
id: String
|
||||||
): String {
|
): String {
|
||||||
// TODO This method should not exists as video should be loaded using stream method
|
// Create a new modified okhttp client to intercept the URL
|
||||||
// Previous method implementation uses assumption that video will be available
|
val builder = subsonicAPIClient.okHttpClient.newBuilder()
|
||||||
// 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
|
|
||||||
|
|
||||||
val latch = CountDownLatch(1)
|
builder.addInterceptor { chain ->
|
||||||
|
// Returns a dummy response
|
||||||
Thread(
|
Response.Builder()
|
||||||
{
|
.code(100)
|
||||||
expectedResult[0] = API.getStreamUrl(id)
|
.body(ResponseBody.create(null, ""))
|
||||||
latch.countDown()
|
.protocol(Protocol.HTTP_2)
|
||||||
},
|
.message("Empty response")
|
||||||
"Get-Video-Url"
|
.request(chain.request())
|
||||||
).start()
|
.build()
|
||||||
|
|
||||||
// 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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
@Throws(Exception::class)
|
||||||
|
|
Loading…
Reference in New Issue