diff --git a/app/build.gradle b/app/build.gradle
index 9a1cae0..3ac518f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -29,8 +29,8 @@ android {
applicationId "fr.forum_thalie.tsumugi"
minSdkVersion 16
targetSdkVersion 29
- versionCode 100
- versionName "1.0.0"
+ versionCode 110
+ versionName "1.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 12e9c38..7e20574 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,9 +31,6 @@
-
{
// We're checking if a new song arrives. If so, we put the currentSong in Lp and update the backup.
+
+ if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING)
+ {
+ Log.d(tag, radioTag + "SONG CHANGED AND PLAYING")
+ // we activate latency compensation only if it's been at least 2 songs...
+ when {
+ PlayerStore.instance.isStreamDown -> {
+ // if we reach here, it means that the observer has been called by a new song and that the stream was down previously.
+ // so the stream is now back to normal.
+ PlayerStore.instance.isStreamDown = false
+ PlayerStore.instance.initApi()
+ }
+ PlayerStore.instance.currentSong.title.value == noConnectionValue -> {
+ PlayerStore.instance.isStreamDown = true
+ }
+ else -> {
+ PlayerStore.instance.fetchApi(numberOfSongs >= 2)
+ }
+ }
+ }
+
if (PlayerStore.instance.currentSong != PlayerStore.instance.currentSongBackup
&& it != noConnectionValue)
{
@@ -163,6 +184,12 @@ class RadioService : MediaBrowserServiceCompat() {
preferenceStore = PreferenceManager.getDefaultSharedPreferences(this)
+ // start ticker for when the player is stopped
+ val periodString = PreferenceManager.getDefaultSharedPreferences(this).getString("fetchPeriod", "10") ?: "10"
+ val period: Long = Integer.parseInt(periodString).toLong()
+ if (period > 0)
+ apiTicker.schedule(ApiFetchTick(), 0, period * 1000)
+
// Define managers
telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt b/app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt
index 512c132..ec114b6 100644
--- a/app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt
+++ b/app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt
@@ -1,8 +1,18 @@
package fr.forum_thalie.tsumugi
+import android.support.v4.media.session.PlaybackStateCompat
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import java.util.*
+class ApiFetchTick : TimerTask() {
+ override fun run() {
+ if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_STOPPED)
+ {
+ PlayerStore.instance.fetchApi()
+ }
+ }
+}
+
class Tick : TimerTask() {
override fun run() {
PlayerStore.instance.currentTime.postValue(PlayerStore.instance.currentTime.value!! + 500)
diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt b/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt
index 41037c6..a2e4b5d 100644
--- a/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt
+++ b/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt
@@ -7,9 +7,16 @@ import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import androidx.lifecycle.MutableLiveData
import fr.forum_thalie.tsumugi.*
+import org.json.JSONObject
+import java.net.URL
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.collections.ArrayList
class PlayerStore {
+ private lateinit var urlToScrape: String
val isPlaying: MutableLiveData = MutableLiveData()
val isServiceStarted: MutableLiveData = MutableLiveData()
val volume: MutableLiveData = MutableLiveData()
@@ -27,6 +34,7 @@ class PlayerStore {
val listenersCount: MutableLiveData = MutableLiveData()
var latencyCompensator : Long = 0
var isInitialized: Boolean = false
+ var isStreamDown: Boolean = false
init {
playbackState.value = PlaybackStateCompat.STATE_STOPPED
@@ -43,6 +51,112 @@ class PlayerStore {
listenersCount.value = 0
}
+ fun initUrl(c: Context)
+ {
+ urlToScrape = c.getString(R.string.API_URL)
+ }
+
+ private fun getTimestamp(s: String) : Long
+ {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault())
+ try {
+ val t: Date? = dateFormat.parse(s)
+ return t!!.time
+ } catch (e: ParseException) {
+ e.printStackTrace()
+ }
+ return 0
+ }
+
+
+ // ##################################################
+ // ################# API FUNCTIONS ##################
+ // ##################################################
+
+ private fun updateApi(res: JSONObject, isCompensatingLatency : Boolean = false) {
+ // If we're not in PLAYING state, update title / artist metadata. If we're playing, the ICY will take care of that.
+
+ val resMain = res.getJSONObject("tracks").getJSONObject("current")
+ if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty()
+ || currentSong.title.value == noConnectionValue)
+ currentSong.setTitleArtist(resMain.getString("name"))
+
+ val starts = getTimestamp(resMain.getString("starts"))
+ val ends = getTimestamp(resMain.getString("ends"))
+
+ if (currentSong.startTime.value != starts)
+ currentSong.startTime.value = starts
+
+ currentSong.stopTime.value = ends
+
+ // I noticed that the server has a big (3 to 9 seconds !!) offset for current time.
+ // we can measure it when the player is playing, to compensate it and have our progress bar perfectly timed
+ // latencyCompensator is set to null when beginPlaying() (we can't measure it at the moment we start playing, since we're in the middle of a song),
+ // at this moment, we set it to 0. Then, next time the updateApi is called when we're playing, we measure the latency and we set out latencyComparator.
+ if(isCompensatingLatency)
+ {
+ latencyCompensator = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (currentSong.startTime.value ?: getTimestamp(res.getJSONObject("station").getString("schedulerTime")))
+ Log.d(tag, playerStoreTag + "latency compensator set to ${(latencyCompensator).toFloat()/1000} s")
+ }
+ currentTime.value = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (latencyCompensator)
+
+ /*
+ val listeners = resMain.getInt("listeners")
+ listenersCount.value = listeners
+ Log.d(tag, playerStoreTag + "store updated")
+ */
+ }
+
+ private val scrape : (Any?) -> String =
+ {
+ URL(urlToScrape).readText()
+ }
+
+ /* initApi is called :
+ - at startup
+ - when a streamer changes.
+ the idea is to fetch the queue when a streamer changes (potentially Hanyuu), and at startup.
+ The Last Played is only fetched if it's empty (so, only at startup), not when a streamer changes.
+ */
+ fun initApi()
+ {
+ val post : (parameter: Any?) -> Unit = {
+ val result = JSONObject(it as String)
+ if (result.has("tracks"))
+ {
+ updateApi(result)
+ currentSongBackup.copy(currentSong)
+
+ isQueueUpdated.value = true
+
+ isLpUpdated.value = true
+ }
+ isInitialized = true
+ }
+ Async(scrape, post)
+ }
+
+ fun fetchApi(isCompensatingLatency: Boolean = false) {
+ val post: (parameter: Any?) -> Unit = {
+ val result = JSONObject(it as String)
+ if (!result.isNull("tracks"))
+ {
+ updateApi(result, isCompensatingLatency)
+ }
+ }
+ Async(scrape, post)
+ }
+
+ private fun extractSong(songJSON: JSONObject) : Song {
+ val song = Song()
+ song.setTitleArtist(songJSON.getString("meta"))
+ song.startTime.value = songJSON.getLong("timestamp")
+ song.stopTime.value = song.startTime.value
+ song.type.value = songJSON.getInt("type")
+ return song
+ }
+
+
// ##################################################
// ############## QUEUE / LP FUNCTIONS ##############
// ##################################################
diff --git a/app/src/main/res/layout/fragment_nowplaying.xml b/app/src/main/res/layout/fragment_nowplaying.xml
index 5fa21cd..f876dd6 100644
--- a/app/src/main/res/layout/fragment_nowplaying.xml
+++ b/app/src/main/res/layout/fragment_nowplaying.xml
@@ -305,7 +305,7 @@
android:progressDrawable="@drawable/progress_bar_progress"
app:layout_constraintBottom_toTopOf="@id/play_pause"
tools:layout_editor_absoluteX="0dp"
- android:visibility="gone"/>
+ android:visibility="visible"/>
+ android:visibility="visible"/>
+ android:visibility="visible"/>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4f9137f..a132784 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,6 +7,7 @@
https://github.com/yattoz/Tsumugi-app/issues/
https://tsumugi.forum-thalie.fr/
https://tsumugi.forum-thalie.fr/?feed=rss2
+ https://radio.mahoro-net.org/airtime/api/live-info-v2
ADD SOME URL HERE