From c2a83fae2d0f3e6d4b9db348dc340e8f4ec08183 Mon Sep 17 00:00:00 2001 From: Felix <mail@nicidienase.de> Date: Thu, 6 Sep 2018 19:08:08 +0200 Subject: [PATCH] remove RxJava and move Downloader from touch-project --- common/build.gradle | 3 - .../mediadata/entities/recording/Recording.kt | 6 +- .../recording/persistence/EventDao.kt | 2 +- .../persistence/PersistentRecording.kt | 36 ++--- .../recording/persistence/RecordingDao.kt | 2 +- .../common/mediadata/network/ApiFactory.kt | 37 +++++ .../mediadata/network/RecordingService.kt | 25 ++- .../mediadata/network/StreamingService.kt | 3 +- .../mediadata/sync/DownloadJobService.kt | 32 ++++ .../common/mediadata/sync/Downloader.kt | 153 ++++++++++++++++++ .../chaosflix/common/util/LiveEvent.kt | 6 + .../chaosflix/common/util/SingleLiveEvent.kt | 76 +++++++++ .../chaosflix/common/util/ThreadHandler.kt | 31 ++++ common/src/main/res/values/urlstrings.xml | 5 + 14 files changed, 376 insertions(+), 41 deletions(-) create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/ApiFactory.kt create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/DownloadJobService.kt create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/Downloader.kt create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/util/LiveEvent.kt create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/util/SingleLiveEvent.kt create mode 100644 common/src/main/java/de/nicidienase/chaosflix/common/util/ThreadHandler.kt create mode 100644 common/src/main/res/values/urlstrings.xml diff --git a/common/build.gradle b/common/build.gradle index 70ffb96f..b87fd627 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -45,12 +45,9 @@ dependencies { api "android.arch.persistence.room:rxjava2:${rootProject.ext.archCompVersion}" kapt "android.arch.persistence.room:compiler:${rootProject.ext.archCompVersion}" - api 'io.reactivex.rxjava2:rxjava:2.1.5' api 'com.squareup.retrofit2:retrofit:2.3.0' - api 'com.squareup.retrofit2:converter-gson:2.3.0' - api 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' api 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.0' diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/Recording.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/Recording.kt index 41ad1bf7..5675861d 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/Recording.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/Recording.kt @@ -27,12 +27,14 @@ data class Recording( ) { var recordingID: Long - var eventID: Long init { val strings = url.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() recordingID = (strings[strings.size - 1]).toLong() + } + + fun getEventString():String { val split = eventUrl.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - eventID = (split[split.size - 1]).toLong() + return (split[split.size - 1]) } } diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt index 4d506d54..5cb8b331 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/EventDao.kt @@ -27,7 +27,7 @@ interface EventDao: PersistentItemDao<PersistentEvent> { @Query("SELECT * FROM event WHERE conference = :id ORDER BY title ASC") fun findEventsByConference(id: Long):LiveData<List<PersistentEvent>> - @Query("SELECT * FROM event WHERE conference = :id ORDER BY title ASC") + @Query("SELECT * FROM event WHERE conferenceId = :id ORDER BY title ASC") fun findEventsByConferenceSync(id: Long):List<PersistentEvent> @Query("SELECT * FROM event INNER JOIN watchlist_item WHERE event.id = watchlist_item.event_id") diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/PersistentRecording.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/PersistentRecording.kt index 99dcfdfe..bbb99f20 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/PersistentRecording.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/PersistentRecording.kt @@ -13,7 +13,7 @@ import de.nicidienase.chaosflix.common.mediadata.entities.recording.Recording childColumns = arrayOf("eventId"))), indices = arrayOf(Index("eventId"))) data class PersistentRecording( - var eventId: Long, + var eventId: Long = 0, var size: Int = 0, var length: Int = 0, var mimeType: String = "", @@ -51,23 +51,23 @@ data class PersistentRecording( } @Ignore - constructor(rec: Recording) : this( - rec.eventID, - rec.size, - rec.length, - rec.mimeType, - rec.language, - rec.filename, - rec.state, - rec.folder, - rec.isHighQuality, - rec.width, - rec.height, - rec.updatedAt, - rec.recordingUrl, - rec.url, - rec.eventUrl, - rec.conferenceUrl) + constructor(rec: Recording, eventId: Long = 0) : this( + eventId = eventId, + size = rec.size, + length = rec.length, + mimeType = rec.mimeType, + language = rec.language, + filename = rec.filename, + state = rec.state, + folder = rec.folder, + isHighQuality = rec.isHighQuality, + width = rec.width, + height = rec.height, + updatedAt = rec.updatedAt, + recordingUrl = rec.recordingUrl, + url = rec.url, + eventUrl = rec.eventUrl, + conferenceUrl = rec.conferenceUrl) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeLong(eventId) diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/RecordingDao.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/RecordingDao.kt index 30f75983..10df1f9b 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/RecordingDao.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/entities/recording/persistence/RecordingDao.kt @@ -7,7 +7,7 @@ import android.arch.persistence.room.OnConflictStrategy import android.arch.persistence.room.Query @Dao -interface RecordingDao{ +interface RecordingDao: PersistentItemDao<RecordingDao>{ @Query("SELECT * FROM recording") fun getAllRecordings(): LiveData<List<PersistentRecording>> diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/ApiFactory.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/ApiFactory.kt new file mode 100644 index 00000000..678cd2d3 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/ApiFactory.kt @@ -0,0 +1,37 @@ +package de.nicidienase.chaosflix.common.mediadata.network + +import android.content.res.Resources +import com.google.gson.Gson +import de.nicidienase.chaosflix.R +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +class ApiFactory(val res: Resources) { + + val gsonConverterFactory by lazy { GsonConverterFactory.create(Gson()) } + + val client: OkHttpClient by lazy { + OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .build() + } + + val recordingApi: RecordingService by lazy { + Retrofit.Builder() + .baseUrl(res.getString(R.string.api_media_ccc_url)) + .client(client) + .addConverterFactory(gsonConverterFactory) + .build() + .create(RecordingService::class.java) + } + + val streamingApi: StreamingService by lazy { Retrofit.Builder() + .baseUrl(res.getString(R.string.streaming_media_ccc_url)) + .client(client) + .addConverterFactory(gsonConverterFactory) + .build() + .create(StreamingService::class.java) } +} \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/RecordingService.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/RecordingService.kt index 872defbf..3d56f517 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/RecordingService.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/RecordingService.kt @@ -4,31 +4,28 @@ import de.nicidienase.chaosflix.common.mediadata.entities.recording.Conference import de.nicidienase.chaosflix.common.mediadata.entities.recording.ConferencesWrapper import de.nicidienase.chaosflix.common.mediadata.entities.recording.Event import de.nicidienase.chaosflix.common.mediadata.entities.recording.Recording -import io.reactivex.Single +import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path -public interface RecordingService { +interface RecordingService { @GET("public/conferences") - fun getConferencesWrapper(): Single<ConferencesWrapper> - - @GET("public/events") - fun getAllEvents(): Single<List<Event>> + fun getConferencesWrapper(): Call<ConferencesWrapper> @GET("public/conferences/{id}") - fun getConference(@Path("id") id: Long): Single<Conference> - - @GET("public/conferences/{id}") - fun getConferenceString(@Path("id") id: Long): Single<String> + fun getConference(@Path("id") id: Long): Call<Conference> @GET("public/conferences/{name}") - fun getConferenceByname(@Path("name") name: String): Single<Conference> + fun getConferenceByName(@Path("name") name: String): Call<Conference> @GET("public/events/{id}") - fun getEvent(@Path("id") id: Long): Single<Event> + fun getEvent(@Path("id") id: Long): Call<Event> + + @GET("public/events/{guid}") + fun getEventByGUID(@Path("guid") guid: String): Call<Event> @GET("public/recordings/{id}") - fun getRecording(@Path("id") id: Long): Single<Recording> + fun getRecording(@Path("id") id: Long): Call<Recording> -} +} \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/StreamingService.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/StreamingService.kt index 01a841fc..a08b6268 100644 --- a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/StreamingService.kt +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/network/StreamingService.kt @@ -1,11 +1,10 @@ package de.nicidienase.chaosflix.common.mediadata.network import de.nicidienase.chaosflix.common.mediadata.entities.streaming.LiveConference -import io.reactivex.Flowable import io.reactivex.Single import retrofit2.http.GET -public interface StreamingService { +interface StreamingService { @GET("streams/v2.json") fun getStreamingConferences(): Single<List<LiveConference>> diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/DownloadJobService.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/DownloadJobService.kt new file mode 100644 index 00000000..886f1a02 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/DownloadJobService.kt @@ -0,0 +1,32 @@ +package de.nicidienase.chaosflix.touch.sync + +import android.content.Intent +import android.support.v4.app.JobIntentService +import de.nicidienase.chaosflix.common.mediadata.sync.Downloader +import de.nicidienase.chaosflix.touch.ViewModelFactory + +class DownloadJobService : JobIntentService() { + + override fun onHandleWork(intent: Intent) { + val downloader = Downloader(ViewModelFactory.recordingApi, ViewModelFactory.database) + val entity: String? = intent.getStringExtra(ENTITY_KEY) + val id: Long = intent.getLongExtra(ID_KEY, -1) + if (entity != null) { + when (entity) { +// ENTITY_KEY_EVERYTHING -> downloader.updateEverything() + ENTITY_KEY_CONFERENCES -> downloader.updateConferencesAndGroups() + ENTITY_KEY_EVENTS -> downloader.updateEventsForConference(id) + ENTITY_KEY_RECORDINGS -> downloader.updateRecordingsForEvent(id) + } + } + } + + companion object { + val ENTITY_KEY: String = "entity_key" + // val ENTITY_KEY_EVERYTHING = "everything" + val ENTITY_KEY_CONFERENCES: String = "conferences" + val ENTITY_KEY_EVENTS: String = "events" + val ENTITY_KEY_RECORDINGS: String = "recodings" + val ID_KEY: String = "id_key" + } +} \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/Downloader.kt b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/Downloader.kt new file mode 100644 index 00000000..f7095a76 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/mediadata/sync/Downloader.kt @@ -0,0 +1,153 @@ +package de.nicidienase.chaosflix.common.mediadata.sync + +import android.arch.lifecycle.LiveData +import android.database.sqlite.SQLiteConstraintException +import android.util.Log +import de.nicidienase.chaosflix.common.Util +import de.nicidienase.chaosflix.common.mediadata.entities.MediaDatabase +import de.nicidienase.chaosflix.common.mediadata.entities.recording.ConferencesWrapper +import de.nicidienase.chaosflix.common.mediadata.entities.recording.Event +import de.nicidienase.chaosflix.common.mediadata.entities.recording.Recording +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.ConferenceGroup +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.PersistentConference +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.PersistentEvent +import de.nicidienase.chaosflix.common.mediadata.entities.recording.persistence.PersistentRecording +import de.nicidienase.chaosflix.common.mediadata.network.RecordingService +import de.nicidienase.chaosflix.common.util.LiveEvent +import de.nicidienase.chaosflix.common.util.SingleLiveEvent +import de.nicidienase.chaosflix.common.util.ThreadHandler +import io.reactivex.schedulers.Schedulers + +class Downloader(val recordingApi: RecordingService, + val database: MediaDatabase) { + + private val threadHandler = ThreadHandler() + + enum class DownloaderState{ + RUNNING, DONE + } + + fun updateConferencesAndGroups(): LiveData<LiveEvent<DownloaderState, ConferencesWrapper, String>> { + val updateState = SingleLiveEvent<LiveEvent<DownloaderState, ConferencesWrapper, String>>() + threadHandler.runOnBackgroundThread { + updateState.postValue(LiveEvent(DownloaderState.RUNNING,null, null)) + val response = recordingApi.getConferencesWrapper().execute() + + if(!response.isSuccessful){ + updateState.postValue(LiveEvent(state = DownloaderState.DONE, error = response.message())) + return@runOnBackgroundThread + } + try { + response.body()?.let { conferencesWrapper -> + saveConferences(conferencesWrapper) + updateState.postValue(LiveEvent(DownloaderState.DONE,conferencesWrapper)) + } + } catch (e: Exception){ + updateState.postValue(LiveEvent(DownloaderState.DONE, error = "Error updating conferences.")) + e.printStackTrace() + } + } + return updateState + } + + private val TAG: String? = Downloader::class.simpleName + + fun updateEventsForConference(conference: PersistentConference) : LiveData<LiveEvent<DownloaderState, List<PersistentEvent>, String>> { + val updateState = SingleLiveEvent<LiveEvent<DownloaderState, List<PersistentEvent>, String>>() + updateState.postValue(LiveEvent(DownloaderState.RUNNING)) + threadHandler.runOnBackgroundThread { + val response = recordingApi.getConferenceByName(conference.acronym).execute() + if(!response.isSuccessful){ + updateState.postValue(LiveEvent(DownloaderState.DONE,error = response.message())) + return@runOnBackgroundThread + } + try { + val persistentEvents = response.body()?.events?.let { events -> + return@let saveEvents(conference, events) + } + updateState.postValue(LiveEvent(DownloaderState.DONE, data = persistentEvents)) + } catch (e: Exception){ + updateState.postValue(LiveEvent(DownloaderState.DONE, error = "Error updating Events for ${conference.acronym}")) + e.printStackTrace() + } + } + return updateState + } + + fun updateRecordingsForEvent(event: PersistentEvent) : + LiveData<LiveEvent<DownloaderState, List<PersistentRecording>, String>> { + val updateState = SingleLiveEvent<LiveEvent<DownloaderState, List<PersistentRecording>, String>>() + updateState.postValue(LiveEvent(DownloaderState.RUNNING)) + threadHandler.runOnBackgroundThread { + val response = recordingApi.getEventByGUID(event.guid).execute() + if(!response.isSuccessful){ + updateState.postValue(LiveEvent(DownloaderState.DONE, error = response.message())) + return@runOnBackgroundThread + } + try { + val recordings = response.body()?.recordings?.let { recordings -> + return@let saveRecordings(event, recordings) + } + updateState.postValue(LiveEvent(DownloaderState.DONE, data = recordings)) + } catch (e: Exception){ + updateState.postValue(LiveEvent(DownloaderState.DONE, error = "Error updating Recordings for ${event.title}")) + e.printStackTrace()} + } + return updateState + } + + fun saveConferences(conferencesWrapper: ConferencesWrapper): List<PersistentConference> { + return conferencesWrapper.conferencesMap.map { entry -> + val conferenceGroup: ConferenceGroup = getOrCreateConferenceGroup(entry.key) + val conferenceList = entry.value + .map { PersistentConference(it) } + .map { it.conferenceGroupId = conferenceGroup.id; it }.toTypedArray() + val conferenceIds = database.conferenceDao().insert(*conferenceList).toList() + return@map conferenceList.zip(conferenceIds) { conf: PersistentConference, id: Long -> + conf.id = id + return@zip conf + } + }.flatten() + } + + private fun getOrCreateConferenceGroup(name: String): ConferenceGroup{ + val conferenceGroup: ConferenceGroup? + = database.conferenceGroupDao().getConferenceGroupByName(name) + if (conferenceGroup != null) { + return conferenceGroup + } + val group = ConferenceGroup(name) + val index = Util.orderedConferencesList.indexOf(group.name) + if (index != -1) + group.index = index + else if (group.name == "other conferences") + group.index = 1_000_001 + group.id = database.conferenceGroupDao().insert(group) + return group + } + + private fun saveEvents(persistentConference: PersistentConference, events: List<Event>): List<PersistentEvent> { + val persistantEvents = events.map { PersistentEvent(it,persistentConference.id) } + val insertEventIds = database.eventDao().insert(*(persistantEvents.toTypedArray())).asList() + val oldEvents = database.eventDao() + .findEventsByConferenceSync(persistentConference.id) + .filter { !insertEventIds.contains(it.id) } + .toTypedArray() + try { + database.eventDao().delete(*oldEvents) + } catch (ex: SQLiteConstraintException){ + Log.d(TAG,"SQLiteException",ex) + } + persistantEvents.zip(insertEventIds) {event: PersistentEvent, id: Long -> + event.id = id + } + return persistantEvents + } + + private fun saveRecordings(event: PersistentEvent,recordings: List<Recording>): List<PersistentRecording> { + val persistentRecordings = recordings.map { PersistentRecording(it, event.id) } + database.recordingDao().insert(*persistentRecordings.toTypedArray()) + return persistentRecordings + } + +} \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/util/LiveEvent.kt b/common/src/main/java/de/nicidienase/chaosflix/common/util/LiveEvent.kt new file mode 100644 index 00000000..9ab23fa1 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/util/LiveEvent.kt @@ -0,0 +1,6 @@ +package de.nicidienase.chaosflix.common.util + +class LiveEvent<T,U,V>( + val state: T, + val data: U? = null, + val error: V? = null) \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/util/SingleLiveEvent.kt b/common/src/main/java/de/nicidienase/chaosflix/common/util/SingleLiveEvent.kt new file mode 100644 index 00000000..6a6fed8b --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/util/SingleLiveEvent.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.nicidienase.chaosflix.common.util + +import android.arch.lifecycle.LifecycleOwner +import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.Observer +import android.support.annotation.MainThread +import android.util.Log + +import java.util.concurrent.atomic.AtomicBoolean + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and Snackbar messages. + * + * + * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + * + * + * Note that only one observer is going to be notified of changes. + */ +class SingleLiveEvent<T> : MutableLiveData<T>() { + + private val mPending = AtomicBoolean(false) + + @MainThread + override fun observe(owner: LifecycleOwner, observer: Observer<T>) { + + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") + } + + // Observe the internal MutableLiveData + super.observe(owner, Observer { t -> + if (mPending.compareAndSet(true, false)) { + observer.onChanged(t) + } + }) + } + + @MainThread + override fun setValue(t: T?) { + mPending.set(true) + super.setValue(t) + } + + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + fun call() { + value = null + } + + companion object { + + private val TAG = "SingleLiveEvent" + } +} \ No newline at end of file diff --git a/common/src/main/java/de/nicidienase/chaosflix/common/util/ThreadHandler.kt b/common/src/main/java/de/nicidienase/chaosflix/common/util/ThreadHandler.kt new file mode 100644 index 00000000..c1354fd6 --- /dev/null +++ b/common/src/main/java/de/nicidienase/chaosflix/common/util/ThreadHandler.kt @@ -0,0 +1,31 @@ +package de.nicidienase.chaosflix.common.util + +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.os.Process + +class ThreadHandler { + + private val uiThreadHandler: Handler + private val backgroundThreadHandler: Handler + + init { + val handlerTread = HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND) + handlerTread.start() + backgroundThreadHandler = android.os.Handler(handlerTread.looper) + uiThreadHandler = Handler(Looper.getMainLooper()) + } + + fun runOnBackgroundThread(runnable: ()->Unit){ + backgroundThreadHandler.post(runnable) + } + + fun runOnMainThread(runnable: ()->Unit){ + uiThreadHandler.post(runnable) + } + + companion object { + private val TAG = ThreadHandler::class.java.simpleName + } +} \ No newline at end of file diff --git a/common/src/main/res/values/urlstrings.xml b/common/src/main/res/values/urlstrings.xml new file mode 100644 index 00000000..64664bae --- /dev/null +++ b/common/src/main/res/values/urlstrings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="recording_url">https://api.media.ccc.de</string> + <string name="streaming_url">https://streaming.media.ccc.de</string> +</resources> \ No newline at end of file