Browse Source

adding up next

yattoz 4 years ago
parent
commit
c5d12c1742

+ 1 - 0
app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt View File

@@ -149,6 +149,7 @@ class RadioService : MediaBrowserServiceCompat() {
149 149
             && it != noConnectionValue)
150 150
         {
151 151
             PlayerStore.instance.updateLp()
152
+            PlayerStore.instance.updateQueue()
152 153
         }
153 154
         nowPlayingNotification.update(this)
154 155
         Planning.instance.checkProgramme()

+ 92 - 9
app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt View File

@@ -77,12 +77,13 @@ class PlayerStore {
77 77
         // If we're not in PLAYING state, update title / artist metadata. If we're playing, the ICY will take care of that.
78 78
 
79 79
         val resMain = res.getJSONObject("tracks").getJSONObject("current")
80
+        val s = extractSong(resMain)
80 81
         if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty()
81 82
             || currentSong.title.value == noConnectionValue)
82
-            currentSong.setTitleArtist(resMain.getString("name"))
83
+            currentSong.setTitleArtist("${s.artist.value} - ${s.title.value}")
83 84
 
84
-        val starts = getTimestamp(resMain.getString("starts"))
85
-        val ends = getTimestamp(resMain.getString("ends"))
85
+        val starts = s.startTime.value
86
+        val ends = s.stopTime.value
86 87
 
87 88
         if (currentSong.startTime.value != starts)
88 89
             currentSong.startTime.value = starts
@@ -96,7 +97,7 @@ class PlayerStore {
96 97
         if(isCompensatingLatency)
97 98
         {
98 99
             latencyCompensator = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (currentSong.startTime.value ?: getTimestamp(res.getJSONObject("station").getString("schedulerTime")))
99
-            Log.d(tag, playerStoreTag +  "latency compensator set to ${(latencyCompensator).toFloat()/1000} s")
100
+            Log.d(tag, "latency compensator set to ${(latencyCompensator).toFloat()/1000} s")
100 101
         }
101 102
         currentTime.value = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (latencyCompensator)
102 103
 
@@ -149,10 +150,10 @@ class PlayerStore {
149 150
 
150 151
     private fun extractSong(songJSON: JSONObject) : Song {
151 152
         val song = Song()
152
-        song.setTitleArtist(songJSON.getString("meta"))
153
-        song.startTime.value = songJSON.getLong("timestamp")
154
-        song.stopTime.value = song.startTime.value
155
-        song.type.value = songJSON.getInt("type")
153
+        song.setTitleArtist(songJSON.getString("name"))
154
+        song.startTime.value = getTimestamp(songJSON.getString("starts"))
155
+        song.stopTime.value = getTimestamp(songJSON.getString("ends"))
156
+        song.type.value = 0 // only used for R/a/dio
156 157
         return song
157 158
     }
158 159
 
@@ -161,6 +162,20 @@ class PlayerStore {
161 162
     // ############## QUEUE / LP FUNCTIONS ##############
162 163
     // ##################################################
163 164
 
165
+    fun updateQueue() {
166
+        if (queue.isNotEmpty()) {
167
+            queue.remove(queue.first())
168
+            Log.d(tag, queue.toString())
169
+            fetchLastRequest()
170
+            isQueueUpdated.value = true
171
+        } else if (isInitialized) {
172
+            fetchLastRequest()
173
+        } else {
174
+            Log.d(tag,  "queue is empty! fetching anyway !!")
175
+            fetchLastRequest()
176
+        }
177
+    }
178
+
164 179
     fun updateLp() {
165 180
         // note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
166 181
         //if (lp.isNotEmpty()){
@@ -170,11 +185,79 @@ class PlayerStore {
170 185
                 lp.add(0, n)
171 186
             currentSongBackup.copy(currentSong)
172 187
             isLpUpdated.value = true
173
-            Log.d(tag, playerStoreTag +  lp.toString())
188
+            Log.d(tag, lp.toString())
174 189
         //}
175 190
     }
176 191
 
177 192
 
193
+    private fun fetchLastRequest()
194
+    {
195
+        val sleepScrape: (Any?) -> String = {
196
+            /* 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.
197
+             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)
198
+             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.
199
+            This way to fetch at the most probable time is a good compromise between fetch speed and fetch frequency
200
+            We don't fetch too often, and we start to fetch at the most *probable* time.
201
+            If there's no latencyCompensator measured yet, we only wait for 3 seconds.
202
+            If the song is the same, it will be called again. 3 seconds is a good compromise between speed and frequency:
203
+            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.
204
+             */
205
+            val sleepTime: Long = if (latencyCompensator > 0) latencyCompensator + 2000 else 3000
206
+            Thread.sleep(sleepTime) // we wait a bit (10s) for the API to get updated on R/a/dio side!
207
+            URL(urlToScrape).readText()
208
+        }
209
+
210
+        lateinit var post: (parameter: Any?) -> Unit
211
+
212
+        fun postFun(result: JSONObject)
213
+        {
214
+            if (result.has("tracks")) {
215
+                val resMain = result.getJSONObject("tracks")
216
+                /*
217
+                if ((resMain.has("isafkstream") && !resMain.getBoolean("isafkstream")) &&
218
+                    queue.isNotEmpty())
219
+                {
220
+                    queue.clear() //we're not requesting anything anymore.
221
+                    isQueueUpdated.value = true
222
+                } else if (resMain.has("isafkstream") && resMain.getBoolean("isafkstream") &&
223
+                    queue.isEmpty())
224
+                {
225
+                    initApi()
226
+                } else
227
+                */
228
+                if (resMain.has("next") /*&& queue.isNotEmpty()*/) {
229
+                    val queueJSON =
230
+                        resMain.getJSONObject("next")
231
+                    val t = extractSong(queueJSON)
232
+                    if (queue.isNotEmpty() && t == queue.last())
233
+                    {
234
+                        Log.d(tag, playerStoreTag +  "Song already in there: $t")
235
+                        Async(sleepScrape, post)
236
+                    } else {
237
+                        queue.add(queue.size, t)
238
+                        Log.d(tag, playerStoreTag +  "added last queue song: $t")
239
+                        isQueueUpdated.value = true
240
+                    }
241
+                }
242
+            }
243
+        }
244
+
245
+        post = {
246
+            val result = JSONObject(it as String)
247
+            /*  The goal is to pass the result to a function that will process it (postFun).
248
+                The magic trick is, under circumstances, the last queue song might not have been updated yet when we fetch it.
249
+                So if this is detected ==> if (t == queue.last() )
250
+                Then the function re-schedule an Async(sleepScrape, post).
251
+                To do that, the "post" must be defined BEFORE the function, but the function must be defined BEFORE the "post" value.
252
+                So I declare "post" as lateinit var, define the function, then define the "post" that calls the function. IT SHOULD WORK.
253
+             */
254
+            postFun(result)
255
+        }
256
+
257
+        Async(sleepScrape, post)
258
+    }
259
+
260
+
178 261
     // ##################################################
179 262
     // ############## PICTURE FUNCTIONS #################
180 263
     // ##################################################

+ 5 - 3
app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt View File

@@ -54,7 +54,7 @@ class NowPlayingFragment : Fragment() {
54 54
 
55 55
         // Note: these values are not used in the generic app, but if you want to, you can use them.
56 56
         val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next)
57
-        //val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next)
57
+        val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next)
58 58
 
59 59
         /*
60 60
         val streamerNameText : TextView = root.findViewById(R.id.streamerName)
@@ -69,7 +69,7 @@ class NowPlayingFragment : Fragment() {
69 69
             listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP)
70 70
          */
71 71
 
72
-        /*
72
+
73 73
         // trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change...
74 74
         // so I observe a dedicated Mutable that gets set when the queue is updated.
75 75
         PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer {
@@ -77,7 +77,7 @@ class NowPlayingFragment : Fragment() {
77 77
             songTitleNextText.text = t.title.value
78 78
             songArtistNextText.text = t.artist.value
79 79
         })
80
-
80
+        /*
81 81
         PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
82 82
             streamerNameText.text = it
83 83
         })
@@ -92,9 +92,11 @@ class NowPlayingFragment : Fragment() {
92 92
             songTitleText.text = it
93 93
         })
94 94
 
95
+        /*
95 96
         Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer {
96 97
             songTitleNextText.text = it
97 98
         })
99
+        */
98 100
 
99 101
         PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer {
100 102
             songArtistText.text = it

+ 4 - 3
app/src/main/res/layout/fragment_nowplaying.xml View File

@@ -74,7 +74,7 @@
74 74
             app:layout_constraintVertical_weight="3"
75 75
             app:layout_constraintStart_toStartOf="@id/streamerPicture"
76 76
             app:layout_constraintTop_toBottomOf="@id/streamerPicture"
77
-            android:visibility="gone"/>
77
+            android:visibility="visible"/>
78 78
         <TextView
79 79
             android:id="@+id/listenersCount"
80 80
             android:layout_width="0dp"
@@ -202,7 +202,7 @@
202 202
                     android:textSize="16sp"
203 203
                     app:layout_constraintStart_toStartOf="parent"
204 204
                     app:layout_constraintTop_toBottomOf="@id/upNext"
205
-                    android:visibility="gone"/>
205
+                    android:visibility="visible"/>
206 206
 
207 207
                 <TextView
208 208
                     android:id="@+id/text_song_title_next"
@@ -214,7 +214,8 @@
214 214
                     android:textColor="@color/whited"
215 215
                     android:textSize="16sp"
216 216
                     app:layout_constraintStart_toStartOf="parent"
217
-                    app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" />
217
+                    app:layout_constraintTop_toBottomOf="@id/text_song_artist_next"
218
+                    android:visibility="visible" />
218 219
 
219 220
             </androidx.constraintlayout.widget.ConstraintLayout>
220 221
         </ScrollView>

+ 2 - 1
app/src/main/res/values-fr/strings.xml View File

@@ -10,7 +10,8 @@
10 10
 
11 11
     <string name="volume">Volume : </string>
12 12
 
13
-    <string name="up_next">Émission en cours :</string>
13
+    <string name="up_next">Prochain titre :</string>
14
+    <string name="current_programme">Émission en cours :</string>
14 15
     <string name="now_streaming">En cours</string>
15 16
     <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>
16 17
     <string name="action_settings">Paramètres</string>

+ 2 - 1
app/src/main/res/values/strings.xml View File

@@ -25,7 +25,8 @@
25 25
 
26 26
     <string name="volume">Volume: </string>
27 27
 
28
-    <string name="up_next">Émission en cours :</string>
28
+    <string name="up_next">Up next :</string>
29
+    <string name="current_programme">Current programme :</string>
29 30
     <string name="now_streaming">Now streaming</string>
30 31
     <string name="error_webView">Error loading WebView. Try downloading Google Chrome on Google Play, or enabling it if you disabled it.</string>
31 32
     <string name="action_settings">Settings</string>