added Airtime API and progress bar

remotes/origin/devel
yattoz 2020-01-11 22:51:50 +01:00
parent 1488d51388
commit c03a002448
9 changed files with 164 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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