adding up next

remotes/origin/devel
yattoz 2020-01-12 00:29:20 +01:00
parent 912857c9cd
commit c5d12c1742
6 changed files with 106 additions and 17 deletions

View File

@ -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()

View File

@ -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 #################
// ################################################## // ##################################################

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>