From c5d12c17420a6ab3cbe956dd02cd79dcbd32a99c Mon Sep 17 00:00:00 2001 From: yattoz <12408848+yattoz@users.noreply.github.com> Date: Sun, 12 Jan 2020 00:29:20 +0100 Subject: [PATCH] adding up next --- .../fr/forum_thalie/tsumugi/RadioService.kt | 1 + .../tsumugi/playerstore/PlayerStore.kt | 101 ++++++++++++++++-- .../ui/nowplaying/NowPlayingFragment.kt | 8 +- .../main/res/layout/fragment_nowplaying.xml | 7 +- app/src/main/res/values-fr/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 6 files changed, 106 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt b/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt index 97c09c7..a046240 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt @@ -149,6 +149,7 @@ class RadioService : MediaBrowserServiceCompat() { && it != noConnectionValue) { PlayerStore.instance.updateLp() + PlayerStore.instance.updateQueue() } nowPlayingNotification.update(this) Planning.instance.checkProgramme() 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 a2e4b5d..7d4dc51 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 @@ -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. val resMain = res.getJSONObject("tracks").getJSONObject("current") + val s = extractSong(resMain) if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty() || currentSong.title.value == noConnectionValue) - currentSong.setTitleArtist(resMain.getString("name")) + currentSong.setTitleArtist("${s.artist.value} - ${s.title.value}") - val starts = getTimestamp(resMain.getString("starts")) - val ends = getTimestamp(resMain.getString("ends")) + val starts = s.startTime.value + val ends = s.stopTime.value if (currentSong.startTime.value != starts) currentSong.startTime.value = starts @@ -96,7 +97,7 @@ class PlayerStore { 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") + Log.d(tag, "latency compensator set to ${(latencyCompensator).toFloat()/1000} s") } currentTime.value = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (latencyCompensator) @@ -149,10 +150,10 @@ class PlayerStore { 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") + song.setTitleArtist(songJSON.getString("name")) + song.startTime.value = getTimestamp(songJSON.getString("starts")) + song.stopTime.value = getTimestamp(songJSON.getString("ends")) + song.type.value = 0 // only used for R/a/dio return song } @@ -161,6 +162,20 @@ class PlayerStore { // ############## 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() { // note : lp is empty at initialization. This check was needed when we used the R/a/dio API. //if (lp.isNotEmpty()){ @@ -170,11 +185,79 @@ class PlayerStore { lp.add(0, n) currentSongBackup.copy(currentSong) 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 ################# // ################################################## diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt index 73294b2..336158d 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt @@ -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. 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) @@ -69,7 +69,7 @@ class NowPlayingFragment : Fragment() { 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... // so I observe a dedicated Mutable that gets set when the queue is updated. PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer { @@ -77,7 +77,7 @@ class NowPlayingFragment : Fragment() { songTitleNextText.text = t.title.value songArtistNextText.text = t.artist.value }) - + /* PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer { streamerNameText.text = it }) @@ -92,9 +92,11 @@ class NowPlayingFragment : Fragment() { songTitleText.text = it }) + /* Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer { songTitleNextText.text = it }) + */ PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer { songArtistText.text = it diff --git a/app/src/main/res/layout/fragment_nowplaying.xml b/app/src/main/res/layout/fragment_nowplaying.xml index f876dd6..7bfcef3 100644 --- a/app/src/main/res/layout/fragment_nowplaying.xml +++ b/app/src/main/res/layout/fragment_nowplaying.xml @@ -74,7 +74,7 @@ app:layout_constraintVertical_weight="3" app:layout_constraintStart_toStartOf="@id/streamerPicture" app:layout_constraintTop_toBottomOf="@id/streamerPicture" - android:visibility="gone"/> + android:visibility="visible"/> + android:visibility="visible"/> + app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" + android:visibility="visible" /> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 59e9378..052cbe3 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -10,7 +10,8 @@ Volume : - Émission en cours : + Prochain titre : + Émission en cours : En cours Erreur du chargement de WebView. Téléchargez Google Chrome sur le Play Store, ou activez le si vous l\'avez désactivé. Paramètres diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc293e3..bf5ff38 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,7 +25,8 @@ Volume: - Émission en cours : + Up next : + Current programme : Now streaming Error loading WebView. Try downloading Google Chrome on Google Play, or enabling it if you disabled it. Settings