adding up next
parent
912857c9cd
commit
c5d12c1742
|
|
@ -149,6 +149,7 @@ class RadioService : MediaBrowserServiceCompat() {
|
||||||
&& it != noConnectionValue)
|
&& it != noConnectionValue)
|
||||||
{
|
{
|
||||||
PlayerStore.instance.updateLp()
|
PlayerStore.instance.updateLp()
|
||||||
|
PlayerStore.instance.updateQueue()
|
||||||
}
|
}
|
||||||
nowPlayingNotification.update(this)
|
nowPlayingNotification.update(this)
|
||||||
Planning.instance.checkProgramme()
|
Planning.instance.checkProgramme()
|
||||||
|
|
|
||||||
|
|
@ -77,12 +77,13 @@ class PlayerStore {
|
||||||
// If we're not in PLAYING state, update title / artist metadata. If we're playing, the ICY will take care of that.
|
// 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")
|
val resMain = res.getJSONObject("tracks").getJSONObject("current")
|
||||||
|
val s = extractSong(resMain)
|
||||||
if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty()
|
if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty()
|
||||||
|| currentSong.title.value == noConnectionValue)
|
|| currentSong.title.value == noConnectionValue)
|
||||||
currentSong.setTitleArtist(resMain.getString("name"))
|
currentSong.setTitleArtist("${s.artist.value} - ${s.title.value}")
|
||||||
|
|
||||||
val starts = getTimestamp(resMain.getString("starts"))
|
val starts = s.startTime.value
|
||||||
val ends = getTimestamp(resMain.getString("ends"))
|
val ends = s.stopTime.value
|
||||||
|
|
||||||
if (currentSong.startTime.value != starts)
|
if (currentSong.startTime.value != starts)
|
||||||
currentSong.startTime.value = starts
|
currentSong.startTime.value = starts
|
||||||
|
|
@ -96,7 +97,7 @@ class PlayerStore {
|
||||||
if(isCompensatingLatency)
|
if(isCompensatingLatency)
|
||||||
{
|
{
|
||||||
latencyCompensator = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (currentSong.startTime.value ?: getTimestamp(res.getJSONObject("station").getString("schedulerTime")))
|
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")
|
Log.d(tag, "latency compensator set to ${(latencyCompensator).toFloat()/1000} s")
|
||||||
}
|
}
|
||||||
currentTime.value = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (latencyCompensator)
|
currentTime.value = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (latencyCompensator)
|
||||||
|
|
||||||
|
|
@ -149,10 +150,10 @@ class PlayerStore {
|
||||||
|
|
||||||
private fun extractSong(songJSON: JSONObject) : Song {
|
private fun extractSong(songJSON: JSONObject) : Song {
|
||||||
val song = Song()
|
val song = Song()
|
||||||
song.setTitleArtist(songJSON.getString("meta"))
|
song.setTitleArtist(songJSON.getString("name"))
|
||||||
song.startTime.value = songJSON.getLong("timestamp")
|
song.startTime.value = getTimestamp(songJSON.getString("starts"))
|
||||||
song.stopTime.value = song.startTime.value
|
song.stopTime.value = getTimestamp(songJSON.getString("ends"))
|
||||||
song.type.value = songJSON.getInt("type")
|
song.type.value = 0 // only used for R/a/dio
|
||||||
return song
|
return song
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,6 +162,20 @@ class PlayerStore {
|
||||||
// ############## QUEUE / LP FUNCTIONS ##############
|
// ############## QUEUE / LP FUNCTIONS ##############
|
||||||
// ##################################################
|
// ##################################################
|
||||||
|
|
||||||
|
fun updateQueue() {
|
||||||
|
if (queue.isNotEmpty()) {
|
||||||
|
queue.remove(queue.first())
|
||||||
|
Log.d(tag, queue.toString())
|
||||||
|
fetchLastRequest()
|
||||||
|
isQueueUpdated.value = true
|
||||||
|
} else if (isInitialized) {
|
||||||
|
fetchLastRequest()
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "queue is empty! fetching anyway !!")
|
||||||
|
fetchLastRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateLp() {
|
fun updateLp() {
|
||||||
// note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
|
// note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
|
||||||
//if (lp.isNotEmpty()){
|
//if (lp.isNotEmpty()){
|
||||||
|
|
@ -170,11 +185,79 @@ class PlayerStore {
|
||||||
lp.add(0, n)
|
lp.add(0, n)
|
||||||
currentSongBackup.copy(currentSong)
|
currentSongBackup.copy(currentSong)
|
||||||
isLpUpdated.value = true
|
isLpUpdated.value = true
|
||||||
Log.d(tag, playerStoreTag + lp.toString())
|
Log.d(tag, lp.toString())
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun fetchLastRequest()
|
||||||
|
{
|
||||||
|
val sleepScrape: (Any?) -> String = {
|
||||||
|
/* we can maximize our chances to retrieve the last queued song by specifically waiting for the number of seconds we measure between ICY metadata and API change.
|
||||||
|
we add 2 seconds just to get a higher probability that the API has correctly updated. (the latency compensator can have a jitter of 1 second usually)
|
||||||
|
If, against all odds, the API hasn't updated yet, we will retry in the same amount of seconds. So we'll have the data anyway.
|
||||||
|
This way to fetch at the most probable time is a good compromise between fetch speed and fetch frequency
|
||||||
|
We don't fetch too often, and we start to fetch at the most *probable* time.
|
||||||
|
If there's no latencyCompensator measured yet, we only wait for 3 seconds.
|
||||||
|
If the song is the same, it will be called again. 3 seconds is a good compromise between speed and frequency:
|
||||||
|
it might be called twice, rarely 3 times, and it's only the 2 first songs ; after these, the latencyCompensator is set to fetch at the most probable time.
|
||||||
|
*/
|
||||||
|
val sleepTime: Long = if (latencyCompensator > 0) latencyCompensator + 2000 else 3000
|
||||||
|
Thread.sleep(sleepTime) // we wait a bit (10s) for the API to get updated on R/a/dio side!
|
||||||
|
URL(urlToScrape).readText()
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var post: (parameter: Any?) -> Unit
|
||||||
|
|
||||||
|
fun postFun(result: JSONObject)
|
||||||
|
{
|
||||||
|
if (result.has("tracks")) {
|
||||||
|
val resMain = result.getJSONObject("tracks")
|
||||||
|
/*
|
||||||
|
if ((resMain.has("isafkstream") && !resMain.getBoolean("isafkstream")) &&
|
||||||
|
queue.isNotEmpty())
|
||||||
|
{
|
||||||
|
queue.clear() //we're not requesting anything anymore.
|
||||||
|
isQueueUpdated.value = true
|
||||||
|
} else if (resMain.has("isafkstream") && resMain.getBoolean("isafkstream") &&
|
||||||
|
queue.isEmpty())
|
||||||
|
{
|
||||||
|
initApi()
|
||||||
|
} else
|
||||||
|
*/
|
||||||
|
if (resMain.has("next") /*&& queue.isNotEmpty()*/) {
|
||||||
|
val queueJSON =
|
||||||
|
resMain.getJSONObject("next")
|
||||||
|
val t = extractSong(queueJSON)
|
||||||
|
if (queue.isNotEmpty() && t == queue.last())
|
||||||
|
{
|
||||||
|
Log.d(tag, playerStoreTag + "Song already in there: $t")
|
||||||
|
Async(sleepScrape, post)
|
||||||
|
} else {
|
||||||
|
queue.add(queue.size, t)
|
||||||
|
Log.d(tag, playerStoreTag + "added last queue song: $t")
|
||||||
|
isQueueUpdated.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post = {
|
||||||
|
val result = JSONObject(it as String)
|
||||||
|
/* The goal is to pass the result to a function that will process it (postFun).
|
||||||
|
The magic trick is, under circumstances, the last queue song might not have been updated yet when we fetch it.
|
||||||
|
So if this is detected ==> if (t == queue.last() )
|
||||||
|
Then the function re-schedule an Async(sleepScrape, post).
|
||||||
|
To do that, the "post" must be defined BEFORE the function, but the function must be defined BEFORE the "post" value.
|
||||||
|
So I declare "post" as lateinit var, define the function, then define the "post" that calls the function. IT SHOULD WORK.
|
||||||
|
*/
|
||||||
|
postFun(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
Async(sleepScrape, post)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ##################################################
|
// ##################################################
|
||||||
// ############## PICTURE FUNCTIONS #################
|
// ############## PICTURE FUNCTIONS #################
|
||||||
// ##################################################
|
// ##################################################
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class NowPlayingFragment : Fragment() {
|
||||||
|
|
||||||
// Note: these values are not used in the generic app, but if you want to, you can use them.
|
// Note: these values are not used in the generic app, but if you want to, you can use them.
|
||||||
val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next)
|
val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next)
|
||||||
//val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next)
|
val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
val streamerNameText : TextView = root.findViewById(R.id.streamerName)
|
val streamerNameText : TextView = root.findViewById(R.id.streamerName)
|
||||||
|
|
@ -69,7 +69,7 @@ class NowPlayingFragment : Fragment() {
|
||||||
listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP)
|
listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
// trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change...
|
// trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change...
|
||||||
// so I observe a dedicated Mutable that gets set when the queue is updated.
|
// so I observe a dedicated Mutable that gets set when the queue is updated.
|
||||||
PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer {
|
PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer {
|
||||||
|
|
@ -77,7 +77,7 @@ class NowPlayingFragment : Fragment() {
|
||||||
songTitleNextText.text = t.title.value
|
songTitleNextText.text = t.title.value
|
||||||
songArtistNextText.text = t.artist.value
|
songArtistNextText.text = t.artist.value
|
||||||
})
|
})
|
||||||
|
/*
|
||||||
PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
|
PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
|
||||||
streamerNameText.text = it
|
streamerNameText.text = it
|
||||||
})
|
})
|
||||||
|
|
@ -92,9 +92,11 @@ class NowPlayingFragment : Fragment() {
|
||||||
songTitleText.text = it
|
songTitleText.text = it
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer {
|
Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer {
|
||||||
songTitleNextText.text = it
|
songTitleNextText.text = it
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer {
|
PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer {
|
||||||
songArtistText.text = it
|
songArtistText.text = it
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
app:layout_constraintVertical_weight="3"
|
app:layout_constraintVertical_weight="3"
|
||||||
app:layout_constraintStart_toStartOf="@id/streamerPicture"
|
app:layout_constraintStart_toStartOf="@id/streamerPicture"
|
||||||
app:layout_constraintTop_toBottomOf="@id/streamerPicture"
|
app:layout_constraintTop_toBottomOf="@id/streamerPicture"
|
||||||
android:visibility="gone"/>
|
android:visibility="visible"/>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/listenersCount"
|
android:id="@+id/listenersCount"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
@ -202,7 +202,7 @@
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/upNext"
|
app:layout_constraintTop_toBottomOf="@id/upNext"
|
||||||
android:visibility="gone"/>
|
android:visibility="visible"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_song_title_next"
|
android:id="@+id/text_song_title_next"
|
||||||
|
|
@ -214,7 +214,8 @@
|
||||||
android:textColor="@color/whited"
|
android:textColor="@color/whited"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" />
|
app:layout_constraintTop_toBottomOf="@id/text_song_artist_next"
|
||||||
|
android:visibility="visible" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
|
|
||||||
<string name="volume">Volume : </string>
|
<string name="volume">Volume : </string>
|
||||||
|
|
||||||
<string name="up_next">Émission en cours :</string>
|
<string name="up_next">Prochain titre :</string>
|
||||||
|
<string name="current_programme">Émission en cours :</string>
|
||||||
<string name="now_streaming">En cours</string>
|
<string name="now_streaming">En cours</string>
|
||||||
<string name="error_webView">Erreur du chargement de WebView. Téléchargez Google Chrome sur le Play Store, ou activez le si vous l\'avez désactivé.</string>
|
<string name="error_webView">Erreur du chargement de WebView. Téléchargez Google Chrome sur le Play Store, ou activez le si vous l\'avez désactivé.</string>
|
||||||
<string name="action_settings">Paramètres</string>
|
<string name="action_settings">Paramètres</string>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@
|
||||||
|
|
||||||
<string name="volume">Volume: </string>
|
<string name="volume">Volume: </string>
|
||||||
|
|
||||||
<string name="up_next">Émission en cours :</string>
|
<string name="up_next">Up next :</string>
|
||||||
|
<string name="current_programme">Current programme :</string>
|
||||||
<string name="now_streaming">Now streaming</string>
|
<string name="now_streaming">Now streaming</string>
|
||||||
<string name="error_webView">Error loading WebView. Try downloading Google Chrome on Google Play, or enabling it if you disabled it.</string>
|
<string name="error_webView">Error loading WebView. Try downloading Google Chrome on Google Play, or enabling it if you disabled it.</string>
|
||||||
<string name="action_settings">Settings</string>
|
<string name="action_settings">Settings</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue