mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-14 18:50:51 +01:00
Merge branch 'develop' into AndroidAuto
This commit is contained in:
commit
3853fce818
@ -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 [SubsonicAPIClient.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&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.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.getStreamUrl(id)
|
||||
|
||||
streamUrl `should be equal to` expectedUrl
|
||||
}
|
||||
}
|
@ -7,14 +7,14 @@ import org.amshove.kluent.`should not be`
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Integration test for [SubsonicAPIClient.getAvatar] call.
|
||||
* Integration test for [SubsonicAPIDefinition.getAvatar] call.
|
||||
*/
|
||||
class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
|
||||
@Test
|
||||
fun `Should handle api error response`() {
|
||||
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
|
||||
|
||||
val response = client.getAvatar("some")
|
||||
val response = client.api.getAvatar("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
@ -28,7 +28,7 @@ class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
|
||||
val httpErrorCode = 500
|
||||
mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(httpErrorCode))
|
||||
|
||||
val response = client.getAvatar("some")
|
||||
val response = client.api.getAvatar("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
stream `should be equal to` null
|
||||
@ -44,7 +44,7 @@ class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
|
||||
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json"))
|
||||
)
|
||||
|
||||
val response = client.stream("some")
|
||||
val response = client.api.stream("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
responseHttpCode `should be equal to` 200
|
||||
|
@ -14,7 +14,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
|
||||
fun `Should handle api error response`() {
|
||||
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
|
||||
|
||||
val response = client.getCoverArt("some-id")
|
||||
val response = client.api.getCoverArt("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
@ -28,7 +28,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
|
||||
val httpErrorCode = 404
|
||||
mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(httpErrorCode))
|
||||
|
||||
val response = client.getCoverArt("some-id")
|
||||
val response = client.api.getCoverArt("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
@ -44,7 +44,7 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
|
||||
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json"))
|
||||
)
|
||||
|
||||
val response = client.getCoverArt("some-id")
|
||||
val response = client.api.getCoverArt("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
responseHttpCode `should be equal to` 200
|
||||
|
@ -14,7 +14,7 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
|
||||
fun `Should handle api error response`() {
|
||||
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
|
||||
|
||||
val response = client.stream("some-id")
|
||||
val response = client.api.stream("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
@ -28,7 +28,7 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
|
||||
val httpErrorCode = 404
|
||||
mockWebServerRule.mockWebServer.enqueue(MockResponse().setResponseCode(httpErrorCode))
|
||||
|
||||
val response = client.stream("some-id")
|
||||
val response = client.api.stream("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
stream `should be` null
|
||||
@ -38,13 +38,13 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should return successfull call stream`() {
|
||||
fun `Should return successful call stream`() {
|
||||
mockWebServerRule.mockWebServer.enqueue(
|
||||
MockResponse()
|
||||
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json"))
|
||||
)
|
||||
|
||||
val response = client.stream("some-id")
|
||||
val response = client.api.stream("some-id").execute().toStreamResponse()
|
||||
|
||||
with(response) {
|
||||
responseHttpCode `should be equal to` 200
|
||||
|
@ -45,7 +45,8 @@ import retrofit2.Call
|
||||
@Suppress("TooManyFunctions")
|
||||
internal class ApiVersionCheckWrapper(
|
||||
val api: SubsonicAPIDefinition,
|
||||
var currentApiVersion: SubsonicAPIVersions
|
||||
var currentApiVersion: SubsonicAPIVersions,
|
||||
var isRealProtocolVersion: Boolean = false
|
||||
) : SubsonicAPIDefinition by api {
|
||||
override fun getArtists(musicFolderId: String?): Call<GetArtistsResponse> {
|
||||
checkVersion(V1_8_0)
|
||||
@ -325,10 +326,15 @@ internal class ApiVersionCheckWrapper(
|
||||
}
|
||||
|
||||
private fun checkVersion(expectedVersion: SubsonicAPIVersions) {
|
||||
if (currentApiVersion < expectedVersion) throw ApiNotSupportedException(currentApiVersion)
|
||||
// If it is true, it is probably the first call with this server
|
||||
if (!isRealProtocolVersion) return
|
||||
if (currentApiVersion < expectedVersion)
|
||||
throw ApiNotSupportedException(currentApiVersion)
|
||||
}
|
||||
|
||||
private fun checkParamVersion(param: Any?, expectedVersion: SubsonicAPIVersions) {
|
||||
// If it is true, it is probably the first call with this server
|
||||
if (!isRealProtocolVersion) return
|
||||
if (param != null) {
|
||||
checkVersion(expectedVersion)
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import java.io.IOException
|
||||
import okhttp3.ResponseBody
|
||||
import org.moire.ultrasonic.api.subsonic.response.StreamResponse
|
||||
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||
import retrofit2.Response
|
||||
|
||||
/**
|
||||
* Converts a Response to a StreamResponse
|
||||
*/
|
||||
fun Response<out ResponseBody>.toStreamResponse(): StreamResponse {
|
||||
val response = this
|
||||
return if (response.isSuccessful) {
|
||||
val responseBody = response.body()
|
||||
val contentType = responseBody?.contentType()
|
||||
if (
|
||||
contentType != null &&
|
||||
contentType.type().equals("application", true) &&
|
||||
contentType.subtype().equals("json", true)
|
||||
) {
|
||||
val error = SubsonicAPIClient.jacksonMapper.readValue<SubsonicResponse>(
|
||||
responseBody.byteStream()
|
||||
)
|
||||
StreamResponse(apiError = error.error, responseHttpCode = response.code())
|
||||
} else {
|
||||
StreamResponse(
|
||||
stream = responseBody?.byteStream(),
|
||||
responseHttpCode = response.code()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
StreamResponse(responseHttpCode = response.code())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This extension checks API call results for errors, API version, etc
|
||||
* It creates Exceptions from the results returned by the Subsonic API
|
||||
*/
|
||||
@Suppress("ThrowsCount")
|
||||
fun <T : SubsonicResponse> Response<out T>.throwOnFailure(): Response<out T> {
|
||||
val response = this
|
||||
|
||||
if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) {
|
||||
return this as Response<T>
|
||||
}
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Server error, code: " + response.code())
|
||||
} else if (
|
||||
response.body()!!.status === SubsonicResponse.Status.ERROR &&
|
||||
response.body()!!.error != null
|
||||
) {
|
||||
throw SubsonicRESTException(response.body()!!.error!!)
|
||||
} else {
|
||||
throw IOException("Failed to perform request: " + response.code())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This extension checks API call results for errors, API version, etc
|
||||
* @return Boolean: True if everything was ok, false if an error was found
|
||||
*/
|
||||
fun Response<out SubsonicResponse>.falseOnFailure(): Boolean {
|
||||
return (this.isSuccessful && this.body()!!.status === SubsonicResponse.Status.OK)
|
||||
}
|
||||
|
||||
/**
|
||||
* This call wraps Subsonic API calls so their results can be checked for errors, API version, etc
|
||||
* It creates Exceptions from a StreamResponse
|
||||
*/
|
||||
fun StreamResponse.throwOnFailure(): StreamResponse {
|
||||
val response = this
|
||||
if (response.hasError() || response.stream == null) {
|
||||
if (response.apiError != null) {
|
||||
throw SubsonicRESTException(response.apiError)
|
||||
} else {
|
||||
throw IOException(
|
||||
"Failed to make endpoint request, code: " + response.responseHttpCode
|
||||
)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
@ -3,7 +3,6 @@ package org.moire.ultrasonic.api.subsonic
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
@ -18,7 +17,6 @@ import org.moire.ultrasonic.api.subsonic.interceptors.ProxyPasswordInterceptor
|
||||
import org.moire.ultrasonic.api.subsonic.interceptors.RangeHeaderInterceptor
|
||||
import org.moire.ultrasonic.api.subsonic.interceptors.VersionInterceptor
|
||||
import org.moire.ultrasonic.api.subsonic.response.StreamResponse
|
||||
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@ -48,18 +46,23 @@ class SubsonicAPIClient(
|
||||
config.enableLdapUserSupport
|
||||
)
|
||||
|
||||
var onProtocolChange: (SubsonicAPIVersions) -> Unit = {}
|
||||
|
||||
/**
|
||||
* Get currently used protocol version.
|
||||
* The currently used protocol version.
|
||||
* The setter also updates the interceptors and callback (if registered)
|
||||
*/
|
||||
var protocolVersion = config.minimalProtocolVersion
|
||||
private set(value) {
|
||||
field = value
|
||||
proxyPasswordInterceptor.apiVersion = field
|
||||
wrappedApi.currentApiVersion = field
|
||||
wrappedApi.isRealProtocolVersion = true
|
||||
versionInterceptor.protocolVersion = field
|
||||
onProtocolChange(field)
|
||||
}
|
||||
|
||||
private val okHttpClient = baseOkClient.newBuilder()
|
||||
val okHttpClient: OkHttpClient = baseOkClient.newBuilder()
|
||||
.readTimeout(READ_TIMEOUT, MILLISECONDS)
|
||||
.apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
|
||||
.addInterceptor { chain ->
|
||||
@ -78,18 +81,19 @@ class SubsonicAPIClient(
|
||||
.apply { if (config.debug) addLogging() }
|
||||
.build()
|
||||
|
||||
private val jacksonMapper = ObjectMapper()
|
||||
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
|
||||
.registerModule(KotlinModule())
|
||||
|
||||
private val retrofit = Retrofit.Builder()
|
||||
// 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
|
||||
val retrofit: Retrofit = Retrofit.Builder()
|
||||
.baseUrl("${config.baseUrl}/rest/")
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(
|
||||
VersionAwareJacksonConverterFactory.create(
|
||||
{ protocolVersion = it },
|
||||
{
|
||||
// Only trigger update on change, or if still using the default
|
||||
if (protocolVersion != it || !config.isRealProtocolVersion) {
|
||||
protocolVersion = it
|
||||
}
|
||||
},
|
||||
jacksonMapper
|
||||
)
|
||||
)
|
||||
@ -97,90 +101,12 @@ class SubsonicAPIClient(
|
||||
|
||||
private val wrappedApi = ApiVersionCheckWrapper(
|
||||
retrofit.create(SubsonicAPIDefinition::class.java),
|
||||
config.minimalProtocolVersion
|
||||
config.minimalProtocolVersion,
|
||||
config.isRealProtocolVersion
|
||||
)
|
||||
|
||||
val api: SubsonicAPIDefinition get() = wrappedApi
|
||||
|
||||
/**
|
||||
* TODO: Remove this in favour of handling the stream response inside RESTService
|
||||
* Convenient method to get cover art from api using item [id] and optional maximum [size].
|
||||
*
|
||||
* It detects the response `Content-Type` and tries to parse subsonic error if there is one.
|
||||
*
|
||||
* Prefer this method over [SubsonicAPIDefinition.getCoverArt] as this handles error cases.
|
||||
*/
|
||||
fun getCoverArt(id: String, size: Long? = null): StreamResponse = handleStreamResponse {
|
||||
api.getCoverArt(id, size).execute()
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Remove this in favour of handling the stream response inside RESTService
|
||||
* 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()
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Remove this in favour of handling the stream response inside RESTService
|
||||
* Convenient method to get user avatar using [username].
|
||||
*
|
||||
* It detects the response `Content-Type` and tries to parse subsonic error if there is one.
|
||||
*
|
||||
* Prefer this method over [SubsonicAPIDefinition.getAvatar] as this handles error cases.
|
||||
*/
|
||||
fun getAvatar(username: String): StreamResponse = handleStreamResponse {
|
||||
api.getAvatar(username).execute()
|
||||
}
|
||||
|
||||
// TODO: Move this to response checker
|
||||
private inline fun handleStreamResponse(apiCall: () -> Response<ResponseBody>): StreamResponse {
|
||||
val response = apiCall()
|
||||
return if (response.isSuccessful) {
|
||||
val responseBody = response.body()
|
||||
val contentType = responseBody?.contentType()
|
||||
if (
|
||||
contentType != null &&
|
||||
contentType.type().equals("application", true) &&
|
||||
contentType.subtype().equals("json", true)
|
||||
) {
|
||||
val error = jacksonMapper.readValue<SubsonicResponse>(responseBody.byteStream())
|
||||
StreamResponse(apiError = error.error, responseHttpCode = response.code())
|
||||
} else {
|
||||
StreamResponse(
|
||||
stream = responseBody?.byteStream(),
|
||||
responseHttpCode = response.code()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
StreamResponse(responseHttpCode = response.code())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stream url.
|
||||
*
|
||||
* Calling this method do actual connection to the backend, though not downloading all content.
|
||||
*
|
||||
* Consider do not use this method, but [stream] call.
|
||||
*/
|
||||
fun getStreamUrl(id: String): String {
|
||||
val request = api.stream(id).execute()
|
||||
val url = request.raw().request().url().toString()
|
||||
if (request.isSuccessful) {
|
||||
request.body()?.close()
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.addLogging() {
|
||||
val loggingInterceptor = HttpLoggingInterceptor(okLogger)
|
||||
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||
@ -202,4 +128,19 @@ class SubsonicAPIClient(
|
||||
|
||||
hostnameVerifier { _, _ -> true }
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is necessary because Mockito has problems with stubbing chained calls
|
||||
*/
|
||||
fun toStreamResponse(call: Response<ResponseBody>): StreamResponse {
|
||||
return call.toStreamResponse()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val jacksonMapper: ObjectMapper = ObjectMapper()
|
||||
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
|
||||
.registerModule(KotlinModule())
|
||||
}
|
||||
}
|
||||
|
@ -11,5 +11,6 @@ data class SubsonicClientConfiguration(
|
||||
val clientID: String,
|
||||
val allowSelfSignedCertificate: Boolean = false,
|
||||
val enableLdapUserSupport: Boolean = false,
|
||||
val debug: Boolean = false
|
||||
val debug: Boolean = false,
|
||||
val isRealProtocolVersion: Boolean = false
|
||||
)
|
||||
|
@ -1,6 +1,4 @@
|
||||
package org.moire.ultrasonic.service
|
||||
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicError
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
/**
|
||||
* Exception returned by API with given `code`.
|
@ -63,7 +63,6 @@ class VersionAwareJacksonConverterFactory(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SwallowedException")
|
||||
class VersionAwareResponseBodyConverter<T> (
|
||||
private val notifier: (SubsonicAPIVersions) -> Unit = {},
|
||||
private val adapter: ObjectReader
|
||||
@ -77,7 +76,7 @@ class VersionAwareJacksonConverterFactory(
|
||||
if (response is SubsonicResponse) {
|
||||
try {
|
||||
notifier(response.version)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
} catch (ignored: IllegalArgumentException) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import org.moire.ultrasonic.api.subsonic.models.AlbumListType.BY_GENRE
|
||||
*/
|
||||
class ApiVersionCheckWrapperTest {
|
||||
private val apiMock = mock<SubsonicAPIDefinition>()
|
||||
private val wrapper = ApiVersionCheckWrapper(apiMock, V1_1_0)
|
||||
private val wrapper = ApiVersionCheckWrapper(apiMock, V1_1_0, isRealProtocolVersion = true)
|
||||
|
||||
@Test
|
||||
fun `Should just call real api for ping`() {
|
||||
|
@ -5,14 +5,11 @@
|
||||
<ID>ComplexCondition:DownloadHandler.kt$DownloadHandler.<no name provided>$!append && !playNext && !unpin && !background</ID>
|
||||
<ID>ComplexCondition:FilePickerAdapter.kt$FilePickerAdapter$currentDirectory.absolutePath == "/" || currentDirectory.absolutePath == "/storage" || currentDirectory.absolutePath == "/storage/emulated" || currentDirectory.absolutePath == "/mnt"</ID>
|
||||
<ID>ComplexCondition:LocalMediaPlayer.kt$LocalMediaPlayer$Util.getGaplessPlaybackPreference() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && ( playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED )</ID>
|
||||
<ID>ComplexCondition:SongView.kt$SongView$TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo && Util.getVideoPlayerType() !== VideoPlayerType.FLASH</ID>
|
||||
<ID>ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>ComplexMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File)</ID>
|
||||
<ID>ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean)</ID>
|
||||
<ID>ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun enableButtons()</ID>
|
||||
<ID>ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
||||
<ID>EmptyCatchBlock:LocalMediaPlayer.kt$LocalMediaPlayer${ }</ID>
|
||||
<ID>EmptyDefaultConstructor:VideoPlayer.kt$VideoPlayer$()</ID>
|
||||
<ID>EmptyFunctionBlock:SongView.kt$SongView${}</ID>
|
||||
<ID>FunctionNaming:ThemeChangedEventDistributor.kt$ThemeChangedEventDistributor$fun RaiseThemeChangedEvent()</ID>
|
||||
<ID>ImplicitDefaultLocale:DownloadFile.kt$DownloadFile$String.format("DownloadFile (%s)", song)</ID>
|
||||
@ -29,7 +26,6 @@
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix)</ID>
|
||||
<ID>LargeClass:RESTMusicService.kt$RESTMusicService : MusicService</ID>
|
||||
<ID>LargeClass:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment</ID>
|
||||
<ID>LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||
@ -59,7 +55,6 @@
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$3</ID>
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$4</ID>
|
||||
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$206</ID>
|
||||
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$5</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$3</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$4</ID>
|
||||
<ID>MagicNumber:SongView.kt$SongView$60</ID>
|
||||
@ -68,14 +63,10 @@
|
||||
<ID>NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean )</ID>
|
||||
<ID>NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
||||
<ID>ReturnCount:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String</ID>
|
||||
<ID>ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap?</ID>
|
||||
<ID>ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean</ID>
|
||||
<ID>ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
||||
<ID>SwallowedException:NavigationActivity.kt$NavigationActivity$catch (e: Resources.NotFoundException) { destination.id.toString() }</ID>
|
||||
<ID>ThrowsCount:ApiCallResponseChecker.kt$ApiCallResponseChecker.Companion$@Throws(SubsonicRESTException::class, IOException::class) fun checkResponseSuccessful(response: Response<out SubsonicResponse>)</ID>
|
||||
<ID>TooGenericExceptionCaught:DownloadFile.kt$DownloadFile$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:FileLoggerTree.kt$FileLoggerTree$x: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$e: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$exception: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$x: Exception</ID>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,85 +0,0 @@
|
||||
package org.moire.ultrasonic.Test.service;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
/**
|
||||
* Created by rcocula on 11/03/2016.
|
||||
*/
|
||||
public class GetPodcastEpisodesTestReaderProvider {
|
||||
|
||||
private static String data = "<subsonic-response status=\"ok\" version=\"1.12.0\" xmlns=\"http://subsonic.org/restapi\">\n" +
|
||||
" <podcasts>\n" +
|
||||
" <channel id=\"0\" url=\"http://radiofrance-podcast.net/podcast09/rss_13183.xml\" title=\"La tribune des critiques de disques\" description=\"Sous la houlette de Jérémie Rousseau, d'éminents critiques musicaux écoutent à l'aveugle différentes versions d'une oeuvre du répertoire et la commentent\" status=\"completed\">\n" +
|
||||
" <episode id=\"2551\" parent=\"42169\" isDir=\"false\" title=\"2ème Sonate, Op. 35 de Frédéric Chopin.\" album=\"2ème Sonate, Op. 35 de Frédéric Chopin.\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87140480\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5428\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-06.03.2016-ITEMA_20929680-0.mp3\" isVideo=\"false\" created=\"2016-03-06T21:13:09.000Z\" albumId=\"4089\" artistId=\"1457\" type=\"podcast\" streamId=\"54710\" description=\"durée : 01:30:16 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec la participation d'Elsa Fottorino, Stéphane Friédérich et Piotr Kaminski. - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-03-06T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2513\" parent=\"42169\" isDir=\"false\" title=\"Harmonielehre de John Adams\" album=\"La tribune des critiques de disques\" coverArt=\"42169\" size=\"639\" contentType=\"audio/mpeg\" suffix=\"mp3\" path=\"La tribune des critiques de disques/13183-28.02.2016-ITEMA_20924164-0.mp3\" isVideo=\"false\" created=\"2016-02-28T21:38:56.000Z\" type=\"podcast\" streamId=\"54674\" description=\"durée : 01:30:27 - La tribune des critiques de disques - par : Jérémie Rousseau - Bertrand Dermoncourt, Classica - Emmanuelle Guiliani, La Croix - Jean-Charles Hoffelé, Diapason - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-02-28T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2465\" parent=\"42169\" isDir=\"false\" title=\"Le Barbier de Séville (Acte I) de Gioachino Rossini\" album=\"Le Barbier de Séville (Acte I) de Gioachino Rossini\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87089280\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5424\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-21.02.2016-ITEMA_20918652-0.mp3\" isVideo=\"false\" created=\"2016-02-22T04:13:19.000Z\" albumId=\"3955\" artistId=\"1457\" type=\"podcast\" streamId=\"54629\" description=\"durée : 01:30:12 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Chantal Cazaux, Emmanuel Dupuy et Sylvain Fort - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-02-21T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2432\" parent=\"42169\" isDir=\"false\" title=\"Suite en La de Jean-Philippe Rameau\" album=\"Suite en La de Jean-Philippe Rameau\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86632576\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5396\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-14.02.2016-ITEMA_20913147-0.mp3\" isVideo=\"false\" created=\"2016-02-16T22:16:23.000Z\" albumId=\"3929\" artistId=\"1457\" type=\"podcast\" streamId=\"54563\" description=\"durée : 01:29:44 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Elsa Fottorino, Piotr Kaminski, Philippe Venturini - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-02-14T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2397\" parent=\"42169\" isDir=\"false\" title=\"La Valse de l'Empereur de Johann Strauss\" album=\"La Valse de l'Empereur de Johann Strauss\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87042176\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5421\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-07.02.2016-ITEMA_20907545-0.mp3\" isVideo=\"false\" created=\"2016-02-09T04:13:46.000Z\" albumId=\"3893\" artistId=\"1457\" type=\"podcast\" streamId=\"54491\" description=\"durée : 01:30:09 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Séverine Garnier, Emmanuelle Giuliani, Christian Merlin - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-02-07T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2328\" parent=\"42169\" isDir=\"false\" title=\"Concerto pour piano n° 22 en mi bémol majeur K 482 de Wolfgang Amadeus Mozart\" album=\"Concerto pour piano n° 22 en mi bémol majeur K 482 de Wolfgang Amadeus Mozart\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87261312\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5435\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-31.01.2016-ITEMA_20901964-00.mp3\" isVideo=\"false\" created=\"2016-02-04T04:13:08.000Z\" albumId=\"3872\" artistId=\"1457\" type=\"podcast\" streamId=\"54400\" description=\"durée : 01:30:23 - La tribune des critiques de disques - par : Jérémie Rousseau - Sylvain Fort, Forum Opéra - Elsa Fottorino, Revue Pianiste - Christian Merlin, Le Figaro - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-01-31T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2321\" parent=\"42169\" isDir=\"false\" title=\"Concerto pour piano n° 22 en mi bémol majeur K 482 de Wolfgang Amadeus Mozart\" album=\"Concerto pour piano n° 22 en mi bémol majeur K 482 de Wolfgang Amadeus Mozart\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87261312\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5435\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-31.01.2016-ITEMA_20901964-0.mp3\" isVideo=\"false\" created=\"2016-02-03T21:23:06.000Z\" albumId=\"3872\" artistId=\"1457\" type=\"podcast\" streamId=\"54385\" description=\"durée : 01:30:23 - La tribune des critiques de disques - par : Jérémie Rousseau - Sylvain Fort, Forum Opéra - Elsa Fottorino, Revue Pianiste - Christian Merlin, Le Figaro - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-01-31T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2267\" parent=\"42169\" isDir=\"false\" title=\"Symphonie n° 2 "Le Double" d'Henri Dutilleux\" album=\"Symphonie n° 2 ''Le Double'' d'Henri Dutilleux\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86952064\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5416\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-24.01.2016-ITEMA_20896460-0.mp3\" isVideo=\"false\" created=\"2016-01-25T04:13:51.000Z\" albumId=\"3829\" artistId=\"1457\" type=\"podcast\" streamId=\"54245\" description=\"durée : 01:30:04 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Bertrand Dermoncourt, Emmanuelle Giuliani, Jean-Charles Hoffelé - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-01-24T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2225\" parent=\"42169\" isDir=\"false\" title=\"Gymnopédies de Erik Satie\" album=\"Gymnopédies de Erik Satie\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86579328\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5392\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-17.01.2016-ITEMA_20890984-0.mp3\" isVideo=\"false\" created=\"2016-01-18T04:13:13.000Z\" albumId=\"3792\" artistId=\"1457\" type=\"podcast\" streamId=\"54089\" description=\"durée : 01:29:40 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Bertrand Dermoncourt, Elsa Fottorino, Antoine Mignon - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-01-17T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2189\" parent=\"42169\" isDir=\"false\" title=\"Quatuor n° 13 en la mineur, D. 804 (op. 29) 'Rosamunde" de Franz Schubert\" album=\"Quatuor n° 13 en la mineur, D. 804 (op. 29) 'Rosamunde'' de Franz Schubert\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86990976\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5418\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-10.01.2016-ITEMA_20885378-0.mp3\" isVideo=\"false\" created=\"2016-01-11T04:14:15.000Z\" albumId=\"3764\" artistId=\"1457\" type=\"podcast\" streamId=\"53982\" description=\"durée : 01:30:06 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Jérémie Cahen, Piotr Kaminski, Philippe Venturini - réalisé par : Marie Grout\" status=\"completed\" publishDate=\"2016-01-10T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2146\" parent=\"42169\" isDir=\"false\" title=\"Les Quatre Derniers Lieder de Richard Strauss\" album=\"Les Quatre Derniers Lieder de Richard Strauss\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87040128\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5421\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-03.01.2016-ITEMA_20879946-0.mp3\" isVideo=\"false\" created=\"2016-01-04T04:12:55.000Z\" albumId=\"3735\" artistId=\"1457\" type=\"podcast\" streamId=\"53939\" description=\"durée : 01:30:09 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Christian Merlin, Emmanuelle Giuliani et Eric Taver - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2016-01-03T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2108\" parent=\"42169\" isDir=\"false\" title=\""Une petite musique de nuit", Sérénade n° 13 en sol Majeur de Wolfgang-Amadeus Mozart\" album=\"''Une petite musique de nuit'', Sérénade n° 13 en sol Majeur de Wolfgang-Amadeus Mozart\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86259840\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5372\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-27.12.2015-ITEMA_20874498-0.mp3\" isVideo=\"false\" created=\"2015-12-28T04:12:57.000Z\" albumId=\"3708\" artistId=\"1457\" type=\"podcast\" streamId=\"53865\" description=\"durée : 01:29:20 - La tribune des critiques de disques - par : Jérémie Rousseau - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-12-27T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2070\" parent=\"42169\" isDir=\"false\" title=\"La Cantate BWV 61 "Nun komm, der Heiden Heiland" de Jean-Sébastien Bach\" album=\"La Cantate BWV 61 ''Nun komm, der Heiden Heiland'' de Jean-Sébastien Bach\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87079040\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5424\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-20.12.2015-ITEMA_20868893-0.mp3\" isVideo=\"false\" created=\"2015-12-21T04:13:10.000Z\" albumId=\"3670\" artistId=\"1457\" type=\"podcast\" streamId=\"53761\" description=\"durée : 01:30:12 - La tribune des critiques de disques - par : Jérémie Rousseau - Emission enregistrée en public jeudi 10 décembre au studio 109 à 19h. Avec la participation d'Emmanuel Dupuy, Séverine Garnier et Emmanuelle Giuliani. - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-12-20T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"2025\" parent=\"42169\" isDir=\"false\" title=\"Les Notations pour piano de Pierre Boulez\" album=\"Les Notations pour piano de Pierre Boulez\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86775936\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5405\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-13.12.2015-ITEMA_20863386-0.mp3\" isVideo=\"false\" created=\"2015-12-14T04:13:16.000Z\" albumId=\"3626\" artistId=\"1457\" type=\"podcast\" streamId=\"53522\" description=\"durée : 01:29:53 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Jérémie Bigorie, Jérémie Cahen et Christian Merlin. - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-12-13T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1983\" parent=\"42169\" isDir=\"false\" title=\"Les Scènes d'enfants op.15 de Robert Schumann\" album=\"Les Scènes d'enfants op.15 de Robert Schumann\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87011456\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5419\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-06.12.2015-ITEMA_20857958-0.mp3\" isVideo=\"false\" created=\"2015-12-07T04:12:55.000Z\" albumId=\"3593\" artistId=\"1457\" type=\"podcast\" streamId=\"53437\" description=\"durée : 01:30:07 - La tribune des critiques de disques - par : Jérémie Rousseau - avec Elsa Fottorino, Jean-Charles Hoffelé et Antoine Mignon. - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-12-06T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1937\" parent=\"42169\" isDir=\"false\" title=\"Concerto pour piano n°1 en mi bémol Majeur de Franz Liszt\" album=\"Concerto pour piano n°1 en mi bémol Majeur de Franz Liszt\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86730880\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5402\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-29.11.2015-ITEMA_20852326-0.mp3\" isVideo=\"false\" created=\"2015-11-30T04:12:27.000Z\" albumId=\"3570\" artistId=\"1457\" type=\"podcast\" streamId=\"53388\" description=\"durée : 01:29:50 - La tribune des critiques de disques - par : Jérémie Rousseau - Emission enregistrée le jeudi 19 novembre au studio 109 à 19h. Avec la participation d'Elsa Fottorino, Stéphane Friédérich et Sylvain Fort. - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-11-29T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1810\" parent=\"42169\" isDir=\"false\" title=\"Le Concerto pour deux mandolines en sol Majeur RV 532 de Vivaldi\" album=\"Le Concerto pour deux mandolines en sol Majeur RV 532 de Vivaldi\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86470784\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5386\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-15.11.2015-ITEMA_20841620-0.mp3\" isVideo=\"false\" created=\"2015-11-16T04:12:27.000Z\" albumId=\"3512\" artistId=\"1457\" type=\"podcast\" streamId=\"53214\" description=\"durée : 01:29:34 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Bigorie, Emmanuelle Giuliani, Piotr Kaminski - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-11-15T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1778\" parent=\"42169\" isDir=\"false\" title=\"Les Contes d'Hoffmann d'Offenbach (acte II)\" album=\"Les Contes d'Hoffmann d'Offenbach (acte II)\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87195776\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5431\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-08.11.2015-ITEMA_20836265-0.mp3\" isVideo=\"false\" created=\"2015-11-09T04:12:48.000Z\" albumId=\"3478\" artistId=\"1457\" type=\"podcast\" streamId=\"53159\" description=\"durée : 01:30:19 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Chantal Cazaux, Emmanuel Dupuy, Sylvain Fort - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-11-08T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1721\" parent=\"42169\" isDir=\"false\" title=\"Symphonie n°7 de Bruckner\" album=\"Symphonie n°7 de Bruckner\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87429248\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5446\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-01.11.2015-ITEMA_20830962-00.mp3\" isVideo=\"false\" created=\"2015-11-03T21:57:28.000Z\" albumId=\"3441\" artistId=\"1457\" type=\"podcast\" streamId=\"51952\" description=\"durée : 01:30:34 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Cahen, Christian Merlin, Philippe Venturini - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-11-01T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1658\" parent=\"42169\" isDir=\"false\" title=\"Jean Sebastien Bach : Partita n°1 en si bémol Majeur BWV 825\" album=\"Jean Sebastien Bach : Partita n°1 en si bémol Majeur BWV 825\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86999168\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5419\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-25.10.2015-ITEMA_20825686-0.mp3\" isVideo=\"false\" created=\"2015-10-26T04:09:47.000Z\" albumId=\"3328\" artistId=\"1457\" type=\"podcast\" streamId=\"50164\" description=\"durée : 01:30:07 - La tribune des critiques de disques - par : Jérémie Rousseau - réalisé par : Sylvain Richard\" status=\"completed\" publishDate=\"2015-10-25T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1618\" parent=\"42169\" isDir=\"false\" title=\"Tchaïkovsky : Sérénade pour cordes\" album=\"Tchaïkovsky : Sérénade pour cordes\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87337088\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5440\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-18.10.2015-ITEMA_20820416-0.mp3\" isVideo=\"false\" created=\"2015-10-19T03:10:05.000Z\" albumId=\"3327\" artistId=\"1457\" type=\"podcast\" streamId=\"49791\" description=\"durée : 01:30:28 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jean-Charles Hoffelé, Antoine Mignon, Eric Taver - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-10-18T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1581\" parent=\"42169\" isDir=\"false\" title=\"Arvo Pärt : Magnificat\" album=\"Arvo Pärt : Magnificat\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86837376\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5408\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-11.10.2015-ITEMA_20815299-0.mp3\" isVideo=\"false\" created=\"2015-10-12T03:10:01.000Z\" albumId=\"3326\" artistId=\"1457\" type=\"podcast\" streamId=\"49731\" description=\"durée : 01:29:57 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Bertrand Dermoncourt, Séverine Garnier, Emmanuelle Giuliani - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-10-11T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1538\" parent=\"42169\" isDir=\"false\" title=\"Scherzos de Chopin\" album=\"Scherzos de Chopin\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87048320\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5422\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-04.10.2015-ITEMA_20810281-0.mp3\" isVideo=\"false\" created=\"2015-10-04T20:20:32.000Z\" albumId=\"3325\" artistId=\"1457\" type=\"podcast\" streamId=\"49693\" description=\"durée : 01:30:10 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Elsa Fottorino, Sylvain Fort, Christian Merlin - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-10-04T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1502\" parent=\"42169\" isDir=\"false\" title=\"Haendel : Dixit Dominus\" album=\"Haendel : Dixit Dominus\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87736448\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5465\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-27.09.2015-ITEMA_20805338-0.mp3\" isVideo=\"false\" created=\"2015-09-28T18:31:27.000Z\" albumId=\"3324\" artistId=\"1457\" type=\"podcast\" streamId=\"49634\" description=\"durée : 01:30:53 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Chantal Cazaux, Emmanuel Dupuy, Emmanuelle Giuliani - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-09-27T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1458\" parent=\"42169\" isDir=\"false\" title=\"Beethoven : Sonate n°23 "Appassionata"\" album=\"Beethoven : Sonate n°23 ''Appassionata''\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86859904\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5410\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-20.09.2015-ITEMA_20800368-0.mp3\" isVideo=\"false\" created=\"2015-09-21T03:14:46.000Z\" albumId=\"3300\" artistId=\"1457\" type=\"podcast\" streamId=\"49559\" description=\"durée : 01:29:58 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Elsa Fottorino, Christian Merlin, Antoine Mignon - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-09-20T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"1417\" parent=\"42169\" isDir=\"false\" title=\"Verdi : Le Trouvère (acte II)\" album=\"Verdi : Le Trouvère (acte II)\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87050368\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5422\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-13.09.2015-ITEMA_20795506-0.mp3\" isVideo=\"false\" created=\"2015-09-14T03:16:50.000Z\" albumId=\"3269\" artistId=\"1457\" type=\"podcast\" streamId=\"49504\" description=\"durée : 01:30:10 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Bigorie, Chantal Cazaux, Piotr Kaminski - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-09-13T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"860\" parent=\"42169\" isDir=\"false\" title=\"Concerto pour clarinette en la majeur K 622 (1791) de Wolfgang-Amadeus Mozart\" album=\"Concerto pour clarinette en la majeur K 622 (1791) de Wolfgang-Amadeus Mozart\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86745216\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5403\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-06.09.2015-ITEMA_20790942-0.mp3\" isVideo=\"false\" created=\"2015-09-07T03:17:51.000Z\" albumId=\"3253\" artistId=\"1457\" type=\"podcast\" streamId=\"49461\" description=\"durée : 01:29:51 - La tribune des critiques de disques - par : Jérémie Rousseau - Stéphane Friédérich, Pianiste - Emmanuelle Giuliani, La Croix - Antoine Mignon, Classica - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-09-06T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"709\" parent=\"42169\" isDir=\"false\" title=\"Gnossiennes de Satie\" album=\"Gnossiennes de Satie\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86691968\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5399\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-30.08.2015-ITEMA_20787504-00.mp3\" isVideo=\"false\" created=\"2015-09-01T19:28:47.000Z\" albumId=\"2615\" artistId=\"1457\" type=\"podcast\" streamId=\"45554\" description=\"durée : 01:29:47 - La tribune des critiques de disques - par : Jérémie Rousseau - thème de la semaine : Rediffusion du 22 février 2015 - avec Christian Merlin, Bertrand Dermoncourt et Elsa Fottorino - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-08-30T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"710\" isDir=\"false\" title=\"Parsifal de Wagner, acte III\" description=\"durée : 01:29:46 - La tribune des critiques de disques - par : Jérémie Rousseau - thème de la semaine : Rediffusion du 23 novembre 2014 - Avec Chantal Cazaux, Emmanuel Dupuy, Christian Merlin - réalisé par : Géraldine Prutner\" status=\"skipped\" publishDate=\"2015-08-23T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"711\" isDir=\"false\" title=\"Sonate opus 111 de Beethoven\" description=\"durée : 01:29:43 - La tribune des critiques de disques - par : Jérémie Rousseau - thème de la semaine : Rediffusion du 11 janvier 2015 - Avec Elsa Fottorino, Christian Merlin, Antoine Mignon - réalisé par : Géraldine Prutner\" status=\"skipped\" publishDate=\"2015-08-16T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"712\" isDir=\"false\" title=\"Concerto pour piano " Jeunehomme " de Mozart\" description=\"durée : 01:29:42 - La tribune des critiques de disques - par : Jérémie Rousseau - thème de la semaine : Rediffusion du 1er mars 2015 - Concerto pour piano &quot; Jeunehomme &quot; de Mozart - réalisé par : Géraldine Prutner\" status=\"skipped\" publishDate=\"2015-08-09T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"713\" isDir=\"false\" title=\"Neuvième Symphonie de Beethoven\" description=\"durée : 01:29:40 - La tribune des critiques de disques - par : Jérémie Rousseau - thème de la semaine : Rediffusion du 26 octobre 2014 - Avec Stéphane Friederich , Emmanuelle Giuliani et Philippe Venturini - réalisé par : Cyrielle Weber\" status=\"skipped\" publishDate=\"2015-08-02T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"714\" isDir=\"false\" title=\"Dans les brumes de Leos Janacek\" description=\"durée : 01:29:54 - La tribune des critiques de disques - par : Jérémie Rousseau - thème de la semaine : Rediffusion du 2 novembre 2014 - Avec Bertrand Dermoncourt, Elsa Fottorino et Piotr Kaminski - réalisé par : Géraldine Prutner\" status=\"skipped\" publishDate=\"2015-07-26T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"715\" isDir=\"false\" title=\""Le Beau Danube Bleu" de Johann Strauss\" description=\"durée : 01:29:41 - La tribune des critiques de disques - par : Jérémie Rousseau - thème de la semaine : Rediffusion - avec Emmanuelle Giuliani, Christian Merlin et Bertrand Dermoncourt - réalisé par : Géraldine Prutner\" status=\"skipped\" publishDate=\"2015-07-19T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"716\" isDir=\"false\" title=\"Gloria de Vivaldi\" description=\"durée : 01:29:44 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Bigorie, Classica, Chantal Cazaux, musicologue et enseignante et Philippe Venturini, Les Echos et Classica - réalisé par : Géraldine Prutner\" status=\"skipped\" publishDate=\"2015-07-12T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"698\" parent=\"42169\" isDir=\"false\" title=\"Symphonie n°8 de Chostakovitch\" album=\"Symphonie n°8 de Chostakovitch\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86915200\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5413\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-28.06.2015-ITEMA_20771947-0.mp3\" isVideo=\"false\" created=\"2015-06-28T22:18:40.000Z\" albumId=\"2614\" artistId=\"1456\" type=\"podcast\" streamId=\"44999\" description=\"durée : 01:30:01 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Bertrand Dermoncourt, Emmanuelle Giuliani, Xavier Lacavalerie - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-06-28T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"681\" parent=\"42169\" isDir=\"false\" title=\"Passion selon Saint Jean de Bach\" album=\"Passion selon Saint Jean de Bach\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86993024\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5418\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-21.06.2015-ITEMA_20769183-0.mp3\" isVideo=\"false\" created=\"2015-06-21T20:47:41.000Z\" albumId=\"2613\" artistId=\"1456\" type=\"podcast\" streamId=\"44877\" description=\"durée : 01:30:06 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Bigorie, Emmanuel Dupuy, Eric Taver\n" +
|
||||
" - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-06-21T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"668\" parent=\"42169\" isDir=\"false\" title=\"Concerto pour violon de Sibelius\" album=\"Concerto pour violon de Sibelius\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86059136\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5360\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-14.06.2015-ITEMA_20766550-00.mp3\" isVideo=\"false\" created=\"2015-06-16T21:56:04.000Z\" albumId=\"2612\" artistId=\"1456\" type=\"podcast\" streamId=\"44869\" description=\"durée : 01:29:08 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Stéphane Friédérich, Jennifer Lesieur, Philippe Venturini - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-06-14T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"661\" parent=\"42169\" isDir=\"false\" title=\"Trio n°1 op. 49 de Mendelssohn\" album=\"Trio n°1 op. 49 de Mendelssohn\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86151296\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5366\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-07.06.2015-ITEMA_20763789-0.mp3\" isVideo=\"false\" created=\"2015-06-07T21:54:20.000Z\" albumId=\"2611\" artistId=\"1456\" type=\"podcast\" streamId=\"44850\" description=\"durée : 01:29:14 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jean-Charles Hoffelé, Antoine Mignon, Eric Taver - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-06-07T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"642\" parent=\"42169\" isDir=\"false\" title=\"Miroirs, de Maurice Ravel\" album=\"Miroirs, de Maurice Ravel\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86952064\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5416\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-31.05.2015-ITEMA_20761227-0.mp3\" isVideo=\"false\" created=\"2015-06-03T21:33:17.000Z\" albumId=\"2610\" artistId=\"1456\" type=\"podcast\" streamId=\"44727\" description=\"durée : 01:30:04 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Cahen, Stéphane Friédérich, Elsa Fottorino - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-05-31T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"643\" isDir=\"false\" title=\"La Walkyrie de Wagner, acte I\" description=\"durée : 01:30:06 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Chantal Cazaux, Emmanuel Dupuy, Christian Merlin - réalisé par : Cyrielle Weber\" status=\"skipped\" publishDate=\"2015-05-24T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"633\" parent=\"42169\" isDir=\"false\" title=\"Messe du Couronnement de Mozart\" album=\"Messe du Couronnement de Mozart\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86921344\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5414\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-17.05.2015-ITEMA_20756109-0.mp3\" isVideo=\"false\" created=\"2015-05-17T21:47:51.000Z\" albumId=\"2609\" artistId=\"1456\" type=\"podcast\" streamId=\"44636\" description=\"durée : 01:30:02 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Bertrand Dermoncourt (Classica), Emmanuelle Giuliani (La Croix) et Piotr Kaminski (Diapason). - réalisé par : Cyrielle Weber\" status=\"completed\" publishDate=\"2015-05-17T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"620\" parent=\"42169\" isDir=\"false\" title=\"Concerto pour piano n°2 de Rachmaninov\" album=\"Concerto pour piano n°2 de Rachmaninov\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86995072\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5418\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-10.05.2015-ITEMA_20753543-0.mp3\" isVideo=\"false\" created=\"2015-05-10T21:47:50.000Z\" albumId=\"2608\" artistId=\"1456\" type=\"podcast\" streamId=\"44626\" description=\"durée : 01:30:06 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Elsa Fottorino (Pianiste), Stéphane Friédérich (Pianiste), Jean-Charles Hoffelé (Diapason, L’Avant-Scène Opéra) - réalisé par : Thomas Jost\" status=\"completed\" publishDate=\"2015-05-10T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"609\" parent=\"42169\" isDir=\"false\" title=\"Symphonie n°82 « L’Ours » de Haydn\" album=\"Symphonie n°82 « L?Ours » de Haydn\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87052416\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5422\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-03.05.2015-ITEMA_20751006-0.mp3\" isVideo=\"false\" created=\"2015-05-03T21:48:13.000Z\" albumId=\"2607\" artistId=\"1456\" type=\"podcast\" streamId=\"44590\" description=\"durée : 01:30:10 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Philippe Venturini, Emmanuelle Giuliani, Christian Merlin - réalisé par : Cyrielle Weber\" status=\"completed\" publishDate=\"2015-05-03T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"599\" parent=\"42169\" isDir=\"false\" title=\"Leçons de Ténèbres de Couperin\" album=\"Leçons de Ténèbres de Couperin\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"87036032\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5421\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-26.04.2015-ITEMA_20748474-0.mp3\" isVideo=\"false\" created=\"2015-04-26T22:33:12.000Z\" albumId=\"2606\" artistId=\"1456\" type=\"podcast\" streamId=\"42807\" description=\"durée : 01:30:09 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Chantal Cazaux (L’Avant-scène Opéra), Piotr Kaminski (Diapason), Philippe Venturini (Les Echos) \n" +
|
||||
" - réalisé par : Sylvain Richard\" status=\"completed\" publishDate=\"2015-04-26T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"590\" parent=\"42169\" isDir=\"false\" title=\"Impromptus D. 899 de Schubert\" album=\"Impromptus D. 899 de Schubert\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86634624\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5396\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-19.04.2015-ITEMA_20745909-0.mp3\" isVideo=\"false\" created=\"2015-04-20T17:12:16.000Z\" albumId=\"2605\" artistId=\"1456\" type=\"podcast\" streamId=\"42716\" description=\"durée : 01:29:44 - La tribune des critiques de disques - par : Jérémie Rousseau - Bertrand Dermoncourt (Classica), Elsa Fottorino (Pianiste), Emmanuelle Giuliani (La Croix) - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-04-19T19:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"574\" parent=\"42169\" isDir=\"false\" title=\"Gloria d'Antonio Vivaldi\" album=\"Gloria d'Antonio Vivaldi\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86798464\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5406\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-15.03.2015-ITEMA_20733576-0.mp3\" isVideo=\"false\" created=\"2015-03-16T15:17:04.000Z\" albumId=\"2604\" artistId=\"1456\" type=\"podcast\" streamId=\"42263\" description=\"durée : 01:29:54 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Bigorie (Classica), Chantal Cazaux (L’Avant-scène Opéra), Philippe Venturini (Les Echos) \n" +
|
||||
" - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-03-15T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"565\" parent=\"42169\" isDir=\"false\" title=\""Lagrime mie" de "L'Eraclito Amoroso" de Barbara Strozzi\" album=\"''Lagrime mie'' de ''L'Eraclito Amoroso'' de Barbara Strozzi\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86646912\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5397\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-08.03.2015-ITEMA_20731103-0.mp3\" isVideo=\"false\" created=\"2015-03-08T23:45:39.000Z\" albumId=\"2603\" artistId=\"1456\" type=\"podcast\" streamId=\"42217\" description=\"durée : 01:29:45 - La tribune des critiques de disques - par : Jérémie Rousseau - thème de la semaine : dans le cadre de la Journée internationale de la femme - Avec Jérémie Bigorie (Classica), Jérémie Cahen (disquaire chez Gibert Joseph) et Jean-Charles Hoffelé (Diapason, L'Avant-Scène opéra) - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-03-08T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"556\" parent=\"42169\" isDir=\"false\" title=\"Concerto pour piano « Jeunehomme » de Wolfgang Amadeus Mozart\" album=\"Concerto pour piano « Jeunehomme » de Wolfgang Amadeus Mozart\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86608000\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5394\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-01.03.2015-ITEMA_20728611-0.mp3\" isVideo=\"false\" created=\"2015-03-02T14:28:25.000Z\" albumId=\"2597\" artistId=\"1456\" type=\"podcast\" streamId=\"41746\" description=\"durée : 01:29:42 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Elsa Fottorino, Stéphane Friédérich, Antoine Mignon - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-03-01T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"546\" parent=\"42169\" isDir=\"false\" title=\"Gnossiennes d'Erik Satie\" album=\"Gnossiennes d'Erik Satie\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86866048\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5410\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-22.02.2015-ITEMA_20726133-0.mp3\" isVideo=\"false\" created=\"2015-02-23T17:47:40.000Z\" albumId=\"2596\" artistId=\"1456\" type=\"podcast\" streamId=\"41705\" description=\"durée : 01:29:58 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Christian Merlin, Bertrand Dermoncourt et Elsa Fottorino - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-02-22T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"513\" parent=\"42169\" isDir=\"false\" title=\"Sextuor n°1 de Johannes Brahms\" album=\"Sextuor n°1 de Johannes Brahms\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86612096\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5394\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-15.02.2015-ITEMA_20723560-0.mp3\" isVideo=\"false\" created=\"2015-02-16T15:30:30.000Z\" albumId=\"2595\" artistId=\"1456\" type=\"podcast\" streamId=\"41592\" description=\"durée : 01:29:42 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jean-Charles Hoffelé, Antoine Mignon, Eric Taver\n" +
|
||||
" - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-02-15T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"506\" parent=\"42169\" isDir=\"false\" title=\"Cavalleria Rusticana de Pietro Mascagni\" album=\"Cavalleria Rusticana de Pietro Mascagni\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86425728\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5383\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-08.02.2015-ITEMA_20721030-0.mp3\" isVideo=\"false\" created=\"2015-02-09T17:03:05.000Z\" albumId=\"2594\" artistId=\"1456\" type=\"podcast\" streamId=\"40570\" description=\"durée : 01:29:31 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Bigorie (Classica), Chantal Cazaux (L'Avant-Scène Opéra) et Emmanuel Dupuy (Diapason) - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-02-08T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"498\" parent=\"42169\" isDir=\"false\" title=\"Cantate « Weinen, Klagen, Sorgen, Zagen » de Jean-Sébastien Bach\" album=\"Cantate « Weinen, Klagen, Sorgen, Zagen » de Jean-Sébastien Bach\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86556800\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5391\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-01.02.2015-ITEMA_20718534-0.mp3\" isVideo=\"false\" created=\"2015-02-01T21:45:28.000Z\" albumId=\"2598\" artistId=\"1456\" type=\"podcast\" streamId=\"42163\" description=\"durée : 01:29:39 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Bertrand Dermoncourt, Emmanuel Dupuy, Philippe Venturini - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-02-01T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"489\" parent=\"42169\" isDir=\"false\" title=\"Quatuor Américain d'Antonin Dvorak\" album=\"Quatuor Américain d'Antonin Dvorak\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86728832\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5402\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-25.01.2015-ITEMA_20716094-0.mp3\" isVideo=\"false\" created=\"2015-01-25T21:43:55.000Z\" albumId=\"2599\" artistId=\"1456\" type=\"podcast\" streamId=\"42164\" description=\"durée : 01:29:50 - La tribune des critiques de disques - par : Jérémie Rousseau - Emmanuelle Giuliani (La Croix), Antoine Mignon (Classica), Eric Taver (L'Etudiant) élisent la version de référence du Quatuor à cordes n°12 « Américain » d’Anton Dvorak.\n" +
|
||||
" - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-01-25T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"479\" parent=\"42169\" isDir=\"false\" title=\"L’Arlésienne de Georges Bizet\" album=\"L?Arlésienne de Georges Bizet\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86802560\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5406\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-18.01.2015-ITEMA_20713431-0.mp3\" isVideo=\"false\" created=\"2015-01-19T21:12:44.000Z\" albumId=\"2600\" artistId=\"1456\" type=\"podcast\" streamId=\"42165\" description=\"durée : 01:29:54 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Jérémie Cahen, Stéphane Friédérich, Emmanuelle Giuliani - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-01-18T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"470\" parent=\"42169\" isDir=\"false\" title=\"Sonate Opus 111 de Beethoven\" album=\"Sonate Opus 111 de Beethoven\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86657152\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5397\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-11.01.2015-ITEMA_20710790-0.mp3\" isVideo=\"false\" created=\"2015-01-11T21:15:24.000Z\" albumId=\"2601\" artistId=\"1456\" type=\"podcast\" streamId=\"42166\" description=\"durée : 01:29:45 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Elsa Fottorino, Christian Merlin, Antoine Mignon - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-01-11T20:00:00.000Z\"/>\n" +
|
||||
" <episode id=\"461\" parent=\"42169\" isDir=\"false\" title=\"Le Beau Danube Bleu de Johann Strauss\" album=\"Le Beau Danube Bleu de Johann Strauss\" artist=\"Jérémie Rousseau\" genre=\"Podcast\" coverArt=\"42169\" size=\"86569088\" contentType=\"audio/mpeg\" suffix=\"mp3\" duration=\"5392\" bitRate=\"128\" path=\"La tribune des critiques de disques/13183-04.01.2015-ITEMA_20708450-0.mp3\" isVideo=\"false\" created=\"2015-01-04T20:49:53.000Z\" albumId=\"2602\" artistId=\"1456\" type=\"podcast\" streamId=\"42167\" description=\"durée : 01:29:40 - La tribune des critiques de disques - par : Jérémie Rousseau - Avec Emmanuelle Giuliani (La Croix), Christian Merlin (Le Figaro) et Bertrand Dermoncourt (Classica, L'Express). - réalisé par : Géraldine Prutner\" status=\"completed\" publishDate=\"2015-01-04T20:00:00.000Z\"/>\n" +
|
||||
" </channel>\n" +
|
||||
" </podcasts>\n" +
|
||||
"</subsonic-response>\n";
|
||||
|
||||
|
||||
public static Reader getReader() {
|
||||
|
||||
return new StringReader(data);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package org.moire.ultrasonic.Test.service;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
/**
|
||||
* Created by rcocula on 11/03/2016.
|
||||
*/
|
||||
public class GetPodcastTestReaderProvider {
|
||||
|
||||
private static String data = "<subsonic-response status=\"ok\" version=\"1.12.0\" xmlns=\"http://subsonic.org/restapi\">\n" +
|
||||
" <podcasts>\n" +
|
||||
" <channel id=\"0\" url=\"http://radiofrance-podcast.net/podcast09/rss_13183.xml\" title=\"La tribune des critiques de disques\" description=\"Sous la houlette de Jérémie Rousseau, d'éminents critiques musicaux écoutent à l'aveugle différentes versions d'une oeuvre du répertoire et la commentent\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"1\" url=\"http://radiofrance-podcast.net/podcast09/rss_11874.xml\" title=\"La Matinale du samedi\" description=\"Une version détendue pour les matinaux du week end\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"2\" url=\"http://radiofrance-podcast.net/podcast09/rss_10467.xml\" title=\"LES NOUVEAUX CHEMINS DE LA CONNAISSANCE\" description=\"Une rencontre quotidienne entre philosophie et monde contemporain.\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"3\" url=\"http://radiofrance-podcast.net/podcast09/rss_12087.xml\" title=\"Alla Breve, l'intégrale\" description=\"Une oeuvre courte, commandée à un compositeur d aujourd hui, diffusée en 5 mouvements durant la semaine et proposée en podcast dans son intégralité.\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"4\" url=\"http://lescastcodeurs.libsyn.com/rss\" title=\"Les Cast Codeurs Podcast\" description=\"Le podcast Java en Français dans le texte\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"6\" url=\"http://radiofrance-podcast.net/podcast09/rss_14003.xml\" title=\"Le cri du patchwork\" description=\"Si le patchwork était un animal, quel serait son cri ?\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"7\" url=\"http://radiofrance-podcast.net/podcast09/rss_12289.xml\" title=\"Electromania\" description=\"Electromania continue de témoigner de toutes les musiques avant tout inventives et inclassables, de Pierre Henry à Nick Cave.\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"8\" url=\"http://radiofrance-podcast.net/podcast09/rss_11910.xml\" title=\"CONTINENT MUSIQUE\" description=\"Funk, baroque, jazz, électro, classique, chanson, musique concrète ou hip-hop abstrait...\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"9\" url=\"http://radiofrance-podcast.net/podcast09/rss_11985.xml\" title=\"SUPERSONIC\" description=\"Un homme ou une femme de son fait partager ses créations et son univers\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"10\" url=\"http://radiofrance-podcast.net/podcast09/rss_12668.xml\" title=\"Label pop\" description=\"Chaque semaine, une oreille attentive à l'actualité, pour restituer l'éclatante vitalité de la pop moderne, entendue au sens le plus large\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"11\" url=\"http://radiofrance-podcast.net/podcast09/rss_11224.xml\" title=\"Le magazine de la contemporaine\" description=\"Interviews et reportages autour de l'actualité de la musique contemporaine\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"12\" url=\"http://radiofrance-podcast.net/podcast09/rss_11393.xml\" title=\"Les greniers de la mémoire\" description=\"Une visite complice et nostalgique des archives musicales de Radio France\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"13\" url=\"http://radiofrance-podcast.net/podcast09/rss_14498.xml\" title=\"Musicopolis\" description=\"Une ville, un compositeur, une époque. Une histoire de la musique racontée chaque semaine.\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"14\" url=\"http://radiofrance-podcast.net/podcast09/rss_14603.xml\" title=\"On ne peut pas tout savoir\" description=\"Parce qu'on ne peut pas tout savoir, Arnaud Merlin vous propose chaque semaine un voyage dans un univers musical différent.\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"15\" url=\"http://radiofrance-podcast.net/podcast09/rss_12576.xml\" title=\"LES CARNETS DE L'ECONOMIE\" description=\"Un chercheur ou un acteur de la sphère économique et sociale nous livre un concentré de ses travaux et de sa réflexion\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"16\" url=\"http://radiofrance-podcast.net/podcast09/rss_14076.xml\" title=\"LES CARNETS DE LA CREATION\" description=\"LES CARNETS DE LA CREATION\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"18\" url=\"http://radiofrance-podcast.net/podcast09/rss_14663.xml\" title=\"LE MONDE SELON XAVIER DELAPORTE\" description=\"LE MONDE SELON XAVIER DELAPORTE\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"19\" url=\"http://radiofrance-podcast.net/podcast09/rss_16260.xml\" title=\"LA SUITE DANS LES IDEES\" description=\"Contribuer à alimenter le débat public par les idées\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"20\" url=\"http://radiofrance-podcast.net/podcast09/rss_13959.xml\" title=\"CULTURE MUSIQUE\" description=\"CULTURE MUSIQUE\" status=\"completed\"/>\n" +
|
||||
" <channel id=\"21\" url=\"http://radiofrance-podcast.net/podcast09/rss_14009.xml\" title=\"Carnets de voyages\" description=\"Carnet de voyage est un atlas ouvert sur les musiques que l'on dit de tradition orale ou extra-européennes.\" status=\"completed\"/>\n" +
|
||||
" </podcasts>\n" +
|
||||
"</subsonic-response>\n";
|
||||
|
||||
|
||||
public static Reader getReader() {
|
||||
|
||||
return new StringReader(data);
|
||||
}
|
||||
}
|
@ -55,7 +55,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
|
||||
private Preference addServerPreference;
|
||||
private ListPreference theme;
|
||||
private ListPreference videoPlayer;
|
||||
private ListPreference maxBitrateWifi;
|
||||
private ListPreference maxBitrateMobile;
|
||||
private ListPreference cacheSize;
|
||||
@ -110,7 +109,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
|
||||
addServerPreference = findPreference(Constants.PREFERENCES_KEY_SERVERS_EDIT);
|
||||
theme = findPreference(Constants.PREFERENCES_KEY_THEME);
|
||||
videoPlayer = findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER);
|
||||
maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI);
|
||||
maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE);
|
||||
cacheSize = findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE);
|
||||
@ -411,7 +409,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
|
||||
private void update() {
|
||||
theme.setSummary(theme.getEntry());
|
||||
videoPlayer.setSummary(videoPlayer.getEntry());
|
||||
maxBitrateWifi.setSummary(maxBitrateWifi.getEntry());
|
||||
maxBitrateMobile.setSummary(maxBitrateMobile.getEntry());
|
||||
cacheSize.setSummary(cacheSize.getEntry());
|
||||
|
@ -30,6 +30,7 @@ import android.widget.Toast;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicRESTException;
|
||||
import org.moire.ultrasonic.app.UApp;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.JukeboxStatus;
|
||||
|
@ -117,7 +117,6 @@ public final class Constants
|
||||
public static final String PREFERENCES_KEY_CLEAR_PLAYLIST = "clearPlaylist";
|
||||
public static final String PREFERENCES_KEY_CLEAR_BOOKMARK = "clearBookmark";
|
||||
public static final String PREFERENCES_KEY_DISC_SORT = "discAndTrackSort";
|
||||
public static final String PREFERENCES_KEY_VIDEO_PLAYER = "videoPlayer";
|
||||
public static final String PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS = "sendBluetoothNotifications";
|
||||
public static final String PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART = "sendBluetoothAlbumArt";
|
||||
public static final String PREFERENCES_KEY_VIEW_REFRESH = "viewRefresh";
|
||||
|
@ -21,8 +21,9 @@ package org.moire.ultrasonic.util;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.*;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
@ -39,7 +40,6 @@ import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.Parcelable;
|
||||
import android.util.DisplayMetrics;
|
||||
import timber.log.Timber;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
@ -51,19 +51,32 @@ import androidx.preference.PreferenceManager;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.app.UApp;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.*;
|
||||
import org.moire.ultrasonic.domain.Bookmark;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
|
||||
import org.moire.ultrasonic.domain.PlayerState;
|
||||
import org.moire.ultrasonic.domain.RepeatMode;
|
||||
import org.moire.ultrasonic.domain.SearchResult;
|
||||
import org.moire.ultrasonic.service.DownloadFile;
|
||||
import org.moire.ultrasonic.service.MediaPlayerService;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
@ -1148,36 +1161,6 @@ public class Util
|
||||
else return minutes > 0 ? String.format(Locale.getDefault(), "%d:%02d", minutes, seconds) : String.format(Locale.getDefault(), "0:%02d", seconds);
|
||||
}
|
||||
|
||||
public static VideoPlayerType getVideoPlayerType()
|
||||
{
|
||||
SharedPreferences preferences = getPreferences();
|
||||
return VideoPlayerType.forKey(preferences.getString(Constants.PREFERENCES_KEY_VIDEO_PLAYER, VideoPlayerType.MX.getKey()));
|
||||
}
|
||||
|
||||
public static boolean isPackageInstalled(Context context, String packageName)
|
||||
{
|
||||
PackageManager pm = context.getPackageManager();
|
||||
List<ApplicationInfo> packages = null;
|
||||
|
||||
if (pm != null)
|
||||
{
|
||||
packages = pm.getInstalledApplications(0);
|
||||
}
|
||||
|
||||
if (packages != null)
|
||||
{
|
||||
for (ApplicationInfo packageInfo : packages)
|
||||
{
|
||||
if (packageInfo.packageName.equals(packageName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getVersionName(Context context)
|
||||
{
|
||||
String versionName = null;
|
||||
|
@ -1,139 +0,0 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2013 (C) Sindre Mehus
|
||||
*/
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id: VideoPlayerType.java 3473 2013-05-23 16:42:49Z sindre_mehus $
|
||||
*/
|
||||
public enum VideoPlayerType
|
||||
{
|
||||
|
||||
MX("mx")
|
||||
{
|
||||
@Override
|
||||
public void playVideo(final Context context, MusicDirectory.Entry entry) throws Exception
|
||||
{
|
||||
|
||||
// Check if MX Player is installed.
|
||||
boolean installedAd = Util.isPackageInstalled(context, PACKAGE_NAME_MX_AD);
|
||||
boolean installedPro = Util.isPackageInstalled(context, PACKAGE_NAME_MX_PRO);
|
||||
|
||||
if (!installedAd && !installedPro)
|
||||
{
|
||||
new AlertDialog.Builder(context).setMessage(R.string.video_get_mx_player_text).setPositiveButton(R.string.video_get_mx_player_button, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i)
|
||||
{
|
||||
try
|
||||
{
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("market://details?id=%s", PACKAGE_NAME_MX_AD))));
|
||||
}
|
||||
catch (android.content.ActivityNotFoundException x)
|
||||
{
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("http://play.google.com/store/apps/details?id=%s", PACKAGE_NAME_MX_AD))));
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).show();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// See documentation on https://sites.google.com/site/mxvpen/api
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setPackage(installedPro ? PACKAGE_NAME_MX_PRO : PACKAGE_NAME_MX_AD);
|
||||
intent.putExtra("title", entry.getTitle());
|
||||
intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService().getVideoUrl(entry.getId(), false)), "video/*");
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
FLASH("flash")
|
||||
{
|
||||
@Override
|
||||
public void playVideo(Context context, MusicDirectory.Entry entry) throws Exception
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(MusicServiceFactory.getMusicService().getVideoUrl(entry.getId(), true)));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
},
|
||||
|
||||
DEFAULT("default")
|
||||
{
|
||||
@Override
|
||||
public void playVideo(Context context, MusicDirectory.Entry entry) throws Exception
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService().getVideoUrl(entry.getId(), false)), "video/*");
|
||||
context.startActivity(intent);
|
||||
}
|
||||
};
|
||||
|
||||
private final String key;
|
||||
|
||||
VideoPlayerType(String key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
public static VideoPlayerType forKey(String key)
|
||||
{
|
||||
for (VideoPlayerType type : VideoPlayerType.values())
|
||||
{
|
||||
if (type.key.equals(key))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract void playVideo(Context context, MusicDirectory.Entry entry) throws Exception;
|
||||
|
||||
private static final String PACKAGE_NAME_MX_AD = "com.mxtech.videoplayer.ad";
|
||||
private static final String PACKAGE_NAME_MX_PRO = "com.mxtech.videoplayer.pro";
|
||||
|
||||
}
|
@ -29,7 +29,6 @@ import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||
@ -151,7 +150,12 @@ class NavigationActivity : AppCompatActivity() {
|
||||
showWelcomeScreen = showWelcomeScreen and !areServersMigrated
|
||||
|
||||
loadSettings()
|
||||
showInfoDialog(showWelcomeScreen)
|
||||
|
||||
// This is a first run with only the demo entry inside the database
|
||||
// We set the active server to the demo one and show the welcome dialog
|
||||
if (showWelcomeScreen) {
|
||||
showWelcomeDialog()
|
||||
}
|
||||
|
||||
nowPlayingEventListener = object : NowPlayingEventListener {
|
||||
override fun onDismissNowPlaying() {
|
||||
@ -313,19 +317,27 @@ class NavigationActivity : AppCompatActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun showInfoDialog(show: Boolean) {
|
||||
private fun showWelcomeDialog() {
|
||||
if (!infoDialogDisplayed) {
|
||||
infoDialogDisplayed = true
|
||||
if (show) {
|
||||
AlertDialog.Builder(this)
|
||||
.setIcon(android.R.drawable.ic_dialog_info)
|
||||
.setTitle(R.string.main_welcome_title)
|
||||
.setMessage(R.string.main_welcome_text)
|
||||
.setPositiveButton(R.string.common_ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
findNavController(R.id.nav_host_fragment).navigate(R.id.settingsFragment)
|
||||
}.show()
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setIcon(android.R.drawable.ic_dialog_info)
|
||||
.setTitle(R.string.main_welcome_title)
|
||||
.setMessage(R.string.main_welcome_text_demo)
|
||||
.setNegativeButton(R.string.main_welcome_cancel) { dialog, _ ->
|
||||
// Go to the settings screen
|
||||
dialog.dismiss()
|
||||
findNavController(R.id.nav_host_fragment).navigate(R.id.settingsFragment)
|
||||
}
|
||||
.setPositiveButton(R.string.common_ok) { dialog, _ ->
|
||||
// Add the demo server
|
||||
val activeServerProvider: ActiveServerProvider by inject()
|
||||
val demoIndex = serverSettingsModel.addDemoServer()
|
||||
activeServerProvider.setActiveServerByIndex(demoIndex)
|
||||
findNavController(R.id.nav_host_fragment).navigate(R.id.mainFragment)
|
||||
dialog.dismiss()
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,12 +37,15 @@ class ActiveServerProvider(
|
||||
cachedServer = repository.findById(serverId)
|
||||
}
|
||||
Timber.d(
|
||||
"getActiveServer retrieved from DataBase, id: $serverId; " +
|
||||
"cachedServer: $cachedServer"
|
||||
"getActiveServer retrieved from DataBase, id: %s cachedServer: %s",
|
||||
serverId, cachedServer
|
||||
)
|
||||
}
|
||||
|
||||
if (cachedServer != null) return cachedServer!!
|
||||
if (cachedServer != null) {
|
||||
return cachedServer!!
|
||||
}
|
||||
|
||||
setActiveServerId(0)
|
||||
}
|
||||
|
||||
@ -105,7 +108,7 @@ class ActiveServerProvider(
|
||||
* @param method: The Rest resource to use
|
||||
* @return The Rest Url of the method on the server
|
||||
*/
|
||||
fun getRestUrl(method: String?): String? {
|
||||
fun getRestUrl(method: String?): String {
|
||||
val builder = StringBuilder(8192)
|
||||
val activeServer = getActiveServer()
|
||||
val serverUrl: String = activeServer.url
|
||||
|
@ -12,6 +12,7 @@ import org.moire.ultrasonic.fragment.ServerSettingsModel
|
||||
import org.moire.ultrasonic.util.Util
|
||||
|
||||
const val SP_NAME = "Default_SP"
|
||||
const val DB_FILENAME = "ultrasonic-database"
|
||||
|
||||
/**
|
||||
* This Koin module contains registration of classes related to permanent storage
|
||||
@ -23,11 +24,10 @@ val appPermanentStorage = module {
|
||||
Room.databaseBuilder(
|
||||
androidContext(),
|
||||
AppDatabase::class.java,
|
||||
"ultrasonic-database"
|
||||
DB_FILENAME
|
||||
)
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.addMigrations(MIGRATION_2_3)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
.build()
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,6 @@ import org.moire.ultrasonic.cache.PermanentFileStorage
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.imageloader.ImageLoader
|
||||
import org.moire.ultrasonic.log.TimberOkHttpLogger
|
||||
import org.moire.ultrasonic.service.ApiCallResponseChecker
|
||||
import org.moire.ultrasonic.service.CachedMusicService
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
import org.moire.ultrasonic.service.OfflineMusicService
|
||||
@ -50,28 +49,29 @@ val musicServiceModule = module {
|
||||
}
|
||||
|
||||
single {
|
||||
val server = get<ActiveServerProvider>().getActiveServer()
|
||||
|
||||
return@single SubsonicClientConfiguration(
|
||||
baseUrl = get<ActiveServerProvider>().getActiveServer().url,
|
||||
username = get<ActiveServerProvider>().getActiveServer().userName,
|
||||
password = get<ActiveServerProvider>().getActiveServer().password,
|
||||
baseUrl = server.url,
|
||||
username = server.userName,
|
||||
password = server.password,
|
||||
minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
|
||||
get<ActiveServerProvider>().getActiveServer().minimumApiVersion
|
||||
server.minimumApiVersion
|
||||
?: Constants.REST_PROTOCOL_VERSION
|
||||
),
|
||||
clientID = Constants.REST_CLIENT_ID,
|
||||
allowSelfSignedCertificate = get<ActiveServerProvider>()
|
||||
.getActiveServer().allowSelfSignedCertificate,
|
||||
enableLdapUserSupport = get<ActiveServerProvider>().getActiveServer().ldapSupport,
|
||||
debug = BuildConfig.DEBUG
|
||||
allowSelfSignedCertificate = server.allowSelfSignedCertificate,
|
||||
enableLdapUserSupport = server.ldapSupport,
|
||||
debug = BuildConfig.DEBUG,
|
||||
isRealProtocolVersion = server.minimumApiVersion != null
|
||||
)
|
||||
}
|
||||
|
||||
single<HttpLoggingInterceptor.Logger> { TimberOkHttpLogger() }
|
||||
single { SubsonicAPIClient(get(), get()) }
|
||||
single { ApiCallResponseChecker(get(), get()) }
|
||||
|
||||
single<MusicService>(named(ONLINE_MUSIC_SERVICE)) {
|
||||
CachedMusicService(RESTMusicService(get(), get(), get(), get()))
|
||||
CachedMusicService(RESTMusicService(get(), get(), get()))
|
||||
}
|
||||
|
||||
single<MusicService>(named(OFFLINE_MUSIC_SERVICE)) {
|
||||
|
@ -22,12 +22,13 @@ import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicRESTException
|
||||
import org.moire.ultrasonic.api.subsonic.falseOnFailure
|
||||
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||
import org.moire.ultrasonic.api.subsonic.throwOnFailure
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.ServerSetting
|
||||
import org.moire.ultrasonic.service.ApiCallResponseChecker
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||
import org.moire.ultrasonic.service.SubsonicRESTException
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.ErrorDialog
|
||||
import org.moire.ultrasonic.util.ModalBackgroundTask
|
||||
@ -360,7 +361,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
|
||||
|
||||
// Execute a ping to check the authentication, now using the correct API version.
|
||||
pingResponse = subsonicApiClient.api.ping().execute()
|
||||
ApiCallResponseChecker.checkResponseSuccessful(pingResponse)
|
||||
pingResponse.throwOnFailure()
|
||||
|
||||
currentServerSetting!!.chatSupport = isServerFunctionAvailable {
|
||||
subsonicApiClient.api.getChatMessages().execute()
|
||||
@ -387,7 +388,8 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
|
||||
updateProgress(getProgress())
|
||||
|
||||
val licenseResponse = subsonicApiClient.api.getLicense().execute()
|
||||
ApiCallResponseChecker.checkResponseSuccessful(licenseResponse)
|
||||
licenseResponse.throwOnFailure()
|
||||
|
||||
if (!licenseResponse.body()!!.license.valid) {
|
||||
return getProgress() + "\n" +
|
||||
resources.getString(R.string.settings_testing_unlicensed)
|
||||
@ -438,9 +440,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
|
||||
|
||||
private fun isServerFunctionAvailable(function: () -> Response<out SubsonicResponse>): Boolean {
|
||||
return try {
|
||||
val response = function()
|
||||
ApiCallResponseChecker.checkResponseSuccessful(response)
|
||||
true
|
||||
function().falseOnFailure()
|
||||
} catch (_: IOException) {
|
||||
false
|
||||
} catch (_: SubsonicRESTException) {
|
||||
|
@ -10,8 +10,6 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import java.net.ConnectException
|
||||
import java.net.UnknownHostException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -89,10 +87,8 @@ open class GenericListModel(application: Application) :
|
||||
|
||||
try {
|
||||
load(isOffline, useId3Tags, musicService, refresh, bundle)
|
||||
} catch (exception: ConnectException) {
|
||||
handleException(exception, swipe.context)
|
||||
} catch (exception: UnknownHostException) {
|
||||
handleException(exception, swipe.context)
|
||||
} catch (all: Exception) {
|
||||
handleException(all, swipe.context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.app.UApp
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.ServerSetting
|
||||
import org.moire.ultrasonic.data.ServerSettingDao
|
||||
@ -25,20 +27,6 @@ class ServerSettingsModel(
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private const val PREFERENCES_KEY_SERVER_MIGRATED = "serverMigrated"
|
||||
// These constants were removed from Constants.java as they are deprecated and only used here
|
||||
private const val PREFERENCES_KEY_JUKEBOX_BY_DEFAULT = "jukeboxEnabled"
|
||||
private const val PREFERENCES_KEY_SERVER_NAME = "serverName"
|
||||
private const val PREFERENCES_KEY_SERVER_URL = "serverUrl"
|
||||
private const val PREFERENCES_KEY_ACTIVE_SERVERS = "activeServers"
|
||||
private const val PREFERENCES_KEY_USERNAME = "username"
|
||||
private const val PREFERENCES_KEY_PASSWORD = "password"
|
||||
private const val PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate"
|
||||
private const val PREFERENCES_KEY_LDAP_SUPPORT = "enableLdapSupport"
|
||||
private const val PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId"
|
||||
}
|
||||
|
||||
private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
/**
|
||||
@ -67,8 +55,8 @@ class ServerSettingsModel(
|
||||
repository.insert(newServerSetting)
|
||||
index++
|
||||
Timber.i(
|
||||
"Imported server from Preferences to Database:" +
|
||||
" ${newServerSetting.name}"
|
||||
"Imported server from Preferences to Database: %s",
|
||||
newServerSetting.name
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -187,6 +175,23 @@ class ServerSettingsModel(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new Setting into the database
|
||||
* @return The id of the demo server
|
||||
*/
|
||||
fun addDemoServer(): Int {
|
||||
val demo = DEMO_SERVER_CONFIG.copy()
|
||||
|
||||
runBlocking {
|
||||
demo.index = (repository.count() ?: 0) + 1
|
||||
demo.id = (repository.getMaxId() ?: 0) + 1
|
||||
repository.insert(demo)
|
||||
Timber.d("Added demo server")
|
||||
}
|
||||
|
||||
return demo.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up a Server Setting stored in the obsolete Preferences
|
||||
*/
|
||||
@ -262,4 +267,36 @@ class ServerSettingsModel(
|
||||
editor.putBoolean(PREFERENCES_KEY_SERVER_MIGRATED + preferenceId, true)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFERENCES_KEY_SERVER_MIGRATED = "serverMigrated"
|
||||
// These constants were removed from Constants.java as they are deprecated and only used here
|
||||
private const val PREFERENCES_KEY_JUKEBOX_BY_DEFAULT = "jukeboxEnabled"
|
||||
private const val PREFERENCES_KEY_SERVER_NAME = "serverName"
|
||||
private const val PREFERENCES_KEY_SERVER_URL = "serverUrl"
|
||||
private const val PREFERENCES_KEY_ACTIVE_SERVERS = "activeServers"
|
||||
private const val PREFERENCES_KEY_USERNAME = "username"
|
||||
private const val PREFERENCES_KEY_PASSWORD = "password"
|
||||
private const val PREFERENCES_KEY_ALLOW_SELF_SIGNED_CERTIFICATE = "allowSSCertificate"
|
||||
private const val PREFERENCES_KEY_LDAP_SUPPORT = "enableLdapSupport"
|
||||
private const val PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId"
|
||||
|
||||
private val DEMO_SERVER_CONFIG = ServerSetting(
|
||||
id = 0,
|
||||
index = 0,
|
||||
name = UApp.applicationContext().getString(R.string.server_menu_demo),
|
||||
url = "https://demo.ampache.dev",
|
||||
userName = "ultrasonic_demo",
|
||||
password = "W7DumQ3ZUR89Se3",
|
||||
jukeboxByDefault = false,
|
||||
allowSelfSignedCertificate = false,
|
||||
ldapSupport = false,
|
||||
musicFolderId = null,
|
||||
minimumApiVersion = "1.13.0",
|
||||
chatSupport = true,
|
||||
bookmarkSupport = true,
|
||||
shareSupport = true,
|
||||
podcastSupport = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
* Loads avatars from subsonic api.
|
||||
*/
|
||||
class AvatarRequestHandler(
|
||||
private val apiClient: SubsonicAPIClient
|
||||
private val client: SubsonicAPIClient
|
||||
) : RequestHandler() {
|
||||
override fun canHandleRequest(data: Request): Boolean {
|
||||
return with(data.uri) {
|
||||
@ -23,7 +23,9 @@ class AvatarRequestHandler(
|
||||
val username = request.uri.getQueryParameter(QUERY_USERNAME)
|
||||
?: throw IllegalArgumentException("Nullable username")
|
||||
|
||||
val response = apiClient.getAvatar(username)
|
||||
// Inverted call order, because Mockito has problems with chained calls.
|
||||
val response = client.toStreamResponse(client.api.getAvatar(username).execute())
|
||||
|
||||
if (response.hasError() || response.stream == null) {
|
||||
throw IOException("${response.apiError}")
|
||||
} else {
|
||||
|
@ -7,13 +7,14 @@ import com.squareup.picasso.RequestHandler
|
||||
import java.io.IOException
|
||||
import okio.Okio
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.api.subsonic.toStreamResponse
|
||||
import org.moire.ultrasonic.util.FileUtil.SUFFIX_LARGE
|
||||
import org.moire.ultrasonic.util.FileUtil.SUFFIX_SMALL
|
||||
|
||||
/**
|
||||
* Loads cover arts from subsonic api.
|
||||
*/
|
||||
class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : RequestHandler() {
|
||||
class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHandler() {
|
||||
override fun canHandleRequest(data: Request): Boolean {
|
||||
return with(data.uri) {
|
||||
scheme == SCHEME &&
|
||||
@ -38,7 +39,10 @@ class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : Request
|
||||
}
|
||||
|
||||
// Try to fetch the image from the API
|
||||
val response = apiClient.getCoverArt(id, size)
|
||||
// Inverted call order, because Mockito has problems with chained calls.
|
||||
val response = client.toStreamResponse(client.api.getCoverArt(id, size).execute())
|
||||
|
||||
// Handle the response
|
||||
if (!response.hasError() && response.stream != null) {
|
||||
return Result(Okio.source(response.stream!!), NETWORK)
|
||||
}
|
||||
|
@ -13,8 +13,9 @@ import java.io.OutputStream
|
||||
import org.moire.ultrasonic.BuildConfig
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.api.subsonic.throwOnFailure
|
||||
import org.moire.ultrasonic.api.subsonic.toStreamResponse
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.service.RESTMusicService
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
@ -24,9 +25,12 @@ import timber.log.Timber
|
||||
*/
|
||||
class ImageLoader(
|
||||
context: Context,
|
||||
private val apiClient: SubsonicAPIClient,
|
||||
apiClient: SubsonicAPIClient,
|
||||
private val config: ImageLoaderConfig
|
||||
) {
|
||||
// Shortcut
|
||||
@Suppress("VariableNaming", "PropertyName")
|
||||
val API = apiClient.api
|
||||
|
||||
private val picasso = Picasso.Builder(context)
|
||||
.addRequestHandler(CoverArtRequestHandler(apiClient))
|
||||
@ -143,8 +147,8 @@ class ImageLoader(
|
||||
|
||||
// Query the API
|
||||
Timber.d("Loading cover art for: %s", entry)
|
||||
val response = apiClient.getCoverArt(id!!, size.toLong())
|
||||
RESTMusicService.checkStreamResponseError(response)
|
||||
val response = API.getCoverArt(id!!, size.toLong()).execute().toStreamResponse()
|
||||
response.throwOnFailure()
|
||||
|
||||
// Check for failure
|
||||
if (response.stream == null) return
|
||||
|
@ -1,66 +0,0 @@
|
||||
package org.moire.ultrasonic.service
|
||||
|
||||
import java.io.IOException
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIDefinition
|
||||
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import retrofit2.Response
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* This call wraps Subsonic API calls so their results can be checked for errors, API version, etc
|
||||
*/
|
||||
class ApiCallResponseChecker(
|
||||
private val subsonicAPIClient: SubsonicAPIClient,
|
||||
private val activeServerProvider: ActiveServerProvider
|
||||
) {
|
||||
/**
|
||||
* Executes a Subsonic API call with response check
|
||||
*/
|
||||
@Throws(SubsonicRESTException::class, IOException::class)
|
||||
fun <T : Response<out SubsonicResponse>> callWithResponseCheck(
|
||||
call: (SubsonicAPIDefinition) -> T
|
||||
): T {
|
||||
// Check for API version when first contacting the server
|
||||
if (activeServerProvider.getActiveServer().minimumApiVersion == null) {
|
||||
try {
|
||||
val response = subsonicAPIClient.api.ping().execute()
|
||||
if (response.body() != null) {
|
||||
val restApiVersion = response.body()!!.version.restApiVersion
|
||||
Timber.i("Server minimum API version set to %s", restApiVersion)
|
||||
activeServerProvider.setMinimumApiVersion(restApiVersion)
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
// This Ping is only used to get the API Version, if it fails, that's no problem.
|
||||
}
|
||||
}
|
||||
|
||||
// This call will be now executed with the correct API Version, so it shouldn't fail
|
||||
val result = call.invoke(subsonicAPIClient.api)
|
||||
checkResponseSuccessful(result)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Exceptions from the results returned by the Subsonic API
|
||||
*/
|
||||
companion object {
|
||||
@Throws(SubsonicRESTException::class, IOException::class)
|
||||
fun checkResponseSuccessful(response: Response<out SubsonicResponse>) {
|
||||
if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) {
|
||||
return
|
||||
}
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Server error, code: " + response.code())
|
||||
} else if (
|
||||
response.body()!!.status === SubsonicResponse.Status.ERROR &&
|
||||
response.body()!!.error != null
|
||||
) {
|
||||
throw SubsonicRESTException(response.body()!!.error!!)
|
||||
} else {
|
||||
throw IOException("Failed to perform request: " + response.code())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -264,8 +264,8 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getVideoUrl(id: String, useFlash: Boolean): String? {
|
||||
return musicService.getVideoUrl(id, useFlash)
|
||||
override fun getVideoUrl(id: String): String? {
|
||||
return musicService.getVideoUrl(id)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
|
@ -28,6 +28,7 @@ import java.security.cert.CertificateException
|
||||
import javax.net.ssl.SSLException
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicRESTException
|
||||
import org.moire.ultrasonic.subsonic.getLocalizedErrorMessage
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
|
@ -123,7 +123,7 @@ interface MusicService {
|
||||
|
||||
// TODO: Refactor and remove this call (see RestMusicService implementation)
|
||||
@Throws(Exception::class)
|
||||
fun getVideoUrl(id: String, useFlash: Boolean): String?
|
||||
fun getVideoUrl(id: String): String?
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun updateJukeboxPlaylist(ids: List<String>?): JukeboxStatus
|
||||
|
@ -396,7 +396,7 @@ class OfflineMusicService : MusicService, KoinComponent {
|
||||
}
|
||||
|
||||
@Throws(OfflineException::class)
|
||||
override fun getVideoUrl(id: String, useFlash: Boolean): String? {
|
||||
override fun getVideoUrl(id: String): String? {
|
||||
throw OfflineException("getVideoUrl isn't available in offline mode")
|
||||
}
|
||||
|
||||
|
@ -11,13 +11,15 @@ 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 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.models.AlbumListType.Companion.fromName
|
||||
import org.moire.ultrasonic.api.subsonic.models.JukeboxAction
|
||||
import org.moire.ultrasonic.api.subsonic.response.StreamResponse
|
||||
import org.moire.ultrasonic.api.subsonic.throwOnFailure
|
||||
import org.moire.ultrasonic.api.subsonic.toStreamResponse
|
||||
import org.moire.ultrasonic.cache.PermanentFileStorage
|
||||
import org.moire.ultrasonic.cache.serializers.getIndexesSerializer
|
||||
import org.moire.ultrasonic.cache.serializers.getMusicFolderListSerializer
|
||||
@ -50,20 +52,23 @@ import timber.log.Timber
|
||||
*/
|
||||
@Suppress("LargeClass")
|
||||
open class RESTMusicService(
|
||||
private val subsonicAPIClient: SubsonicAPIClient,
|
||||
val subsonicAPIClient: SubsonicAPIClient,
|
||||
private val fileStorage: PermanentFileStorage,
|
||||
private val activeServerProvider: ActiveServerProvider,
|
||||
private val responseChecker: ApiCallResponseChecker
|
||||
private val activeServerProvider: ActiveServerProvider
|
||||
) : MusicService {
|
||||
|
||||
// Shortcut to the API
|
||||
@Suppress("VariableNaming", "PropertyName")
|
||||
val API = subsonicAPIClient.api
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun ping() {
|
||||
responseChecker.callWithResponseCheck { api -> api.ping().execute() }
|
||||
API.ping().execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun isLicenseValid(): Boolean {
|
||||
val response = responseChecker.callWithResponseCheck { api -> api.getLicense().execute() }
|
||||
val response = API.getLicense().execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.license.valid
|
||||
}
|
||||
@ -78,9 +83,7 @@ open class RESTMusicService(
|
||||
|
||||
if (cachedMusicFolders != null && !refresh) return cachedMusicFolders
|
||||
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getMusicFolders().execute()
|
||||
}
|
||||
val response = API.getMusicFolders().execute().throwOnFailure()
|
||||
|
||||
val musicFolders = response.body()!!.musicFolders.toDomainEntityList()
|
||||
fileStorage.store(MUSIC_FOLDER_STORAGE_NAME, musicFolders, getMusicFolderListSerializer())
|
||||
@ -98,9 +101,7 @@ open class RESTMusicService(
|
||||
val cachedIndexes = fileStorage.load(indexName, getIndexesSerializer())
|
||||
if (cachedIndexes != null && !refresh) return cachedIndexes
|
||||
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getIndexes(musicFolderId, null).execute()
|
||||
}
|
||||
val response = API.getIndexes(musicFolderId, null).execute().throwOnFailure()
|
||||
|
||||
val indexes = response.body()!!.indexes.toDomainEntity()
|
||||
fileStorage.store(indexName, indexes, getIndexesSerializer())
|
||||
@ -114,9 +115,7 @@ open class RESTMusicService(
|
||||
val cachedArtists = fileStorage.load(ARTISTS_STORAGE_NAME, getIndexesSerializer())
|
||||
if (cachedArtists != null && !refresh) return cachedArtists
|
||||
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getArtists(null).execute()
|
||||
}
|
||||
val response = API.getArtists(null).execute().throwOnFailure()
|
||||
|
||||
val indexes = response.body()!!.indexes.toDomainEntity()
|
||||
fileStorage.store(ARTISTS_STORAGE_NAME, indexes, getIndexesSerializer())
|
||||
@ -129,7 +128,7 @@ open class RESTMusicService(
|
||||
albumId: String?,
|
||||
artistId: String?
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api -> api.star(id, albumId, artistId).execute() }
|
||||
API.star(id, albumId, artistId).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -138,7 +137,7 @@ open class RESTMusicService(
|
||||
albumId: String?,
|
||||
artistId: String?
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api -> api.unstar(id, albumId, artistId).execute() }
|
||||
API.unstar(id, albumId, artistId).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -146,7 +145,7 @@ open class RESTMusicService(
|
||||
id: String,
|
||||
rating: Int
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api -> api.setRating(id, rating).execute() }
|
||||
API.setRating(id, rating).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -155,9 +154,7 @@ open class RESTMusicService(
|
||||
name: String?,
|
||||
refresh: Boolean
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getMusicDirectory(id).execute()
|
||||
}
|
||||
val response = API.getMusicDirectory(id).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.musicDirectory.toDomainEntity()
|
||||
}
|
||||
@ -168,7 +165,7 @@ open class RESTMusicService(
|
||||
name: String?,
|
||||
refresh: Boolean
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api -> api.getArtist(id).execute() }
|
||||
val response = API.getArtist(id).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.artist.toMusicDirectoryDomainEntity()
|
||||
}
|
||||
@ -179,7 +176,7 @@ open class RESTMusicService(
|
||||
name: String?,
|
||||
refresh: Boolean
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api -> api.getAlbum(id).execute() }
|
||||
val response = API.getAlbum(id).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.album.toMusicDirectoryDomainEntity()
|
||||
}
|
||||
@ -207,10 +204,9 @@ open class RESTMusicService(
|
||||
private fun searchOld(
|
||||
criteria: SearchCriteria
|
||||
): SearchResult {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.search(null, null, null, criteria.query, criteria.songCount, null, null)
|
||||
.execute()
|
||||
}
|
||||
val response =
|
||||
API.search(null, null, null, criteria.query, criteria.songCount, null, null)
|
||||
.execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.searchResult.toDomainEntity()
|
||||
}
|
||||
@ -223,12 +219,10 @@ open class RESTMusicService(
|
||||
criteria: SearchCriteria
|
||||
): SearchResult {
|
||||
requireNotNull(criteria.query) { "Query param is null" }
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.search2(
|
||||
criteria.query, criteria.artistCount, null, criteria.albumCount, null,
|
||||
criteria.songCount, null
|
||||
).execute()
|
||||
}
|
||||
val response = API.search2(
|
||||
criteria.query, criteria.artistCount, null, criteria.albumCount, null,
|
||||
criteria.songCount, null
|
||||
).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.searchResult.toDomainEntity()
|
||||
}
|
||||
@ -238,12 +232,10 @@ open class RESTMusicService(
|
||||
criteria: SearchCriteria
|
||||
): SearchResult {
|
||||
requireNotNull(criteria.query) { "Query param is null" }
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.search3(
|
||||
criteria.query, criteria.artistCount, null, criteria.albumCount, null,
|
||||
criteria.songCount, null
|
||||
).execute()
|
||||
}
|
||||
val response = API.search3(
|
||||
criteria.query, criteria.artistCount, null, criteria.albumCount, null,
|
||||
criteria.songCount, null
|
||||
).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.searchResult.toDomainEntity()
|
||||
}
|
||||
@ -253,9 +245,7 @@ open class RESTMusicService(
|
||||
id: String,
|
||||
name: String
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getPlaylist(id).execute()
|
||||
}
|
||||
val response = API.getPlaylist(id).execute().throwOnFailure()
|
||||
|
||||
val playlist = response.body()!!.playlist.toMusicDirectoryDomainEntity()
|
||||
savePlaylist(name, playlist)
|
||||
@ -300,9 +290,7 @@ open class RESTMusicService(
|
||||
override fun getPlaylists(
|
||||
refresh: Boolean
|
||||
): List<Playlist> {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getPlaylists(null).execute()
|
||||
}
|
||||
val response = API.getPlaylists(null).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.playlists.toDomainEntitiesList()
|
||||
}
|
||||
@ -318,16 +306,15 @@ open class RESTMusicService(
|
||||
for ((id1) in entries) {
|
||||
pSongIds.add(id1)
|
||||
}
|
||||
responseChecker.callWithResponseCheck { api ->
|
||||
api.createPlaylist(id, name, pSongIds.toList()).execute()
|
||||
}
|
||||
|
||||
API.createPlaylist(id, name, pSongIds.toList()).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun deletePlaylist(
|
||||
id: String
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api -> api.deletePlaylist(id).execute() }
|
||||
API.deletePlaylist(id).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -337,19 +324,15 @@ open class RESTMusicService(
|
||||
comment: String?,
|
||||
pub: Boolean
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api ->
|
||||
api.updatePlaylist(id, name, comment, pub, null, null)
|
||||
.execute()
|
||||
}
|
||||
API.updatePlaylist(id, name, comment, pub, null, null)
|
||||
.execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getPodcastsChannels(
|
||||
refresh: Boolean
|
||||
): List<PodcastsChannel> {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getPodcasts(false, null).execute()
|
||||
}
|
||||
val response = API.getPodcasts(false, null).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.podcastChannels.toDomainEntitiesList()
|
||||
}
|
||||
@ -358,9 +341,7 @@ open class RESTMusicService(
|
||||
override fun getPodcastEpisodes(
|
||||
podcastChannelId: String?
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getPodcasts(true, podcastChannelId).execute()
|
||||
}
|
||||
val response = API.getPodcasts(true, podcastChannelId).execute().throwOnFailure()
|
||||
|
||||
val podcastEntries = response.body()!!.podcastChannels[0].episodeList
|
||||
val musicDirectory = MusicDirectory()
|
||||
@ -384,9 +365,7 @@ open class RESTMusicService(
|
||||
artist: String,
|
||||
title: String
|
||||
): Lyrics {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getLyrics(artist, title).execute()
|
||||
}
|
||||
val response = API.getLyrics(artist, title).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.lyrics.toDomainEntity()
|
||||
}
|
||||
@ -396,9 +375,7 @@ open class RESTMusicService(
|
||||
id: String,
|
||||
submission: Boolean
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api ->
|
||||
api.scrobble(id, null, submission).execute()
|
||||
}
|
||||
API.scrobble(id, null, submission).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -408,10 +385,15 @@ open class RESTMusicService(
|
||||
offset: Int,
|
||||
musicFolderId: String?
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getAlbumList(fromName(type), size, offset, null, null, null, musicFolderId)
|
||||
.execute()
|
||||
}
|
||||
val response = API.getAlbumList(
|
||||
fromName(type),
|
||||
size,
|
||||
offset,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
musicFolderId
|
||||
).execute().throwOnFailure()
|
||||
|
||||
val childList = response.body()!!.albumList.toDomainEntityList()
|
||||
val result = MusicDirectory()
|
||||
@ -427,17 +409,15 @@ open class RESTMusicService(
|
||||
offset: Int,
|
||||
musicFolderId: String?
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getAlbumList2(
|
||||
fromName(type),
|
||||
size,
|
||||
offset,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
musicFolderId
|
||||
).execute()
|
||||
}
|
||||
val response = API.getAlbumList2(
|
||||
fromName(type),
|
||||
size,
|
||||
offset,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
musicFolderId
|
||||
).execute().throwOnFailure()
|
||||
|
||||
val result = MusicDirectory()
|
||||
result.addAll(response.body()!!.albumList.toDomainEntityList())
|
||||
@ -449,15 +429,13 @@ open class RESTMusicService(
|
||||
override fun getRandomSongs(
|
||||
size: Int
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getRandomSongs(
|
||||
size,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
).execute()
|
||||
}
|
||||
val response = API.getRandomSongs(
|
||||
size,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
).execute().throwOnFailure()
|
||||
|
||||
val result = MusicDirectory()
|
||||
result.addAll(response.body()!!.songsList.toDomainEntityList())
|
||||
@ -467,18 +445,14 @@ open class RESTMusicService(
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getStarred(): SearchResult {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getStarred(null).execute()
|
||||
}
|
||||
val response = API.getStarred(null).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.starred.toDomainEntity()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getStarred2(): SearchResult {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getStarred2(null).execute()
|
||||
}
|
||||
val response = API.getStarred2(null).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.starred2.toDomainEntity()
|
||||
}
|
||||
@ -491,8 +465,10 @@ open class RESTMusicService(
|
||||
): Pair<InputStream, Boolean> {
|
||||
val songOffset = if (offset < 0) 0 else offset
|
||||
|
||||
val response = subsonicAPIClient.stream(song.id, maxBitrate, songOffset)
|
||||
checkStreamResponseError(response)
|
||||
val response = API.stream(song.id, maxBitrate, offset = songOffset)
|
||||
.execute().toStreamResponse()
|
||||
|
||||
response.throwOnFailure()
|
||||
|
||||
if (response.stream == null) {
|
||||
throw IOException("Null stream response")
|
||||
@ -502,41 +478,51 @@ 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,
|
||||
useFlash: Boolean
|
||||
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)
|
||||
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()
|
||||
}
|
||||
|
||||
Thread(
|
||||
{
|
||||
expectedResult[0] = subsonicAPIClient.getStreamUrl(id) + "&format=raw"
|
||||
latch.countDown()
|
||||
},
|
||||
"Get-Video-Url"
|
||||
).start()
|
||||
// Create a new Okhttp client
|
||||
val client = builder.build()
|
||||
|
||||
latch.await(5, TimeUnit.SECONDS)
|
||||
// Get the request from Retrofit, but don't execute it!
|
||||
val request = API.stream(id, format = "raw").request()
|
||||
|
||||
return expectedResult[0]!!
|
||||
// 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)
|
||||
override fun updateJukeboxPlaylist(
|
||||
ids: List<String>?
|
||||
): JukeboxStatus {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.jukeboxControl(JukeboxAction.SET, null, null, ids, null)
|
||||
.execute()
|
||||
}
|
||||
val response = API.jukeboxControl(JukeboxAction.SET, null, null, ids, null)
|
||||
.execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.jukebox.toDomainEntity()
|
||||
}
|
||||
@ -546,40 +532,32 @@ open class RESTMusicService(
|
||||
index: Int,
|
||||
offsetSeconds: Int
|
||||
): JukeboxStatus {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.jukeboxControl(JukeboxAction.SKIP, index, offsetSeconds, null, null)
|
||||
.execute()
|
||||
}
|
||||
val response = API.jukeboxControl(JukeboxAction.SKIP, index, offsetSeconds, null, null)
|
||||
.execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.jukebox.toDomainEntity()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun stopJukebox(): JukeboxStatus {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.jukeboxControl(JukeboxAction.STOP, null, null, null, null)
|
||||
.execute()
|
||||
}
|
||||
val response = API.jukeboxControl(JukeboxAction.STOP, null, null, null, null)
|
||||
.execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.jukebox.toDomainEntity()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun startJukebox(): JukeboxStatus {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.jukeboxControl(JukeboxAction.START, null, null, null, null)
|
||||
.execute()
|
||||
}
|
||||
val response = API.jukeboxControl(JukeboxAction.START, null, null, null, null)
|
||||
.execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.jukebox.toDomainEntity()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getJukeboxStatus(): JukeboxStatus {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.jukeboxControl(JukeboxAction.STATUS, null, null, null, null)
|
||||
.execute()
|
||||
}
|
||||
val response = API.jukeboxControl(JukeboxAction.STATUS, null, null, null, null)
|
||||
.execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.jukebox.toDomainEntity()
|
||||
}
|
||||
@ -588,10 +566,8 @@ open class RESTMusicService(
|
||||
override fun setJukeboxGain(
|
||||
gain: Float
|
||||
): JukeboxStatus {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.jukeboxControl(JukeboxAction.SET_GAIN, null, null, null, gain)
|
||||
.execute()
|
||||
}
|
||||
val response = API.jukeboxControl(JukeboxAction.SET_GAIN, null, null, null, gain)
|
||||
.execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.jukebox.toDomainEntity()
|
||||
}
|
||||
@ -600,7 +576,7 @@ open class RESTMusicService(
|
||||
override fun getShares(
|
||||
refresh: Boolean
|
||||
): List<Share> {
|
||||
val response = responseChecker.callWithResponseCheck { api -> api.getShares().execute() }
|
||||
val response = API.getShares().execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.shares.toDomainEntitiesList()
|
||||
}
|
||||
@ -609,7 +585,7 @@ open class RESTMusicService(
|
||||
override fun getGenres(
|
||||
refresh: Boolean
|
||||
): List<Genre>? {
|
||||
val response = responseChecker.callWithResponseCheck { api -> api.getGenres().execute() }
|
||||
val response = API.getGenres().execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.genresList.toDomainEntityList()
|
||||
}
|
||||
@ -620,9 +596,7 @@ open class RESTMusicService(
|
||||
count: Int,
|
||||
offset: Int
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getSongsByGenre(genre, count, offset, null).execute()
|
||||
}
|
||||
val response = API.getSongsByGenre(genre, count, offset, null).execute().throwOnFailure()
|
||||
|
||||
val result = MusicDirectory()
|
||||
result.addAll(response.body()!!.songsList.toDomainEntityList())
|
||||
@ -634,9 +608,7 @@ open class RESTMusicService(
|
||||
override fun getUser(
|
||||
username: String
|
||||
): UserInfo {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getUser(username).execute()
|
||||
}
|
||||
val response = API.getUser(username).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.user.toDomainEntity()
|
||||
}
|
||||
@ -645,9 +617,7 @@ open class RESTMusicService(
|
||||
override fun getChatMessages(
|
||||
since: Long?
|
||||
): List<ChatMessage> {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.getChatMessages(since).execute()
|
||||
}
|
||||
val response = API.getChatMessages(since).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.chatMessages.toDomainEntitiesList()
|
||||
}
|
||||
@ -656,12 +626,12 @@ open class RESTMusicService(
|
||||
override fun addChatMessage(
|
||||
message: String
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api -> api.addChatMessage(message).execute() }
|
||||
API.addChatMessage(message).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getBookmarks(): List<Bookmark> {
|
||||
val response = responseChecker.callWithResponseCheck { api -> api.getBookmarks().execute() }
|
||||
val response = API.getBookmarks().execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.bookmarkList.toDomainEntitiesList()
|
||||
}
|
||||
@ -671,23 +641,21 @@ open class RESTMusicService(
|
||||
id: String,
|
||||
position: Int
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api ->
|
||||
api.createBookmark(id, position.toLong(), null).execute()
|
||||
}
|
||||
API.createBookmark(id, position.toLong(), null).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun deleteBookmark(
|
||||
id: String
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api -> api.deleteBookmark(id).execute() }
|
||||
API.deleteBookmark(id).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getVideos(
|
||||
refresh: Boolean
|
||||
): MusicDirectory {
|
||||
val response = responseChecker.callWithResponseCheck { api -> api.getVideos().execute() }
|
||||
val response = API.getVideos().execute().throwOnFailure()
|
||||
|
||||
val musicDirectory = MusicDirectory()
|
||||
musicDirectory.addAll(response.body()!!.videosList.toDomainEntityList())
|
||||
@ -701,9 +669,7 @@ open class RESTMusicService(
|
||||
description: String?,
|
||||
expires: Long?
|
||||
): List<Share> {
|
||||
val response = responseChecker.callWithResponseCheck { api ->
|
||||
api.createShare(ids, description, expires).execute()
|
||||
}
|
||||
val response = API.createShare(ids, description, expires).execute().throwOnFailure()
|
||||
|
||||
return response.body()!!.shares.toDomainEntitiesList()
|
||||
}
|
||||
@ -712,7 +678,7 @@ open class RESTMusicService(
|
||||
override fun deleteShare(
|
||||
id: String
|
||||
) {
|
||||
responseChecker.callWithResponseCheck { api -> api.deleteShare(id).execute() }
|
||||
API.deleteShare(id).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
@ -726,8 +692,15 @@ open class RESTMusicService(
|
||||
expiresValue = null
|
||||
}
|
||||
|
||||
responseChecker.callWithResponseCheck { api ->
|
||||
api.updateShare(id, description, expiresValue).execute()
|
||||
API.updateShare(id, description, expiresValue).execute().throwOnFailure()
|
||||
}
|
||||
|
||||
init {
|
||||
// The client will notice if the minimum supported API version has changed
|
||||
// By registering a callback we ensure this info is saved in the database as well
|
||||
subsonicAPIClient.onProtocolChange = {
|
||||
Timber.i("Server minimum API version set to %s", it)
|
||||
activeServerProvider.setMinimumApiVersion(it.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -735,19 +708,5 @@ open class RESTMusicService(
|
||||
private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder"
|
||||
private const val INDEXES_STORAGE_NAME = "indexes"
|
||||
private const val ARTISTS_STORAGE_NAME = "artists"
|
||||
|
||||
// TODO: Move to response checker
|
||||
@Throws(SubsonicRESTException::class, IOException::class)
|
||||
fun checkStreamResponseError(response: StreamResponse) {
|
||||
if (response.hasError() || response.stream == null) {
|
||||
if (response.apiError != null) {
|
||||
throw SubsonicRESTException(response.apiError!!)
|
||||
} else {
|
||||
throw IOException(
|
||||
"Failed to make endpoint request, code: " + response.responseHttpCode
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicError.TokenAuthNotSupportedForL
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicError.TrialPeriodIsOver
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicError.UserNotAuthorizedForOperation
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicError.WrongUsernameOrPassword
|
||||
import org.moire.ultrasonic.service.SubsonicRESTException
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicRESTException
|
||||
|
||||
/**
|
||||
* Extension for [SubsonicRESTException] that returns localized error string, that can used to
|
||||
@ -21,7 +21,7 @@ import org.moire.ultrasonic.service.SubsonicRESTException
|
||||
fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String =
|
||||
when (error) {
|
||||
is Generic -> {
|
||||
val message = error.message
|
||||
val message = (error as Generic).message
|
||||
val errorMessage = if (message == "") {
|
||||
context.getString(R.string.api_subsonic_generic_no_message)
|
||||
} else {
|
||||
|
@ -1,22 +1,30 @@
|
||||
package org.moire.ultrasonic.subsonic
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||
import org.moire.ultrasonic.util.Util
|
||||
|
||||
/**
|
||||
* This utility class helps starting video playback
|
||||
*/
|
||||
class VideoPlayer() {
|
||||
class VideoPlayer {
|
||||
fun playVideo(context: Context, entry: MusicDirectory.Entry?) {
|
||||
if (!Util.isNetworkConnected()) {
|
||||
if (!Util.isNetworkConnected() || entry == null) {
|
||||
Util.toast(context, R.string.select_album_no_network)
|
||||
return
|
||||
}
|
||||
val player = Util.getVideoPlayerType()
|
||||
try {
|
||||
player.playVideo(context, entry)
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
val url = MusicServiceFactory.getMusicService().getVideoUrl(entry.id)
|
||||
intent.setDataAndType(
|
||||
Uri.parse(url),
|
||||
"video/*"
|
||||
)
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Util.toast(context, e.toString(), false)
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ import org.moire.ultrasonic.service.DownloadFile
|
||||
import org.moire.ultrasonic.service.MediaPlayerController
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.util.VideoPlayerType
|
||||
import org.moire.ultrasonic.view.EntryAdapter.SongViewHolder
|
||||
import timber.log.Timber
|
||||
|
||||
@ -111,8 +110,7 @@ class SongView(context: Context) : UpdateView(context), Checkable, KoinComponent
|
||||
val transcodedSuffix = song.transcodedSuffix
|
||||
|
||||
fileFormat = if (
|
||||
TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix ||
|
||||
song.isVideo && Util.getVideoPlayerType() !== VideoPlayerType.FLASH
|
||||
TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo
|
||||
) suffix else String.format("%s > %s", suffix, transcodedSuffix)
|
||||
|
||||
val artistName = song.artist
|
||||
|
@ -101,7 +101,6 @@
|
||||
<string name="main.songs_starred">Označené hvězdičkou</string>
|
||||
<string name="main.songs_title">Skladby</string>
|
||||
<string name="main.videos">Videa</string>
|
||||
<string name="main.welcome_text">Vítejte v aplikaci Ultrasonic! Aplikace není ještě nakonfigurována. Poté co nastavíte svůj osobní server (dostupný na <b>subsonic.org</b>), vyberte <i>Správa serverů</i> v <b>Nastavení</b> a připojte aplikaci.</string>
|
||||
<string name="main.welcome_title">Vítejte!</string>
|
||||
<string name="menu.about">O aplikaci</string>
|
||||
<string name="menu.common">Další</string>
|
||||
@ -130,16 +129,11 @@
|
||||
<string name="search.search">Kliknout pro vyhledání</string>
|
||||
<string name="search.songs">Skladby</string>
|
||||
<string name="search.title">Hledat</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">Zkušební doba vypršela</string>
|
||||
<string name="select_album.donate_dialog_later">Pozdějí</string>
|
||||
<string name="select_album.donate_dialog_message">Získejte neomezená stahování přispěním na Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Hned</string>
|
||||
<string name="select_album.empty">Média nenalezena</string>
|
||||
<string name="select_album.n_selected">%d skladeb označeno.</string>
|
||||
<string name="select_album.n_unselected">%d skladeb odznačeno.</string>
|
||||
<string name="select_album.no_network">Varování: Připojení nedostupné.</string>
|
||||
<string name="select_album.no_sdcard">Chyba: SD karta nedostupná.</string>
|
||||
<string name="select_album.not_licensed">Server bez licence. Zbývá %d dní zkušební doby.</string>
|
||||
<string name="select_album.play_all">Přehrát vše</string>
|
||||
<string name="select_artist.all_folders">Všechny adresáře</string>
|
||||
<string name="select_artist.folder">Vybrat adresář</string>
|
||||
@ -311,8 +305,7 @@
|
||||
<string name="settings.use_folder_for_album_artist_summary">Očekává jména hlavních adresářů obsahující jména umělců</string>
|
||||
<string name="settings.use_id3">Procházet za použití ID3 tagů</string>
|
||||
<string name="settings.use_id3_summary">Používat metodu ID3 tagů místo jmen na základě adresářové struktury</string>
|
||||
<string name="settings.video_title">Video</string>
|
||||
<string name="settings.video_player">Videopřehrávač</string>
|
||||
<string name="main.video">Video</string>
|
||||
<string name="settings.view_refresh">Obnovení náhledu</string>
|
||||
<string name="settings.view_refresh_500">.5 sekundy</string>
|
||||
<string name="settings.view_refresh_1000">1 sekunda</string>
|
||||
@ -334,8 +327,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">MX Player není nainstalován. Nainstalujte z Obchodu Play nebo změňte nastavení videí.</string>
|
||||
<string name="video.get_mx_player_button">Stáhnout MX Player</string>
|
||||
<string name="widget.initial_text">Ťuknutím vybrat hudbu</string>
|
||||
<string name="widget.sdcard_busy">SD karta nedostupná</string>
|
||||
<string name="widget.sdcard_missing">Chybí SD karta</string>
|
||||
@ -371,9 +362,6 @@
|
||||
<string name="settings.share_greeting_default">Výchozí pozdrav sdílení</string>
|
||||
<string name="share_default_greeting">Mrkni na hudbu sdílenou z %s</string>
|
||||
<string name="share_via">Sdílet skladby přes</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Výchozí</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Sdílení</string>
|
||||
<string name="select_album_all_songs">Všechny skladby od %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Zobrazit všechny skladby umělce</string>
|
||||
|
@ -100,7 +100,6 @@
|
||||
<string name="main.songs_starred">Mit Stern</string>
|
||||
<string name="main.songs_title">Titel</string>
|
||||
<string name="main.videos">Filme</string>
|
||||
<string name="main.welcome_text">Willkommen bei Ultrasonic! Die App ist zurzeit unkonfiguriert. Nachdem du deinen Server konfiguriert hast (siehe <b>subsonic.org</b>), bitte <i>Server hinzufügen</i> in den <b>Einstellungen</b> klicken um die Verbindun herzustellen.</string>
|
||||
<string name="main.welcome_title">Willkommen</string>
|
||||
<string name="menu.about">Über</string>
|
||||
<string name="menu.common">Allgemein</string>
|
||||
@ -129,16 +128,11 @@
|
||||
<string name="search.search">Neue Suche</string>
|
||||
<string name="search.songs">Titel</string>
|
||||
<string name="search.title">Suche</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">Testperiode zu Ende</string>
|
||||
<string name="select_album.donate_dialog_later">Später</string>
|
||||
<string name="select_album.donate_dialog_message">Unbegresnze Downloads bei Spende an Sunsonic</string>
|
||||
<string name="select_album.donate_dialog_now">Jetzt</string>
|
||||
<string name="select_album.empty">Keine Medien gefunden</string>
|
||||
<string name="select_album.n_selected">%d Titel ausgewählt.</string>
|
||||
<string name="select_album.n_unselected">%d Titel abgewählt.</string>
|
||||
<string name="select_album.no_network">Warnung: kein Netz.</string>
|
||||
<string name="select_album.no_sdcard">Fehler: Keine SD Karte verfügbar.</string>
|
||||
<string name="select_album.not_licensed">Server nicht lizenziert. Noch %d Testtage</string>
|
||||
<string name="select_album.play_all">Alles wiedergeben</string>
|
||||
<string name="select_artist.all_folders">Alle Ordner</string>
|
||||
<string name="select_artist.folder">Ordner wählen</string>
|
||||
@ -308,8 +302,7 @@
|
||||
<string name="settings.use_folder_for_album_artist_summary">Annehmen, dass der Ordner der obersten Ebene der Name des Albumkünstlers ist</string>
|
||||
<string name="settings.use_id3">Durchsuchen von ID3-Tags</string>
|
||||
<string name="settings.use_id3_summary">Nutze ID3 Tag Methode anstatt Dateisystem-Methode</string>
|
||||
<string name="settings.video_title">Film</string>
|
||||
<string name="settings.video_player">Filmwiedergabe</string>
|
||||
<string name="main.video">Film</string>
|
||||
<string name="settings.view_refresh">Aktualisierungsinterval</string>
|
||||
<string name="settings.view_refresh_500">.5 Sekunden</string>
|
||||
<string name="settings.view_refresh_1000">1 Sekunde</string>
|
||||
@ -331,8 +324,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">MX Player ist nicht installiert. Holen Sie ihn sich kostenlos im Play Store, oder ändern Sie die Filmeinstellungen.</string>
|
||||
<string name="video.get_mx_player_button">MX Player holen</string>
|
||||
<string name="widget.initial_text">Berühren, um Musik auszuwählen</string>
|
||||
<string name="widget.sdcard_busy">SD Karte nicht verfügbar</string>
|
||||
<string name="widget.sdcard_missing">Keine SD Karte</string>
|
||||
@ -368,9 +359,6 @@
|
||||
<string name="settings.share_greeting_default">Standard Begrüßung beim Teilen</string>
|
||||
<string name="share_default_greeting">Hör dir mal die Musik an, die ich mit dir über %s geteilt habe.</string>
|
||||
<string name="share_via">Titel teilen über</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Standard</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Freigabe</string>
|
||||
<string name="select_album_all_songs">Alle Titel von %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Alle Titel nach Künstler sortieren</string>
|
||||
|
@ -111,7 +111,6 @@
|
||||
<string name="main.songs_starred">Me gusta</string>
|
||||
<string name="main.songs_title">Canciones</string>
|
||||
<string name="main.videos">Vídeos</string>
|
||||
<string name="main.welcome_text">Te damos la bienvenida a Ultrasonic! La aplicación no está configurada actualmente. Después de que hayas configurado tu servidor personal (disponible desde <b>subsonic.org</b>), por favor haz click en <i>Administrar servidores</i> en <b>Configuración</b> para conectarte con él.</string>
|
||||
<string name="main.welcome_title">¡Saludos!</string>
|
||||
<string name="menu.about">Acerca de</string>
|
||||
<string name="menu.common">Común</string>
|
||||
@ -140,16 +139,11 @@
|
||||
<string name="search.search">Haz click para buscar</string>
|
||||
<string name="search.songs">Canciones</string>
|
||||
<string name="search.title">Buscar</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">El periodo de prueba ha finalizado</string>
|
||||
<string name="select_album.donate_dialog_later">Mas tarde</string>
|
||||
<string name="select_album.donate_dialog_message">Consigue descargas ilimitadas donando a Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Ahora</string>
|
||||
<string name="select_album.empty">No se han encontrado medios</string>
|
||||
<string name="select_album.n_selected">%d pista(s) seleccionada(s).</string>
|
||||
<string name="select_album.n_unselected">%d pista(s) deseleccionada(s).</string>
|
||||
<string name="select_album.no_network">Atención: No hay red disponible.</string>
|
||||
<string name="select_album.no_sdcard">Error: No hay tarjeta SD disponible.</string>
|
||||
<string name="select_album.not_licensed">Servidor sin licencia. Quedan %d dia(s) de prueba.</string>
|
||||
<string name="select_album.play_all">Reproducir todo</string>
|
||||
<string name="select_artist.all_folders">Todas las carpetas</string>
|
||||
<string name="select_artist.folder">Seleccionar la carpeta</string>
|
||||
@ -325,8 +319,7 @@
|
||||
<string name="settings.use_id3_summary">Usar el método de etiquetas ID3 en lugar del método basado en el sistema de ficheros</string>
|
||||
<string name="settings.show_artist_picture">Mostrar la imagen del artista en la lista de artistas</string>
|
||||
<string name="settings.show_artist_picture_summary">Muestra la imagen del artista en la lista de artistas si está disponible</string>
|
||||
<string name="settings.video_title">Vídeo</string>
|
||||
<string name="settings.video_player">Reproductor de vídeo</string>
|
||||
<string name="main.video">Vídeo</string>
|
||||
<string name="settings.view_refresh">Refresco de la vista</string>
|
||||
<string name="settings.view_refresh_500">.5 segundos</string>
|
||||
<string name="settings.view_refresh_1000">1 segundo</string>
|
||||
@ -348,8 +341,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">El MX Player no esta instalado. Descárgalo grátis de la Play Store, o cambia la configuración de vídeo.</string>
|
||||
<string name="video.get_mx_player_button">Obtener MX Player</string>
|
||||
<string name="widget.initial_text">Toca para seleccionar música</string>
|
||||
<string name="widget.sdcard_busy">Tarjeta SD no disponible</string>
|
||||
<string name="widget.sdcard_missing">No hay tarjeta SD</string>
|
||||
@ -385,9 +376,6 @@
|
||||
<string name="settings.share_greeting_default">Saludo predeterminado para los compartidos</string>
|
||||
<string name="share_default_greeting">Echa un vistazo a esta música que te comparto desde %s</string>
|
||||
<string name="share_via">Compartir canciones vía</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Por defecto</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Compartir</string>
|
||||
<string name="select_album_all_songs">Todas las canciones por %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Mostrar todas las canciones por artista</string>
|
||||
|
@ -101,7 +101,6 @@
|
||||
<string name="main.songs_starred">Favoris</string>
|
||||
<string name="main.songs_title">Titres</string>
|
||||
<string name="main.videos">Vidéos</string>
|
||||
<string name="main.welcome_text">Bienvenue dans Ultrasonic ! L\'application n\'est pas configurée. Après avoir configuré votre serveur personnel (disponible à partir de <b>subsonic.org</b>), veuillez accéder aux <b>Paramètres</b> et modifier la configuration pour vous y connecter.</string>
|
||||
<string name="main.welcome_title">Bienvenue !</string>
|
||||
<string name="menu.about">À propos</string>
|
||||
<string name="menu.common">Général</string>
|
||||
@ -130,16 +129,11 @@
|
||||
<string name="search.search">Cliquer pour rechercher</string>
|
||||
<string name="search.songs">Titres</string>
|
||||
<string name="search.title">Recherche</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">La période d\'essai est terminée</string>
|
||||
<string name="select_album.donate_dialog_later">Plus tard</string>
|
||||
<string name="select_album.donate_dialog_message">Obtenez des téléchargements illimités en faisant un don pour Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Maintenant</string>
|
||||
<string name="select_album.empty">Aucun titre trouvé</string>
|
||||
<string name="select_album.n_selected">%d pistes sélectionnées.</string>
|
||||
<string name="select_album.n_unselected">%d pistes non sélectionnés.</string>
|
||||
<string name="select_album.no_network">Avertissement : Aucun réseau disponible.</string>
|
||||
<string name="select_album.no_sdcard">Erreur : Aucune carte SD disponible.</string>
|
||||
<string name="select_album.not_licensed">Serveur sans licence. %d jours d\'essai restants.</string>
|
||||
<string name="select_album.play_all">Tout jouer</string>
|
||||
<string name="select_artist.all_folders">Tous les dossiers</string>
|
||||
<string name="select_artist.folder">Sélectionner le dossier</string>
|
||||
@ -313,8 +307,7 @@
|
||||
<string name="settings.use_id3_summary">Utiliser ID3 Tags à la place du système de fichier basique</string>
|
||||
<string name="settings.show_artist_picture">Afficher l’image de l’artiste dans la liste</string>
|
||||
<string name="settings.show_artist_picture_summary">Affiche l’image de l’artiste dans la liste des artistes si celle-ci est disponible</string>
|
||||
<string name="settings.video_title">Vidéo</string>
|
||||
<string name="settings.video_player">Lecteur vidéo</string>
|
||||
<string name="main.video">Vidéo</string>
|
||||
<string name="settings.view_refresh">Actualisation de la vue</string>
|
||||
<string name="settings.view_refresh_500">0,5 secondes</string>
|
||||
<string name="settings.view_refresh_1000">1 seconde</string>
|
||||
@ -336,8 +329,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 Mo</string>
|
||||
<string name="util.no_time">—:——</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">MX Player n\'est pas installé. Récupérez le gratuitement sur Play Store, ou modifier les paramètres vidéo.</string>
|
||||
<string name="video.get_mx_player_button">Obtenez MX Player</string>
|
||||
<string name="widget.initial_text">Touchez pour sélectionner un titre</string>
|
||||
<string name="widget.sdcard_busy">Carte SD non disponible</string>
|
||||
<string name="widget.sdcard_missing">Aucune carte SD</string>
|
||||
@ -373,9 +364,6 @@
|
||||
<string name="settings.share_greeting_default">Texte par défaut lors d\'un partage</string>
|
||||
<string name="share_default_greeting">Regardez cette musique que j\'ai partagée depuis %s</string>
|
||||
<string name="share_via">Partager des titres via</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Défaut</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Partager</string>
|
||||
<string name="select_album_all_songs">Tous les titres de %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Voir tous les titres par artiste</string>
|
||||
|
@ -111,7 +111,6 @@
|
||||
<string name="main.songs_starred">Csillaggal megjelölt</string>
|
||||
<string name="main.songs_title">Dalok</string>
|
||||
<string name="main.videos">Videók</string>
|
||||
<string name="main.welcome_text">Üdvözli az Ultrasonic! Az alkalmazás még nincs beállítva. Miután konfigurálta saját kiszolgálóját (elérhető: <b>subsonic.org</b>), húzza balról jobbra az oldalsávot, lépjen be a <b>Beállítások</b> menüpontba, és adja meg csatlakozási adatokat!</string>
|
||||
<string name="main.welcome_title">Üdvözlet!</string>
|
||||
<string name="menu.about">Névjegy</string>
|
||||
<string name="menu.common">Általános</string>
|
||||
@ -140,16 +139,11 @@
|
||||
<string name="search.search">Érintse meg a kereséshez</string>
|
||||
<string name="search.songs">Dalok</string>
|
||||
<string name="search.title">Keresés</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">A próbaidőszak lejárt!</string>
|
||||
<string name="select_album.donate_dialog_later">Később</string>
|
||||
<string name="select_album.donate_dialog_message">Korlátlan letöltéshez juthat a Subsonic támogatásával.</string>
|
||||
<string name="select_album.donate_dialog_now">Most</string>
|
||||
<string name="select_album.empty">Nem található média!</string>
|
||||
<string name="select_album.n_selected">%d dal kijelölve.</string>
|
||||
<string name="select_album.n_unselected">%d dal visszavonva.</string>
|
||||
<string name="select_album.no_network">Figyelem: Hálózat nem áll rendelkezésre!</string>
|
||||
<string name="select_album.no_sdcard">Hiba: SD kártya nem áll rendelkezésre!</string>
|
||||
<string name="select_album.not_licensed">A kiszolgálónak nincs licence! %d próba nap van hátra!</string>
|
||||
<string name="select_album.play_all">Összes lejátszása</string>
|
||||
<string name="select_artist.all_folders">Összes mappa</string>
|
||||
<string name="select_artist.folder">Mappa kiválasztása</string>
|
||||
@ -325,8 +319,7 @@
|
||||
<string name="settings.use_id3_summary">ID3 Tag módszer használata a fájlredszer alapú mód helyett.</string>
|
||||
<string name="settings.show_artist_picture">Előadó képének megjelenítése</string>
|
||||
<string name="settings.show_artist_picture_summary">Az előadó listában megjeleníti a képeket, amennyiben elérhetőek</string>
|
||||
<string name="settings.video_title">Videó</string>
|
||||
<string name="settings.video_player">Videólejátszó</string>
|
||||
<string name="main.video">Videó</string>
|
||||
<string name="settings.view_refresh">Nézet frissítési gyakorisága</string>
|
||||
<string name="settings.view_refresh_500">.5 másodperc</string>
|
||||
<string name="settings.view_refresh_1000">1 másodperc</string>
|
||||
@ -348,8 +341,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">Az MX Player nincs telepítve. Töltse le díjmentesen a Play Áruházból, vagy módosítsa a videó beállításait!</string>
|
||||
<string name="video.get_mx_player_button">MX Player letöltése</string>
|
||||
<string name="widget.initial_text">Érintse meg a zene kiválasztásához</string>
|
||||
<string name="widget.sdcard_busy">Az SD kártya nem elérhető!</string>
|
||||
<string name="widget.sdcard_missing">Nincs SD kártya!</string>
|
||||
@ -385,9 +376,6 @@
|
||||
<string name="settings.share_greeting_default">Alapértelmezett megosztási üzenet</string>
|
||||
<string name="share_default_greeting">Hallgasd meg ezt a zenét, megosztottam innen: %s</string>
|
||||
<string name="share_via">Dalok megosztása ezzel</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Alapértelmezett</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Megosztás</string>
|
||||
<string name="select_album_all_songs">%s minden dala</string>
|
||||
<string name="settings.show_all_songs_by_artist">Az előadó összes dalának megjelenítése</string>
|
||||
|
@ -125,16 +125,11 @@
|
||||
<string name="search.search">Selezione per cercare</string>
|
||||
<string name="search.songs">Canzoni</string>
|
||||
<string name="search.title">Cerca</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">Periodo di prova terminato </string>
|
||||
<string name="select_album.donate_dialog_later">Dopo</string>
|
||||
<string name="select_album.donate_dialog_message">Ottieni download illimitato con una donazione a Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Ora</string>
|
||||
<string name="select_album.empty">Nessun media trovato</string>
|
||||
<string name="select_album.n_selected">%dtracce selezionate.</string>
|
||||
<string name="select_album.n_unselected">%d tracce non selezionate.</string>
|
||||
<string name="select_album.no_network">Attenzione: nessuna rete disponibile.</string>
|
||||
<string name="select_album.no_sdcard">Errore: Nessuna memoria SD disponibile.</string>
|
||||
<string name="select_album.not_licensed">Nessuna licenza presente. %d giorni di prova rimanenti.</string>
|
||||
<string name="select_album.play_all">Riproduci tutto</string>
|
||||
<string name="select_artist.all_folders">Tutte le cartelle</string>
|
||||
<string name="select_artist.folder">Seleziona cartella</string>
|
||||
@ -299,8 +294,7 @@
|
||||
<string name="settings.use_folder_for_album_artist_summary">Presumi che la cartella superiore sia il nome dell\'artista dell\'album</string>
|
||||
<string name="settings.use_id3">Sfoglia Utilizzando Tag ID3</string>
|
||||
<string name="settings.use_id3_summary">Usa metodi tag ID3 invece dei metodi basati sul filesystem</string>
|
||||
<string name="settings.video_title">Video</string>
|
||||
<string name="settings.video_player">Riproduttore video</string>
|
||||
<string name="main.video">Video</string>
|
||||
<string name="settings.view_refresh_500">.5 secondo</string>
|
||||
<string name="settings.view_refresh_1000">1 secondo</string>
|
||||
<string name="settings.view_refresh_1500">1.5 secondi</string>
|
||||
@ -319,8 +313,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">MX Player non è installato. Scaricalo gratuitamente dal Play Store, o cambia le impostazioni video.</string>
|
||||
<string name="video.get_mx_player_button">Ottieni MX Player</string>
|
||||
<string name="widget.initial_text">Tocca per selezionare musica</string>
|
||||
<string name="widget.sdcard_busy">Scheda SD non disponibile</string>
|
||||
<string name="widget.sdcard_missing">Nessuna scheda SD</string>
|
||||
@ -336,8 +328,6 @@
|
||||
<string name="share_comment">Commenta</string>
|
||||
<string name="download_song_removed">\"%s\" è stato rimosso dalla playlist</string>
|
||||
<string name="share_via">Condividi canzoni via</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Predefinito</string>
|
||||
<plurals name="select_album_n_songs">
|
||||
<item quantity="one">1 canzone</item>
|
||||
<item quantity="other">%d canzoni</item>
|
||||
|
@ -111,7 +111,6 @@
|
||||
<string name="main.songs_starred">Favorieten</string>
|
||||
<string name="main.songs_title">Nummers</string>
|
||||
<string name="main.videos">Video\'s</string>
|
||||
<string name="main.welcome_text">Welkom bij Ultrasonic! De app is nog niet ingesteld. Nadat je je persoonlijke server hebt opgezet (beschikbaar op <b>subsonic.org</b>), kun je naar de <b>Instellingen</b> gaan en drukken op <i>Server toevoegen</i>.</string>
|
||||
<string name="main.welcome_title">Welkom!</string>
|
||||
<string name="menu.about">Over</string>
|
||||
<string name="menu.common">Algemeen</string>
|
||||
@ -140,16 +139,11 @@
|
||||
<string name="search.search">Druk om te zoeken</string>
|
||||
<string name="search.songs">Nummers</string>
|
||||
<string name="search.title">Zoeken</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">Proefperiode is afgelopen</string>
|
||||
<string name="select_album.donate_dialog_later">Later</string>
|
||||
<string name="select_album.donate_dialog_message">Verkrijg ongelimiteerde downloads door te doneren aan Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Nu</string>
|
||||
<string name="select_album.empty">Geen media gevonden</string>
|
||||
<string name="select_album.n_selected">%d nummers geselecteerd.</string>
|
||||
<string name="select_album.n_unselected">%d nummers gedeselecteerd.</string>
|
||||
<string name="select_album.no_network">Waarschuwing: geen internetverbinding.</string>
|
||||
<string name="select_album.no_sdcard">Fout: geen SD-kaart beschikbaar.</string>
|
||||
<string name="select_album.not_licensed">Geen serverlicentie; nog %d dagen resterend van de proefperiode.</string>
|
||||
<string name="select_album.play_all">Alles afspelen</string>
|
||||
<string name="select_artist.all_folders">Alle mappen</string>
|
||||
<string name="select_artist.folder">Map kiezen</string>
|
||||
@ -325,8 +319,7 @@
|
||||
<string name="settings.use_id3_summary">ID3-labels gebruiken in plaats van systeemlabels</string>
|
||||
<string name="settings.show_artist_picture">Artiestfoto tonen op artiestenlijst</string>
|
||||
<string name="settings.show_artist_picture_summary">Toont de artiestfoto op de artiestenlijst (indien beschikbaar)</string>
|
||||
<string name="settings.video_title">Video</string>
|
||||
<string name="settings.video_player">Videospeler</string>
|
||||
<string name="main.video">Video</string>
|
||||
<string name="settings.view_refresh">Verversen</string>
|
||||
<string name="settings.view_refresh_500">0,5 seconden</string>
|
||||
<string name="settings.view_refresh_1000">1 seconde</string>
|
||||
@ -348,8 +341,6 @@
|
||||
<string name="util.bytes_format.megabyte">0,00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">MX Player is niet geïnstalleerd. Installeer deze gratis via de Play Store of wijzig de video-instellingen.</string>
|
||||
<string name="video.get_mx_player_button">MX Player installeren</string>
|
||||
<string name="widget.initial_text">Druk om muziek te selecteren</string>
|
||||
<string name="widget.sdcard_busy">SD-kaart niet beschikbaar</string>
|
||||
<string name="widget.sdcard_missing">Geen SD-kaart</string>
|
||||
@ -385,9 +376,6 @@
|
||||
<string name="settings.share_greeting_default">Standaard deelbericht</string>
|
||||
<string name="share_default_greeting">Hé, luister eens naar de muziek die ik heb gedeeld via %s</string>
|
||||
<string name="share_via">Nummers delen via</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Standaard</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Delen</string>
|
||||
<string name="select_album_all_songs">Alle nummers van %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Alle nummers van artiest tonen</string>
|
||||
|
@ -100,7 +100,6 @@
|
||||
<string name="main.songs_starred">Ulubione</string>
|
||||
<string name="main.songs_title">Utwory</string>
|
||||
<string name="main.videos">Klipy wideo</string>
|
||||
<string name="main.welcome_text">Witaj w Ultrasonic! Obecnie aplikacja nie jest skonfigurowana. Jeśli masz uruchomiony własny serwer (dostępny na <b>subsonic.org</b>), proszę wybrać <i>Dodaj serwer</i> w <b>Ustawieniach</b> aby się z nim połączyć.</string>
|
||||
<string name="main.welcome_title">Witaj!</string>
|
||||
<string name="menu.about">O aplikacji</string>
|
||||
<string name="menu.common">Wspólne</string>
|
||||
@ -128,16 +127,11 @@
|
||||
<string name="search.search">Kliknij, aby wyszukać</string>
|
||||
<string name="search.songs">Utwory</string>
|
||||
<string name="search.title">Wyszukiwanie</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">Okres próbny zakończył się</string>
|
||||
<string name="select_album.donate_dialog_later">Później</string>
|
||||
<string name="select_album.donate_dialog_message">Uzyskaj możliwość nieograniczonych pobrań przekazując darowiznę na rzecz Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Teraz</string>
|
||||
<string name="select_album.empty">Brak mediów</string>
|
||||
<string name="select_album.n_selected">Zaznaczono %d utworów.</string>
|
||||
<string name="select_album.n_unselected">Odznaczono %d utworów.</string>
|
||||
<string name="select_album.no_network">Uwaga: sieć niedostępna.</string>
|
||||
<string name="select_album.no_sdcard">Błąd: Niedostępna karta SD.</string>
|
||||
<string name="select_album.not_licensed">Serwer bez licencji. Pozostało %d dni próbnych.</string>
|
||||
<string name="select_album.play_all">Odtwórz wszystkie</string>
|
||||
<string name="select_artist.all_folders">Wszystkie foldery</string>
|
||||
<string name="select_artist.folder">Wybierz folder</string>
|
||||
@ -308,8 +302,7 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
|
||||
<string name="settings.use_folder_for_album_artist_summary">Zakłada, że folder najwyższego poziomu jest nazwą artysty albumu</string>
|
||||
<string name="settings.use_id3">Przeglądaj używając tagów ID3</string>
|
||||
<string name="settings.use_id3_summary">Używa metod z tagów ID3 zamiast metod opartych na systemie plików</string>
|
||||
<string name="settings.video_title">Wideo</string>
|
||||
<string name="settings.video_player">Odtwarzacz wideo</string>
|
||||
<string name="main.video">Wideo</string>
|
||||
<string name="settings.view_refresh">Odświeżanie widoku</string>
|
||||
<string name="settings.view_refresh_500">co pół sekundy</string>
|
||||
<string name="settings.view_refresh_1000">co 1 sekundę</string>
|
||||
@ -331,8 +324,6 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">MX Player nie jest zainstalowany, Pobierz go za darmo w Sklepie Play, lub zmień ustawiena wideo.</string>
|
||||
<string name="video.get_mx_player_button">Pobierz MX Player</string>
|
||||
<string name="widget.initial_text">Dotknij, aby wybrać muzykę</string>
|
||||
<string name="widget.sdcard_busy">Karta SD jest niedostępna</string>
|
||||
<string name="widget.sdcard_missing">Brak karty SD</string>
|
||||
@ -368,9 +359,6 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
|
||||
<string name="settings.share_greeting_default">Domyślny tekst podczas udostępniania</string>
|
||||
<string name="share_default_greeting">Sprawdź muzykę, którą udostępniam na %s</string>
|
||||
<string name="share_via">Udostępnij utwory za pomocą</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Domyślny</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Udostępnianie</string>
|
||||
<string name="select_album_all_songs">Wszystkie utwory %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Wyświetlaj wszystkie utwory artysty</string>
|
||||
|
@ -101,7 +101,6 @@
|
||||
<string name="main.songs_starred">Favoritas</string>
|
||||
<string name="main.songs_title">Músicas</string>
|
||||
<string name="main.videos">Vídeos</string>
|
||||
<string name="main.welcome_text">Bem-vindo ao Ultrasonic! O aplicativo ainda não está configurado. Após configurar seu servidor pessoal (disponível em <b>subsonic.org</b>), clique em <i>Adicionar Servidor</i> em <b>Configurações</b> para a conexão.</string>
|
||||
<string name="main.welcome_title">Bem-vindo!</string>
|
||||
<string name="menu.about">Sobre</string>
|
||||
<string name="menu.common">Comum</string>
|
||||
@ -130,16 +129,11 @@
|
||||
<string name="search.search">Clique para pesquisar</string>
|
||||
<string name="search.songs">Músicas</string>
|
||||
<string name="search.title">Pesquisar</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">O período de teste acabou</string>
|
||||
<string name="select_album.donate_dialog_later">Mais tarde</string>
|
||||
<string name="select_album.donate_dialog_message">Obtenha downloads ilimitados fazendo uma doação ao Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Agora</string>
|
||||
<string name="select_album.empty">Nenhuma mídia encontrada</string>
|
||||
<string name="select_album.n_selected">%d faixas selecionadas.</string>
|
||||
<string name="select_album.n_unselected">%d faixas desselecionadas.</string>
|
||||
<string name="select_album.no_network">Aviso: Nenhuma rede disponível.</string>
|
||||
<string name="select_album.no_sdcard">Erro: Nenhum cartão SD disponível.</string>
|
||||
<string name="select_album.not_licensed">Servidor não licenciado. Restam %d dias de teste.</string>
|
||||
<string name="select_album.play_all">Tocar Tudo</string>
|
||||
<string name="select_artist.all_folders">Todas as Pastas</string>
|
||||
<string name="select_artist.folder">Selecionar Pasta</string>
|
||||
@ -313,8 +307,7 @@
|
||||
<string name="settings.use_id3_summary">Usar as etiquetas ID3 ao invés do sistema de arquivos</string>
|
||||
<string name="settings.show_artist_picture">Mostrar Foto do Artista na Lista</string>
|
||||
<string name="settings.show_artist_picture_summary">Mostrar a imagem do artista na lista de artistas, se disponível</string>
|
||||
<string name="settings.video_title">Vídeo</string>
|
||||
<string name="settings.video_player">Player de Vídeo</string>
|
||||
<string name="main.video">Vídeo</string>
|
||||
<string name="settings.view_refresh">Atualização da Tela</string>
|
||||
<string name="settings.view_refresh_500">.5 segundos</string>
|
||||
<string name="settings.view_refresh_1000">1 segundo</string>
|
||||
@ -336,8 +329,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">O MX Player não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo.</string>
|
||||
<string name="video.get_mx_player_button">Baixar MX Player</string>
|
||||
<string name="widget.initial_text">Toque para selecionar a música</string>
|
||||
<string name="widget.sdcard_busy">Cartão SD indisponível</string>
|
||||
<string name="widget.sdcard_missing">Sem cartão SD</string>
|
||||
@ -373,9 +364,6 @@
|
||||
<string name="settings.share_greeting_default">Saudação Padrão do Compartilhamento</string>
|
||||
<string name="share_default_greeting">Confira esta música que compartilhei do %s</string>
|
||||
<string name="share_via">Compartilhar músicas via</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Padrão</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Compartilhar</string>
|
||||
<string name="select_album_all_songs">Todas as Músicas de %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Mostrar Todas as Músicas por Artista</string>
|
||||
|
@ -100,7 +100,6 @@
|
||||
<string name="main.songs_starred">Favoritas</string>
|
||||
<string name="main.songs_title">Músicas</string>
|
||||
<string name="main.videos">Vídeos</string>
|
||||
<string name="main.welcome_text">Bem-vindo ao Ultrasonic! O aplicativo ainda não está configurado. Após configurar seu servidor pessoal (disponível em <b>subsonic.org</b>), clique em <i>Adicionar Servidor</i> em <b>Configurações</b> para a conexão.</string>
|
||||
<string name="main.welcome_title">Bem-vindo!</string>
|
||||
<string name="menu.about">Sobre</string>
|
||||
<string name="menu.common">Comum</string>
|
||||
@ -128,16 +127,11 @@
|
||||
<string name="search.search">Clique para pesquisar</string>
|
||||
<string name="search.songs">Músicas</string>
|
||||
<string name="search.title">Pesquisar</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">O período de teste acabou</string>
|
||||
<string name="select_album.donate_dialog_later">Mais tarde</string>
|
||||
<string name="select_album.donate_dialog_message">Obtenha downloads ilimitados fazendo uma doação ao Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Agora</string>
|
||||
<string name="select_album.empty">Nenhuma mídia encontrada</string>
|
||||
<string name="select_album.n_selected">%d faixas selecionadas.</string>
|
||||
<string name="select_album.n_unselected">%d faixas desselecionadas.</string>
|
||||
<string name="select_album.no_network">Aviso: Nenhuma rede disponível.</string>
|
||||
<string name="select_album.no_sdcard">Erro: Nenhum cartão SD disponível.</string>
|
||||
<string name="select_album.not_licensed">Servidor não licenciado. Restam %d dias de teste.</string>
|
||||
<string name="select_album.play_all">Tocar Tudo</string>
|
||||
<string name="select_artist.all_folders">Todas as Pastas</string>
|
||||
<string name="select_artist.folder">Selecionar Pasta</string>
|
||||
@ -308,8 +302,7 @@
|
||||
<string name="settings.use_folder_for_album_artist_summary">Assumir que a pasta mais acima é o nome do artista</string>
|
||||
<string name="settings.use_id3">Navegar Usando Etiquetas ID3</string>
|
||||
<string name="settings.use_id3_summary">Usa as etiquetas ID3 ao invés do sistema de ficheiros</string>
|
||||
<string name="settings.video_title">Vídeo</string>
|
||||
<string name="settings.video_player">Player de Vídeo</string>
|
||||
<string name="main.video">Vídeo</string>
|
||||
<string name="settings.view_refresh">Atualização do Ecrã</string>
|
||||
<string name="settings.view_refresh_500">.5 segundos</string>
|
||||
<string name="settings.view_refresh_1000">1 segundo</string>
|
||||
@ -331,8 +324,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">—:——</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">O MX Player não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo.</string>
|
||||
<string name="video.get_mx_player_button">Descarregar MX Player</string>
|
||||
<string name="widget.initial_text">Toque para selecionar a música</string>
|
||||
<string name="widget.sdcard_busy">Cartão SD indisponível</string>
|
||||
<string name="widget.sdcard_missing">Sem cartão SD</string>
|
||||
@ -368,9 +359,6 @@
|
||||
<string name="settings.share_greeting_default">Saudação Padrão</string>
|
||||
<string name="share_default_greeting">Confira esta música que compartilhei do %s</string>
|
||||
<string name="share_via">Compartilhar músicas via</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Padrão</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Compartilhar</string>
|
||||
<string name="select_album_all_songs">Todas as Músicas de %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Todas as Músicas do Artista</string>
|
||||
|
@ -125,16 +125,11 @@
|
||||
<string name="search.search">Нажми для поиска</string>
|
||||
<string name="search.songs">Песни</string>
|
||||
<string name="search.title">Поиск</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">Пробный период окончен</string>
|
||||
<string name="select_album.donate_dialog_later">Позже</string>
|
||||
<string name="select_album.donate_dialog_message">Получите неограниченное количество загрузок, пожертвовав Subsonic</string>
|
||||
<string name="select_album.donate_dialog_now">Сейчас</string>
|
||||
<string name="select_album.empty">Медиа не найдена</string>
|
||||
<string name="select_album.n_selected">%d треки выбраны.</string>
|
||||
<string name="select_album.n_unselected">%d треки не выбраны.</string>
|
||||
<string name="select_album.no_network">Предупреждение: сеть недоступна.</string>
|
||||
<string name="select_album.no_sdcard">Ошибка: нет SD-карты</string>
|
||||
<string name="select_album.not_licensed">Сервер не лицензирован. %d пробные дни остались.</string>
|
||||
<string name="select_album.play_all">Воспроизвести все</string>
|
||||
<string name="select_artist.all_folders">Все папки</string>
|
||||
<string name="select_artist.folder">Выбрать папку</string>
|
||||
@ -300,8 +295,7 @@
|
||||
<string name="settings.use_folder_for_album_artist_summary">Предположим, папка верхнего уровня - это имя исполнителя альбома</string>
|
||||
<string name="settings.use_id3">Обзор с использованием тегов ID3</string>
|
||||
<string name="settings.use_id3_summary">Используйте методы тегов ID3 вместо методов на основе файловой системы</string>
|
||||
<string name="settings.video_title">Видео</string>
|
||||
<string name="settings.video_player">Видеоплеер</string>
|
||||
<string name="main.video">Видео</string>
|
||||
<string name="settings.view_refresh">Посмотреть Обновить</string>
|
||||
<string name="settings.view_refresh_500">.5 секунд</string>
|
||||
<string name="settings.view_refresh_1000">1 секунда</string>
|
||||
@ -323,8 +317,6 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">MX Player не установлен. Получите его бесплатно в магазине Play Store или измените настройки видео.</string>
|
||||
<string name="video.get_mx_player_button">Получить MX Player</string>
|
||||
<string name="widget.initial_text">Нажмите, чтобы выбрать музыку</string>
|
||||
<string name="widget.sdcard_busy">SD-карта недоступна</string>
|
||||
<string name="widget.sdcard_missing">Нет SD-карты</string>
|
||||
@ -360,9 +352,6 @@
|
||||
<string name="settings.share_greeting_default">Поделиться приветствием по умолчанию</string>
|
||||
<string name="share_default_greeting">Проверьте эту музыку, с которой я поделился %s</string>
|
||||
<string name="share_via">Поделиться треками через</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">По умолчанию</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Поделиться</string>
|
||||
<string name="select_album_all_songs">Все треки %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Показать все треки исполнителя</string>
|
||||
|
@ -106,10 +106,6 @@
|
||||
<string name="search.search">点击搜索</string>
|
||||
<string name="search.songs">歌曲</string>
|
||||
<string name="search.title">搜索</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">试用已结束</string>
|
||||
<string name="select_album.donate_dialog_later">稍后</string>
|
||||
<string name="select_album.donate_dialog_message">通过捐赠 Subsonic 得到无限制的下载。</string>
|
||||
<string name="select_album.donate_dialog_now">现在</string>
|
||||
<string name="select_album.empty">找不到歌曲</string>
|
||||
<string name="select_album.no_network">警告:网络不可用</string>
|
||||
<string name="select_album.no_sdcard">错误:没有SD卡</string>
|
||||
@ -224,8 +220,7 @@
|
||||
<string name="settings.testing_unlicensed">连接正常, 服务器未授权。</string>
|
||||
<string name="settings.theme_title">主题</string>
|
||||
<string name="settings.title.allow_self_signed_certificate">允许自签名 HTTPS 证书</string>
|
||||
<string name="settings.video_title">视频</string>
|
||||
<string name="settings.video_player">视频播放器</string>
|
||||
<string name="main.video">视频</string>
|
||||
<string name="settings.view_refresh">刷新视图</string>
|
||||
<string name="settings.view_refresh_500">.5 秒</string>
|
||||
<string name="settings.view_refresh_1000">1 秒</string>
|
||||
@ -261,8 +256,6 @@
|
||||
<string name="share_comment">评论</string>
|
||||
<string name="download_song_removed">%s已从播放列表中移除</string>
|
||||
<string name="download.share_playlist">分享播放列表</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">默认</string>
|
||||
<string name="menu.share">分享</string>
|
||||
<string name="settings.playback.bluetooth_disabled">已禁用</string>
|
||||
<string name="settings.debug.log_delete">删除文件</string>
|
||||
|
@ -224,16 +224,6 @@
|
||||
<item>@string/settings.search_250</item>
|
||||
<item>@string/settings.search_500</item>
|
||||
</string-array>
|
||||
<string-array name="videoPlayerValues" translatable="false">
|
||||
<item>mx</item>
|
||||
<item>default</item>
|
||||
<item>flash</item>
|
||||
</string-array>
|
||||
<string-array name="videoPlayerNames" translatable="false">
|
||||
<item>@string/settings.video_mx_player</item>
|
||||
<item>@string/settings.video_default</item>
|
||||
<item>@string/settings.video_flash</item>
|
||||
</string-array>
|
||||
<string-array name="viewRefreshNames" translatable="false">
|
||||
<item>@string/settings.view_refresh_500</item>
|
||||
<item>@string/settings.view_refresh_1000</item>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="background_task.loading">Loading…</string>
|
||||
<string name="background_task.network_error">A network error occurred. Please check the server address or try again later.</string>
|
||||
@ -111,8 +111,9 @@
|
||||
<string name="main.songs_starred">Starred</string>
|
||||
<string name="main.songs_title">Songs</string>
|
||||
<string name="main.videos">Videos</string>
|
||||
<string name="main.welcome_text">Welcome to Ultrasonic! The app is currently not configured. After you\'ve set up your personal server (available from <b>subsonic.org</b>), please click <i>Manage Servers</i> in <b>Settings</b> to connect to it.</string>
|
||||
<string name="main.welcome_title">Welcome!</string>
|
||||
<string name="main.welcome_text_demo">To use Ultrasonic with your own music you will need your <b>own server</b>. \n\n➤ In case you want to try out the app first, it can add a demo server now. \n\n➤ Otherwise you can configure your server in the <b>settings</b>.</string>
|
||||
<string name="main.welcome_title">Welcome to Ultrasonic!</string>
|
||||
<string name="main.welcome_cancel">Take me to the settings</string>
|
||||
<string name="menu.about">About</string>
|
||||
<string name="menu.common">Common</string>
|
||||
<string name="menu.deleted_playlist">Deleted playlist %s</string>
|
||||
@ -140,16 +141,11 @@
|
||||
<string name="search.search">Click to search</string>
|
||||
<string name="search.songs">Songs</string>
|
||||
<string name="search.title">Search</string>
|
||||
<string name="select_album.donate_dialog_0_trial_days_left">Trial period is over</string>
|
||||
<string name="select_album.donate_dialog_later">Later</string>
|
||||
<string name="select_album.donate_dialog_message">Get unlimited downloads by donating to Subsonic.</string>
|
||||
<string name="select_album.donate_dialog_now">Now</string>
|
||||
<string name="select_album.empty">No media found</string>
|
||||
<string name="select_album.n_selected">%d tracks selected.</string>
|
||||
<string name="select_album.n_unselected">%d tracks unselected.</string>
|
||||
<string name="select_album.no_network">Warning: No network available.</string>
|
||||
<string name="select_album.no_sdcard">Error: No SD card available.</string>
|
||||
<string name="select_album.not_licensed">Server not licensed. %d trial days left.</string>
|
||||
<string name="select_album.play_all">Play All</string>
|
||||
<string name="select_artist.all_folders">All Folders</string>
|
||||
<string name="select_artist.folder">Select Folder</string>
|
||||
@ -327,8 +323,7 @@
|
||||
<string name="settings.use_id3_summary">Use ID3 tag methods instead of file system based methods</string>
|
||||
<string name="settings.show_artist_picture">Show artist picture in artist list</string>
|
||||
<string name="settings.show_artist_picture_summary">Displays the artist picture in the artist list if available</string>
|
||||
<string name="settings.video_title">Video</string>
|
||||
<string name="settings.video_player">Video player</string>
|
||||
<string name="main.video" tools:ignore="UnusedResources">Video</string>
|
||||
<string name="settings.view_refresh">View Refresh</string>
|
||||
<string name="settings.view_refresh_500">.5 seconds</string>
|
||||
<string name="settings.view_refresh_1000">1 second</string>
|
||||
@ -348,10 +343,8 @@
|
||||
<string name="util.bytes_format.gigabyte">0.00 GB</string>
|
||||
<string name="util.bytes_format.kilobyte">0 KB</string>
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.no_time" tools:ignore="TypographyDashes">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">MX Player is not installed. Get it for free on Play Store, or change video settings.</string>
|
||||
<string name="video.get_mx_player_button">Get MX Player</string>
|
||||
<string name="widget.initial_text">Touch to select music</string>
|
||||
<string name="widget.sdcard_busy">SD card unavailable</string>
|
||||
<string name="widget.sdcard_missing">No SD card</string>
|
||||
@ -387,9 +380,6 @@
|
||||
<string name="settings.share_greeting_default">Default Share Greeting</string>
|
||||
<string name="share_default_greeting">Check out this music I shared from %s</string>
|
||||
<string name="share_via">Share songs via</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Default</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Share</string>
|
||||
<string name="select_album_all_songs">All Songs by %s</string>
|
||||
<string name="settings.show_all_songs_by_artist">Show All Songs By Artist</string>
|
||||
@ -449,6 +439,7 @@
|
||||
<string name="server_editor.authentication">Authentication</string>
|
||||
<string name="server_editor.advanced">Advanced settings</string>
|
||||
<string name="server_editor.disabled_feature">One or more features were disabled because the server doesn\'t support them.\nYou can run this test again anytime.</string>
|
||||
<string name="server_menu.demo">Demo Server</string>
|
||||
|
||||
<plurals name="select_album_n_songs">
|
||||
<item quantity="one">1 song</item>
|
||||
|
@ -70,13 +70,13 @@
|
||||
a:key="playbackControlSettings"
|
||||
app:iconSpaceReserved="false">
|
||||
<CheckBoxPreference
|
||||
a:defaultValue="false"
|
||||
a:defaultValue="true"
|
||||
a:key="useId3Tags"
|
||||
a:summary="@string/settings.use_id3_summary"
|
||||
a:title="@string/settings.use_id3"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<CheckBoxPreference
|
||||
a:defaultValue="false"
|
||||
a:defaultValue="true"
|
||||
a:key="showArtistPicture"
|
||||
a:summary="@string/settings.show_artist_picture_summary"
|
||||
a:title="@string/settings.show_artist_picture"
|
||||
@ -180,17 +180,6 @@
|
||||
a:title="@string/settings.send_bluetooth_album_art"
|
||||
app:iconSpaceReserved="false"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
a:title="@string/settings.video_title"
|
||||
app:iconSpaceReserved="false">
|
||||
<ListPreference
|
||||
a:defaultValue="default"
|
||||
a:entries="@array/videoPlayerNames"
|
||||
a:entryValues="@array/videoPlayerValues"
|
||||
a:key="videoPlayer"
|
||||
a:title="@string/settings.video_player"
|
||||
app:iconSpaceReserved="false"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
a:title="@string/settings.sharing_title"
|
||||
app:iconSpaceReserved="false">
|
||||
|
@ -9,18 +9,20 @@ import org.amshove.kluent.`should throw`
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
import org.moire.ultrasonic.api.subsonic.response.StreamResponse
|
||||
import org.moire.ultrasonic.api.subsonic.toStreamResponse
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE)
|
||||
class AvatarRequestHandlerTest {
|
||||
private val mockApiClient: SubsonicAPIClient = mock()
|
||||
private val mockApiClient: SubsonicAPIClient = mock(defaultAnswer = Answers.RETURNS_DEEP_STUBS)
|
||||
private val handler = AvatarRequestHandler(mockApiClient)
|
||||
|
||||
@Test
|
||||
@ -59,8 +61,10 @@ class AvatarRequestHandlerTest {
|
||||
apiError = null,
|
||||
responseHttpCode = 200
|
||||
)
|
||||
whenever(mockApiClient.getAvatar(any()))
|
||||
.thenReturn(streamResponse)
|
||||
|
||||
whenever(
|
||||
mockApiClient.toStreamResponse(any())
|
||||
).thenReturn(streamResponse)
|
||||
|
||||
val response = handler.load(
|
||||
createLoadAvatarRequest("some-username").buildRequest(), 0
|
||||
|
@ -10,8 +10,8 @@ import org.amshove.kluent.`should throw`
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.anyOrNull
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||
@ -20,7 +20,7 @@ import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class CoverArtRequestHandlerTest {
|
||||
private val mockApiClient: SubsonicAPIClient = mock()
|
||||
private val mockApiClient: SubsonicAPIClient = mock(defaultAnswer = Answers.RETURNS_DEEP_STUBS)
|
||||
private val handler = CoverArtRequestHandler(mockApiClient)
|
||||
|
||||
@Test
|
||||
@ -56,7 +56,9 @@ class CoverArtRequestHandlerTest {
|
||||
fun `Should throw IOException when request to api failed`() {
|
||||
val streamResponse = StreamResponse(null, null, 500)
|
||||
|
||||
whenever(mockApiClient.getCoverArt(any(), anyOrNull())).thenReturn(streamResponse)
|
||||
whenever(
|
||||
mockApiClient.toStreamResponse(any())
|
||||
).thenReturn(streamResponse)
|
||||
|
||||
val fail = {
|
||||
handler.load(createLoadCoverArtRequest("some").buildRequest(), 0)
|
||||
@ -73,7 +75,9 @@ class CoverArtRequestHandlerTest {
|
||||
responseHttpCode = 200
|
||||
)
|
||||
|
||||
whenever(mockApiClient.getCoverArt(any(), anyOrNull())).thenReturn(streamResponse)
|
||||
whenever(
|
||||
mockApiClient.toStreamResponse(any())
|
||||
).thenReturn(streamResponse)
|
||||
|
||||
val response = handler.load(
|
||||
createLoadCoverArtRequest("some").buildRequest(), 0
|
||||
|
Loading…
x
Reference in New Issue
Block a user