Add stream call.

Also introduced helper method in SubsonicApiClient that handles error
cases.

Signed-off-by: Yahor Berdnikau <egorr.berd@gmail.com>
This commit is contained in:
Yahor Berdnikau 2017-11-05 22:14:02 +01:00
parent c8c8766b55
commit ba412721ac
7 changed files with 178 additions and 16 deletions

View File

@ -46,7 +46,7 @@ fun parseDate(dateAsString: String): Calendar {
return result
}
fun <T: SubsonicResponse> checkErrorCallParsed(mockWebServerRule: MockWebServerRule,
fun <T: SubsonicResponse> checkErrorCallParsed(mockWebServerRule : MockWebServerRule,
apiRequest: () -> Response<T>): T {
mockWebServerRule.enqueueResponse("generic_error_response.json")

View File

@ -19,7 +19,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
with(response) {
stream `should be` null
requestErrorCode `should be` null
responseHttpCode `should equal to` 200
apiError `should equal` SubsonicError.GENERIC
}
}
@ -33,7 +33,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
with(response) {
stream `should be` null
requestErrorCode `should equal` 404
responseHttpCode `should equal` 404
apiError `should be` null
}
}
@ -46,7 +46,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
val response = client.getCoverArt("some-id")
with(response) {
requestErrorCode `should be` null
responseHttpCode `should equal to` 200
apiError `should be` null
stream `should not be` null
val expectedContent = mockWebServerRule.loadJsonResponse("ping_ok.json")

View File

@ -0,0 +1,125 @@
package org.moire.ultrasonic.api.subsonic
import okhttp3.mockwebserver.MockResponse
import org.amshove.kluent.`should be`
import org.amshove.kluent.`should equal to`
import org.amshove.kluent.`should equal`
import org.amshove.kluent.`should not be`
import org.junit.Test
/**
* Integration test for [SubsonicAPIClient] for [SubsonicAPIDefinition.stream] call.
*/
class SubsonicApiStreamTest : SubsonicAPIClientTest() {
@Test
fun `Should handle api error response`() {
mockWebServerRule.enqueueResponse("generic_error_response.json")
val response = client.stream("some-id")
with(response) {
stream `should be` null
responseHttpCode `should equal to` 200
apiError `should equal` SubsonicError.GENERIC
}
}
@Test
fun `Should handle server error`() {
val httpErrorCode = 404
mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(httpErrorCode))
val response = client.stream("some-id")
with(response) {
stream `should be` null
responseHttpCode `should equal to` httpErrorCode
apiError `should be` null
}
}
@Test
fun `Should return successfull call stream`() {
mockWebServerRule.mockWebServer.enqueue(MockResponse()
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json")))
val response = client.stream("some-id")
with(response) {
responseHttpCode `should equal to` 200
apiError `should be` null
stream `should not be` null
val expectedContent = mockWebServerRule.loadJsonResponse("ping_ok.json")
stream!!.bufferedReader().readText() `should equal to` expectedContent
}
}
@Test
fun `Should pass id as parameter`() {
val id = "asdo123"
mockWebServerRule.assertRequestParam("ping_ok.json", id) {
client.api.stream(id = id).execute()
}
}
@Test
fun `Should pass max bit rate as param`() {
val maxBitRate = 360
mockWebServerRule.assertRequestParam("ping_ok.json",
"maxBitRate=$maxBitRate") {
client.api.stream("some-id", maxBitRate = maxBitRate).execute()
}
}
@Test
fun `Should pass format as param`() {
val format = "aac"
mockWebServerRule.assertRequestParam("ping_ok.json",
"format=$format") {
client.api.stream("some-id", format = format).execute()
}
}
@Test
fun `Should pass time offset as param`() {
val timeOffset = 155
mockWebServerRule.assertRequestParam("ping_ok.json",
"timeOffset=$timeOffset") {
client.api.stream("some-id", timeOffset = timeOffset).execute()
}
}
@Test
fun `Should pass video size as param`() {
val videoSize = "44144"
mockWebServerRule.assertRequestParam("ping_ok.json",
"size=$videoSize") {
client.api.stream("some-id", videoSize = videoSize).execute()
}
}
@Test
fun `Should pass estimate content length as param`() {
val estimateContentLength = true
mockWebServerRule.assertRequestParam("ping_ok.json",
"estimateContentLength=$estimateContentLength") {
client.api.stream("some-id", estimateContentLength = estimateContentLength).execute()
}
}
@Test
fun `Should pass converted as param`() {
val converted = false
mockWebServerRule.assertRequestParam("ping_ok.json",
"converted=$converted") {
client.api.stream("some-id", converted = converted).execute()
}
}
}

View File

@ -83,6 +83,20 @@ class SubsonicAPIClient(baseUrl: String,
api.getCoverArt(id, size).execute()
}
/**
* Convenient method to get media stream from api using item [id] and optional [maxBitrate].
*
* Optionally also you can provide [offset] that stream should start from.
*
* It detects the response `Content-Type` and tries to parse subsonic error if there is one.
*
* Prefer this method over [SubsonicAPIDefinition.stream] as this handles error cases.
*/
fun stream(id: String, maxBitrate: Int? = null, offset: Long? = null): StreamResponse =
handleStreamResponse {
api.stream(id, maxBitrate, offset = offset).execute()
}
private inline fun handleStreamResponse(apiCall: () -> Response<ResponseBody>): StreamResponse {
val response = apiCall()
return if (response.isSuccessful) {
@ -92,12 +106,13 @@ class SubsonicAPIClient(baseUrl: String,
contentType.type().equals("application", true) &&
contentType.subtype().equals("json", true)) {
val error = jacksonMapper.readValue<SubsonicResponse>(responseBody.byteStream())
StreamResponse(apiError = error.error)
StreamResponse(apiError = error.error, responseHttpCode = response.code())
} else {
StreamResponse(stream = responseBody.byteStream())
StreamResponse(stream = responseBody.byteStream(),
responseHttpCode = response.code())
}
} else {
StreamResponse(requestErrorCode = response.code())
StreamResponse(responseHttpCode = response.code())
}
}

View File

@ -24,6 +24,7 @@ import org.moire.ultrasonic.api.subsonic.response.SearchTwoResponse
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
import retrofit2.http.Streaming
@ -167,4 +168,15 @@ interface SubsonicAPIDefinition {
@GET("getCoverArt.view")
fun getCoverArt(@Query("id") id: String,
@Query("size") size: Long? = null): Call<ResponseBody>
@Streaming
@GET("stream.view")
fun stream(@Query("id") id: String,
@Query("maxBitRate") maxBitRate: Int? = null,
@Query("format") format: String? = null,
@Query("timeOffset") timeOffset: Int? = null,
@Query("size") videoSize: String? = null,
@Query("estimateContentLength") estimateContentLength: Boolean? = null,
@Query("converted") converted: Boolean? = null,
@Header("Range") offset: Long? = null): Call<ResponseBody>
}

View File

@ -5,15 +5,15 @@ import java.io.InputStream
/**
* Special response that contains either [stream] of data from api, or [apiError],
* or [requestErrorCode].
* or [responseHttpCode].
*
* [requestErrorCode] will be only if there problem on http level.
* [responseHttpCode] will be there always.
*/
class StreamResponse(val stream: InputStream? = null,
val apiError: SubsonicError? = null,
val requestErrorCode: Int? = null) {
val responseHttpCode: Int) {
/**
* Check if this response has error.
*/
fun hasError(): Boolean = apiError != null || requestErrorCode != null
fun hasError(): Boolean = apiError != null || responseHttpCode !in 200..300
}

View File

@ -10,16 +10,26 @@ import org.moire.ultrasonic.api.subsonic.SubsonicError.GENERIC
class StreamResponseTest {
@Test
fun `Should have error if subsonic error is not null`() {
StreamResponse(apiError = GENERIC).hasError() `should equal to` true
StreamResponse(apiError = GENERIC, responseHttpCode = 200).hasError() `should equal to` true
}
@Test
fun `Should have error if http error is not null`() {
StreamResponse(requestErrorCode = 500).hasError() `should equal to` true
fun `Should have error if http error is greater then 300`() {
StreamResponse(responseHttpCode = 301).hasError() `should equal to` true
}
@Test
fun `Should not have error if subsonic error and http error is null`() {
StreamResponse().hasError() `should equal to` false
fun `Should have error of http error code is lower then 200`() {
StreamResponse(responseHttpCode = 199).hasError() `should equal to` true
}
@Test
fun `Should not have error if http code is 200`() {
StreamResponse(responseHttpCode = 200).hasError() `should equal to` false
}
@Test
fun `Should not have error if http code is 300`() {
StreamResponse(responseHttpCode = 300).hasError() `should equal to` false
}
}