added Airtime API and progress bar
parent
1488d51388
commit
c03a002448
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,6 @@
|
|||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name=".streamerNotificationService.StreamerMonitorService"
|
||||
android:enabled="true"
|
||||
android:exported="true"/>
|
||||
|
||||
<receiver android:name=".BootBroadcastReceiver"
|
||||
android:directBootAware="true"
|
||||
|
|
|
|||
|
|
@ -22,9 +22,13 @@ class BootBroadcastReceiver : BroadcastReceiver(){
|
|||
|
||||
if (arg1.getStringExtra("action") == "$tag.${Actions.PLAY_OR_FALLBACK.name}" )
|
||||
{
|
||||
|
||||
RadioAlarm.instance.setNextAlarm(context) // schedule next alarm
|
||||
if (!PlayerStore.instance.isInitialized)
|
||||
PlayerStore.instance.initApi()
|
||||
if (PlayerStore.instance.streamerName.value.isNullOrBlank())
|
||||
PlayerStore.instance.initPicture(context)
|
||||
// TODO: add initialization for programme.
|
||||
|
||||
val i = Intent(context, RadioService::class.java)
|
||||
i.putExtra("action", Actions.PLAY_OR_FALLBACK.name)
|
||||
|
|
|
|||
|
|
@ -129,6 +129,9 @@ class MainActivity : BaseActivity() {
|
|||
colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
|
||||
colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null))
|
||||
|
||||
PlayerStore.instance.initUrl(this)
|
||||
PlayerStore.instance.initApi()
|
||||
|
||||
// Post-UI Launch
|
||||
if (PlayerStore.instance.isInitialized)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -124,6 +124,27 @@ class RadioService : MediaBrowserServiceCompat() {
|
|||
|
||||
private val titleObserver = Observer<String> {
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<Boolean> = MutableLiveData()
|
||||
val isServiceStarted: MutableLiveData<Boolean> = MutableLiveData()
|
||||
val volume: MutableLiveData<Int> = MutableLiveData()
|
||||
|
|
@ -27,6 +34,7 @@ class PlayerStore {
|
|||
val listenersCount: MutableLiveData<Int> = 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 ##############
|
||||
// ##################################################
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
||||
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
|
||||
<TextView
|
||||
|
|
@ -318,7 +318,7 @@
|
|||
android:textAlignment="textEnd"
|
||||
app:layout_constraintEnd_toEndOf="@id/progressBar"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBar"
|
||||
android:visibility="gone"/>
|
||||
android:visibility="visible"/>
|
||||
|
||||
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
|
||||
<TextView
|
||||
|
|
@ -331,7 +331,7 @@
|
|||
android:textAlignment="textStart"
|
||||
app:layout_constraintStart_toStartOf="@id/progressBar"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBar"
|
||||
android:visibility="gone"/>
|
||||
android:visibility="visible"/>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<string name="github_url_new_issue">https://github.com/yattoz/Tsumugi-app/issues/</string>
|
||||
<string name="website_url">https://tsumugi.forum-thalie.fr/</string>
|
||||
<string name="rss_url">https://tsumugi.forum-thalie.fr/?feed=rss2</string>
|
||||
<string name="API_URL">https://radio.mahoro-net.org/airtime/api/live-info-v2</string>
|
||||
<string name="planning_url">ADD SOME URL HERE</string>
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue