Compare commits

..

No commits in common. "master" and "v1.0" have entirely different histories.
master ... v1.0

131 changed files with 683 additions and 1604 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "ExoPlayer"]
path = ExoPlayer
url = https://github.com/google/ExoPlayer/

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
Tsumugi-app

View File

@ -1,5 +1,8 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -1,19 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Android Studio default JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/av1" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/cast" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/cronet" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/ffmpeg" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/flac" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/gvr" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/ima" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/jobdispatcher" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/leanback" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/mediasession" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/okhttp" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/opus" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/rtmp" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/vp9" />
<option value="$PROJECT_DIR$/ExoPlayer/extensions/workmanager" />
<option value="$PROJECT_DIR$/ExoPlayer/library/all" />
<option value="$PROJECT_DIR$/ExoPlayer/library/core" />
<option value="$PROJECT_DIR$/ExoPlayer/library/dash" />
<option value="$PROJECT_DIR$/ExoPlayer/library/hls" />
<option value="$PROJECT_DIR$/ExoPlayer/library/smoothstreaming" />
<option value="$PROJECT_DIR$/ExoPlayer/library/ui" />
<option value="$PROJECT_DIR$/ExoPlayer/testutils" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="testRunner" value="PLATFORM" />
</GradleProjectSettings>
</option>
</component>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
</project>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/ExoPlayer" vcs="Git" />
</component>
</project>

1
ExoPlayer Submodule

@ -0,0 +1 @@
Subproject commit 24a19264dbf35e326bbd7bb36c232eb180e1e26d

View File

@ -1,12 +0,0 @@
PRIVACY POLICY [ENGLISH]
This app does not gather nor upload any data from your device.
This app needs permission to read your phone state (android.permission.READ\_PHONE\_STATE) only and exclusively to cut down the volume of the radio when you receive a phone call. Please feel free to check the source code on GitHub at https://github.com/yattoz/webradio-app to assess this.
DECLARATION DE CONFIDENTIALITE [FRANCAIS]
Cette application ne récupère ni n'émet aucune donnée de votre appareil.
Cette application nécessite de lire l'état de votre téléphone (android.permission.READ\_PHONE\_STATE) uniquement et exclusivement pour couper le volume de la radio si vous recevez un appel téléphonique. Le code source est mis à votre disposition sur GitHub à l'adresse https://github.com/yattoz/webradio-app où vous pouvez vérifier ceci.

View File

@ -16,21 +16,21 @@ configurations.all {
}
android {
compileSdkVersion 32
compileSdkVersion 29
buildToolsVersion "29.0.2"
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "1.8"
}
defaultConfig {
applicationId "fr.riff_app.riff"
applicationId "fr.forum_thalie.tsumugi"
minSdkVersion 16
targetSdkVersion 32
multiDexEnabled true
versionCode 121
versionName "1.2.1"
targetSdkVersion 29
versionCode 100
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
@ -48,9 +48,6 @@ android {
}
dependencies {
def multidex_version = "2.0.1"
implementation "androidx.multidex:multidex:$multidex_version"
implementation fileTree(dir: 'libs', include: ['*.jar'])
/*
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@ -71,18 +68,15 @@ dependencies {
*/
implementation "androidx.media:media:1.5.0"
implementation "androidx.media:media:1.1.0"
//implementation 'com.google.android.exoplayer:exoplayer:2.11.1'
implementation project(':exoplayer-library') // uses local ExoPlayer clone, from dev-v2 branch (fixes ISO-8859-1 ICY metadata
//noinspection GradleDependency
implementation 'com.google.android.exoplayer:exoplayer:2.11.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
//implementation project(':exoplayer-library') // uses local ExoPlayer clone, from dev-v2 branch (fixes ISO-8859-1 ICY metadata
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
// jsoup HTML parser library @ https://jsoup.org/
// implementation 'org.jsoup:jsoup:1.12.1'

View File

@ -0,0 +1,24 @@
package fr.forum_thalie.tsumugi
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("fr.forum_thalie.tsumugi", appContext.packageName)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

View File

@ -1,14 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.84375"
android:scaleY="0.84375">
<path
android:strokeWidth="1"
android:pathData="M-22.4,-18.02h249.46v189.63h-249.46z"
android:fillColor="#000000"
android:strokeColor="#ffffff"/>
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="fr.riff_app.riff">
package="fr.forum_thalie.tsumugi">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
@ -10,11 +10,9 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- needed for API 28 -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="true"
@ -23,25 +21,27 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme.Launcher"
tools:ignore="AllowBackup,GoogleAppIndexingWarning"
android:name="androidx.multidex.MultiDexApplication">
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<service
android:name=".RadioService"
android:enabled="true"
android:exported="false">
android:exported="true">
<intent-filter>
<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"
tools:targetApi="n"
android:exported="true">
tools:targetApi="n">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="fr.riff_app.riff.PLAY_OR_FALLBACK" />
<action android:name="fr.riff_app.riff.NOTIFY" /> <!-- this is for Snooze -->
<action android:name="fr.forum_thalie.tsumugi.PLAY_OR_FALLBACK" />
<action android:name="fr.forum_thalie.tsumugi.NOTIFY" /> <!-- this is for Snooze -->
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
@ -49,9 +49,9 @@
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="fullUser"
android:exported="true">
android:screenOrientation="fullSensor">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -63,10 +63,9 @@
android:configChanges="orientation|screenSize"
android:launchMode="singleTask"
android:parentActivityName=".MainActivity"
android:screenOrientation="fullUser" />
android:screenOrientation="fullSensor" />
<receiver android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>

View File

@ -1,78 +1,66 @@
{
"planning": [
{
"title": "La playlist Base Rock",
"title": "Musique Classique",
"periodicity": "1111111",
"hour_begin": "00:00",
"hour_begin": "6:00",
"hour_end": "7:00"
},
{
"title": "Jeux Vidéos Matin",
"periodicity": "1111100",
"hour_begin": "09:00",
"hour_end": "10:30"
},
{
"title": "Anime Matin",
"periodicity": "1111100",
"hour_begin": "10:30",
"hour_end": "12:00"
},
{
"title": "Jeux Vidéos Aprem'",
"periodicity": "1111111",
"hour_begin": "14:00",
"hour_end": "15:30"
},
{
"title": "Anime Aprem'",
"periodicity": "1111111",
"hour_begin": "15:30",
"hour_end": "17:00"
},
{
"title": "Jeux Vidéos Soirée",
"periodicity": "1111100",
"hour_begin": "19:00",
"hour_end": "20:00"
},
{
"title": "Anime Soirée",
"periodicity": "1111100",
"hour_begin": "20:00",
"hour_end": "21:00"
},
{
"title": "Only Covers",
"periodicity": "1000000",
"hour_begin": "20:30",
"hour_end": "00:00"
"title": "Anime Nuit",
"periodicity": "1111111",
"hour_begin": "00:00",
"hour_end": "01:00"
},
{
"title": "Only Indies",
"periodicity": "0100000",
"hour_begin": "20:30",
"hour_end": "00:00"
"title": "Jeux Vidéos Nuit",
"periodicity": "1111111",
"hour_begin": "01:00",
"hour_end": "02:00"
},
{
"title": "Only Acoustic",
"periodicity": "0010000",
"hour_begin": "20:30",
"hour_end": "00:00"
},
{
"title": "Only Sweden",
"periodicity": "0001000",
"hour_begin": "20:30",
"hour_end": "00:00"
},
{
"title": "Only Lives",
"periodicity": "0000100",
"hour_begin": "20:30",
"hour_end": "00:00"
},
{
"title": "Only Games",
"periodicity": "0000010",
"hour_begin": "21:00",
"hour_end": "23:15"
},
{
"title": "Beyond [Azraël]",
"periodicity": "0100000",
"hour_begin": "21:00",
"hour_end": "23:00"
},
{
"title": "Piste Rouge [Louise et Trotsky]",
"periodicity": "0010000",
"hour_begin": "21:00",
"hour_end": "23:00"
},
{
"title": "Katamariff [Herondil & co.]",
"periodicity": "0000100",
"hour_begin": "21:00",
"hour_end": "23:00"
},
{
"title": "Le 4/4 Jazz [Melomaniaque]",
"periodicity": "0000001",
"hour_begin": "21:00",
"hour_end": "22:00"
},
{
"title": "Beyond, la playlist",
"periodicity": "0000001",
"hour_begin": "22:10",
"hour_end": "00:00"
"title": "Programmation nocturne",
"periodicity": "1111111",
"hour_begin": "02:00",
"hour_end": "06:00"
}
],
"regular_programme" : "Programmation habituelle",
"timezone": "Europe/Paris"
"timezone": "GMT+1"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
enum class Actions
{

View File

@ -1,8 +1,8 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.os.AsyncTask
import android.util.Log
import fr.riff_app.riff.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
private val actionOnError: ActionOnError = ActionOnError.RESET, private val parameters: Any? = null) :
@ -13,7 +13,7 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
execute()
} catch (e: Exception)
{
//[REMOVE LOG CALLS]Log.d(tag,e.toString())
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag,e.toString())
}
}
@ -46,14 +46,14 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
}
//[REMOVE LOG CALLS]Log.d(tag, "fallback for no network. Store reset : $storeReset")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "fallback for no network. Store reset : $storeReset")
}
override fun doInBackground(vararg params: Any?): Any? {
try {
return handler(parameters)
} catch (e: Exception) {
//[REMOVE LOG CALLS]Log.d(tag,e.toString())
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag,e.toString())
onException(e)
}
return null
@ -63,7 +63,7 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {},
try {
post(result)
} catch (e: Exception) {
//[REMOVE LOG CALLS]Log.d(tag,e.toString())
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag,e.toString())
onException(e)
}
}

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.content.Intent
import android.os.Bundle
@ -21,7 +21,7 @@ abstract class BaseActivity : AppCompatActivity() {
val height = ((rootLayout?.height ?: 0))
val width = ((rootLayout?.width ?: 0))
//[REMOVE LOG CALLS]Log.d(tag, "$viewWidth, $viewHeight, $width, $height, ${viewHeight.toDouble()/viewWidth.toDouble()}, ${height.toDouble()/width.toDouble()}")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$viewWidth, $viewHeight, $width, $height, ${viewHeight.toDouble()/viewWidth.toDouble()}, ${height.toDouble()/width.toDouble()}")
val broadcastManager = LocalBroadcastManager.getInstance(this@BaseActivity)
if(height <= viewHeight * 2 / 3 /*height.toDouble()/width.toDouble() < 1.20 */){
@ -49,14 +49,14 @@ abstract class BaseActivity : AppCompatActivity() {
// do things when keyboard is shown
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.visibility = View.GONE
//[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to GONE (height $keyboardHeight)")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to GONE (height $keyboardHeight)")
}
private fun onHideKeyboard() {
// do things when keyboard is hidden
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.visibility = View.VISIBLE
//[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to VISIBLE")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to VISIBLE")
}
protected fun attachKeyboardListeners() {

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.app.Notification
import android.app.NotificationChannel
@ -50,7 +50,6 @@ abstract class BaseNotification(private val notificationChannelId: String,
notificationManager.notify(notificationId, notification)
}
@RequiresApi(Build.VERSION_CODES.M)
open fun create(c: Context) {
notificationManager = c.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -59,7 +58,7 @@ abstract class BaseNotification(private val notificationChannelId: String,
// thanks to the launchMode specified in the Manifest : android:launchMode="singleTop"
val pendingIntent = PendingIntent.getActivity(
c, 0,
notificationIntent, PendingIntent.FLAG_IMMUTABLE
notificationIntent, 0
)
var channelID = ""

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.content.BroadcastReceiver
import android.content.Context
@ -6,14 +6,13 @@ import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.preference.PreferenceManager
import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
class BootBroadcastReceiver : BroadcastReceiver(){
override fun onReceive(context: Context, arg1: Intent) {
//[REMOVE LOG CALLS]Log.d(tag, "Broadcast Receiver received $arg1")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "Broadcast Receiver received $arg1")
// define preferenceStore for places of the program that needs to access Preferences without a context
preferenceStore = PreferenceManager.getDefaultSharedPreferences(context)
@ -23,11 +22,7 @@ class BootBroadcastReceiver : BroadcastReceiver(){
if (arg1.getStringExtra("action") == "$tag.${Actions.PLAY_OR_FALLBACK.name}" )
{
RadioAlarm.instance.setNextAlarm(context) // schedule next alarm
Planning.instance.parseUrl(context = context)
if (!PlayerStore.instance.isInitialized)
PlayerStore.instance.initApi()
if (PlayerStore.instance.streamerName.value.isNullOrBlank())
PlayerStore.instance.initPicture(context)

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
@ -11,13 +11,12 @@ import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import fr.riff_app.riff.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import java.util.Timer
import android.view.MenuItem
import com.google.android.material.snackbar.Snackbar
import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.planning.Planning
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.planning.Planning
/* Log to file import
@ -87,14 +86,6 @@ class MainActivity : BaseActivity() {
true
}
*/
R.id.action_refresh -> {
PlayerStore.instance.queue.clear()
//PlayerStore.instance.lp.clear()
PlayerStore.instance.initApi()
val s = Snackbar.make(findViewById(R.id.nav_host_container), getString(R.string.refreshing) as CharSequence, Snackbar.LENGTH_LONG)
s.show()
true
}
R.id.action_settings -> {
val i = Intent(this, ParametersActivity::class.java)
startActivity(i)
@ -117,7 +108,7 @@ class MainActivity : BaseActivity() {
}
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState ?: Bundle())
// Now that BottomNavigationBar has restored its instance state
// and its selectedItemId, we can proceed with setting up the
@ -138,16 +129,10 @@ class MainActivity : BaseActivity() {
colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null))
// fetch program
Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
PlayerStore.instance.initUrl(this)
PlayerStore.instance.initApi()
// Post-UI Launch
if (PlayerStore.instance.isInitialized)
{
//[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
} else {
// if the service is not started, start it in STOP mode.
// It's not a dummy action : with STOP mode, the player does not buffer audio (and does not use data connection without the user's consent).
@ -175,6 +160,9 @@ class MainActivity : BaseActivity() {
isTimerStarted = true
}
// fetch program
Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
// initialize the UI
setTheme(R.style.AppTheme)
setContentView(R.layout.activity_main)
@ -205,7 +193,7 @@ class MainActivity : BaseActivity() {
val i = Intent(this, RadioService::class.java)
i.putExtra("action", a.name)
i.putExtra("value", v)
//[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}")
startService(i)
}
@ -250,7 +238,7 @@ class MainActivity : BaseActivity() {
// File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
val logDirectory = File("$appDirectory/log")
val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
//[REMOVE LOG CALLS]Log.d(
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(
tag,
"appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
)
@ -258,20 +246,20 @@ class MainActivity : BaseActivity() {
// create app folder
if (!appDirectory.exists()) {
appDirectory.mkdir()
//[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
}
// create log folder
if (!logDirectory.exists()) {
logDirectory.mkdir()
//[REMOVE LOG CALLS]Log.d(tag, "$logDirectory created")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$logDirectory created")
}
// clear the previous logcat and then write the new one to the file
try {
Runtime.getRuntime().exec("logcat -c")
Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ")
//[REMOVE LOG CALLS]Log.d(tag, "logcat started")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "logcat started")
} catch (e: IOException) {
e.printStackTrace()
}

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.content.Intent

View File

@ -1,14 +1,12 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import fr.riff_app.riff.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
class NowPlayingNotification(
notificationChannelId: String,
@ -36,19 +34,18 @@ class NowPlayingNotification(
// got it right
val delIntent = Intent(c, RadioService::class.java)
delIntent.putExtra("action", Actions.KILL.name)
val deleteIntent = PendingIntent.getService(c, 0, delIntent, PendingIntent.FLAG_NO_CREATE + PendingIntent.FLAG_IMMUTABLE)
val deleteIntent = PendingIntent.getService(c, 0, delIntent, PendingIntent.FLAG_NO_CREATE)
builder.setDeleteIntent(deleteIntent)
mediaStyle = androidx.media.app.NotificationCompat.DecoratedMediaCustomViewStyle().also {
it.setMediaSession(m.sessionToken)
it.setShowActionsInCompactView(0, 1) // index 0 = show actions 0 and 1 (show action #0 (play/pause))
it.setShowActionsInCompactView(0) // index 0 = show actions 0 and 1 (show action #0 (play/pause))
it.setCancelButtonIntent(deleteIntent)
}
builder.setStyle(mediaStyle)
update(c)
}
@RequiresApi(Build.VERSION_CODES.M)
fun update(c: Context, isUpdatingNotificationButton: Boolean = false, isRinging: Boolean = false) {
if (isUpdatingNotificationButton)
@ -75,18 +72,18 @@ class NowPlayingNotification(
playPauseAction = if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING) {
intent.putExtra("action", Actions.PAUSE.name)
val pendingButtonIntent = PendingIntent.getService(c, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
val pendingButtonIntent = PendingIntent.getService(c, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
NotificationCompat.Action.Builder(R.drawable.ic_pause, "Pause", pendingButtonIntent).build()
} else {
intent.putExtra("action", Actions.PLAY.name)
val pendingButtonIntent = PendingIntent.getService(c, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
val pendingButtonIntent = PendingIntent.getService(c, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
NotificationCompat.Action.Builder(R.drawable.ic_play,"Play", pendingButtonIntent).build()
}
builder.addAction(playPauseAction)
val intent2 = Intent(c, RadioService::class.java)
intent2.putExtra("action", Actions.KILL.name)
val pendingButtonIntent = PendingIntent.getService(c, 2, intent2, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
val stopAction = NotificationCompat.Action.Builder(R.drawable.ic_close,"Stop", pendingButtonIntent).build()
val pendingButtonIntent = PendingIntent.getService(c, 2, intent2, PendingIntent.FLAG_UPDATE_CURRENT)
val stopAction = NotificationCompat.Action.Builder(R.drawable.ic_stop,"Stop", pendingButtonIntent).build()
builder.addAction(stopAction)
if (isRinging) {
@ -95,7 +92,7 @@ class NowPlayingNotification(
val snoozeIntent = Intent(c, RadioService::class.java)
snoozeIntent.putExtra("action", Actions.SNOOZE.name)
val pendingSnoozeIntent = PendingIntent.getService(c, 5, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE)
val pendingSnoozeIntent = PendingIntent.getService(c, 5, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val snoozeAction = NotificationCompat.Action.Builder(R.drawable.ic_alarm, "Snooze ($snoozeMinutes min.)", pendingSnoozeIntent ).build()
if (snoozeMinutes > 0)
builder.addAction(snoozeAction)

View File

@ -1,8 +1,7 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.os.Bundle
import android.view.MenuItem
import fr.riff_app.riff.preferences.*
import fr.forum_thalie.tsumugi.preferences.*
class ParametersActivity : BaseActivity() {
@ -39,15 +38,4 @@ class ParametersActivity : BaseActivity() {
.replace(R.id.parameters_host_container, fragmentToLoad)
.commit()
}
// Make the Up button function as back instead of always bringing us to the main activity
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}
}

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.app.Service
import android.content.BroadcastReceiver
@ -32,10 +32,10 @@ import androidx.media.AudioManagerCompat
import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.metadata.icy.*
import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.alarm.RadioSleeper
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.alarm.RadioSleeper
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import java.util.*
import kotlin.math.exp
import kotlin.math.ln
@ -68,14 +68,14 @@ class RadioService : MediaBrowserServiceCompat() {
// This *should* work in any case...
when (intent.getIntExtra("state", -1)) {
0 -> {
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is unplugged")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is unplugged")
}
1 -> {
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is plugged")
headsetPluggedIn = true && PreferenceManager.getDefaultSharedPreferences(context).getBoolean("autoStartOnPlug", false)
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is plugged")
headsetPluggedIn = true
}
else -> {
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "I have no idea what the headset state is")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "I have no idea what the headset state is")
}
}
/*
@ -91,7 +91,7 @@ class RadioService : MediaBrowserServiceCompat() {
}
else
{
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Can't get state?")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Can't get state?")
}
*/
@ -124,32 +124,10 @@ 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)
{
//[REMOVE LOG CALLS]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)
{
PlayerStore.instance.updateLp()
PlayerStore.instance.updateQueue()
}
nowPlayingNotification.update(this)
Planning.instance.checkProgramme()
@ -185,16 +163,9 @@ 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)
telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
//define the audioFocusRequest
@ -234,7 +205,7 @@ class RadioService : MediaBrowserServiceCompat() {
startForeground(radioServiceId, nowPlayingNotification.notification)
PlayerStore.instance.isServiceStarted.value = true
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "created")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "created")
}
private val handler = Handler()
@ -275,7 +246,7 @@ class RadioService : MediaBrowserServiceCompat() {
Actions.CANCEL_FADE_OUT.name -> { handler.removeCallbacks(lowerVolumeRunnable) }
Actions.SNOOZE.name -> { RadioAlarm.instance.snooze(this) }
}
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "intent received : " + intent.getStringExtra("action"))
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "intent received : " + intent.getStringExtra("action"))
super.onStartCommand(intent, flags, startId)
// The service must be re-created if it is destroyed by the system. This allows the user to keep actions like Bluetooth and headphones plug available.
return START_STICKY
@ -287,7 +258,7 @@ class RadioService : MediaBrowserServiceCompat() {
stopSelf()
}
super.onTaskRemoved(rootIntent)
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "task removed")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "task removed")
}
override fun onDestroy() {
@ -319,7 +290,7 @@ class RadioService : MediaBrowserServiceCompat() {
}
apiTicker.cancel() // stops the timer.
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "destroyed")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "destroyed")
// if the service is destroyed, the application had become useless.
exitProcess(0)
}
@ -390,10 +361,10 @@ class RadioService : MediaBrowserServiceCompat() {
for (i in 0 until it.length()) {
val entry = it.get(i)
if (entry is IcyHeaders) {
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: IcyHeaders $entry")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: IcyHeaders $entry")
}
if (entry is IcyInfo) {
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: Title ----> ${entry.title}")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: Title ----> ${entry.title}")
// Note : Kotlin supports UTF-8 by default.
numberOfSongs++
val data = entry.title!!
@ -428,7 +399,7 @@ class RadioService : MediaBrowserServiceCompat() {
)
// This is the MediaSource representing the media to be played.
radioMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(getString(R.string.STREAM_URL_RIFF)))
.createMediaSource(Uri.parse(getString(R.string.STREAM_URL_TSUMUGI)))
fallbackMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse("file:///android_asset/the_stream_is_down.mp3"))
@ -455,13 +426,12 @@ class RadioService : MediaBrowserServiceCompat() {
// this function is playing the stream if available, or a default sound if there's a problem.
private fun beginPlayingOrFallback()
{
PlayerStore.instance.volume.value = PreferenceManager.getDefaultSharedPreferences(this).getInt("alarmVolume", 100)
// we set the max volume for exoPlayer to be sure it rings correctly.
PlayerStore.instance.volume.value = 100 // we set the max volume for exoPlayer to be sure it rings correctly.
beginPlaying(isRinging = true, isFallback = false)
val wait: (Any?) -> Any = {
/*
Here we lower the isAlarmStopped flag and we wait for 17s. (seems like 12 could be a bit too short since I increased the buffer!!)
If the user stops the alarm (by calling an intent), the isAlarmStopped flag will be raised.
If the player stops the alarm (by calling an intent), the isAlarmStopped flag will be raised.
*/
isAlarmStopped = false // reset the flag
var i = 0
@ -469,7 +439,7 @@ class RadioService : MediaBrowserServiceCompat() {
{
Thread.sleep(1000)
i++
//[REMOVE LOG CALLS]Log.d(tag, "$i, isAlarmStopped=$isAlarmStopped")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$i, isAlarmStopped=$isAlarmStopped")
}
}
val post: (Any?) -> Unit = {
@ -500,7 +470,6 @@ class RadioService : MediaBrowserServiceCompat() {
.setUsage(C.USAGE_ALARM)
.build()
} else {
isAlarmStopped = true // if we're not ringing and it tries playing, it means the user opened the app somehow
audioAttributes.setUsage(AudioAttributesCompat.USAGE_MEDIA)
audioFocusRequestBuilder.setAudioAttributes(audioAttributes.build())
audioFocusRequest = audioFocusRequestBuilder.build()
@ -515,11 +484,9 @@ class RadioService : MediaBrowserServiceCompat() {
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return
}
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_PLAYING && !isRinging && isAlarmStopped)
{
return //nothing to do here
}
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_PLAYING && !isRinging)
return // nothing to do here
PlayerStore.instance.playbackState.value = PlaybackStateCompat.STATE_PLAYING
// Reinitialize media player. Otherwise the playback doesn't resume when beginPlaying. Dunno why.
@ -545,8 +512,7 @@ class RadioService : MediaBrowserServiceCompat() {
SystemClock.elapsedRealtime()
)
mediaSession.setPlaybackState(playbackStateBuilder.build())
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "begin playing")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "begin playing")
}
private fun pausePlaying()
@ -559,6 +525,7 @@ class RadioService : MediaBrowserServiceCompat() {
{
if (mediaSession.controller.playbackState.state == PlaybackStateCompat.STATE_STOPPED)
return // nothing to do here
if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING)
isAlarmStopped = true
@ -574,7 +541,7 @@ class RadioService : MediaBrowserServiceCompat() {
1.0f,
SystemClock.elapsedRealtime()
)
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "stopped")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "stopped")
mediaSession.setPlaybackState(playbackStateBuilder.build())
}
@ -662,7 +629,7 @@ class RadioService : MediaBrowserServiceCompat() {
Player.STATE_ENDED -> state = "Player.STATE_ENDED"
Player.STATE_READY -> state = "Player.STATE_READY"
}
//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Player changed state: ${state}. numberOfSongs reset.")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, radioTag + "Player changed state: ${state}. numberOfSongs reset.")
}
}

View File

@ -0,0 +1,11 @@
package fr.forum_thalie.tsumugi
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import java.util.*
class Tick : TimerTask() {
override fun run() {
PlayerStore.instance.currentTime.postValue(PlayerStore.instance.currentTime.value!! + 500)
}
}

View File

@ -1,12 +1,12 @@
package fr.riff_app.riff
package fr.forum_thalie.tsumugi
import android.content.SharedPreferences
import android.content.res.ColorStateList
import kotlin.collections.ArrayList
const val tag = "fr.riff_app.riff"
const val tag = "fr.forum_thalie.tsumugi"
const val noConnectionValue = ""
const val streamDownValue = "Riff est HS !" // we don't want this value to be displaed in the "last played" screen.
const val streamDownValue = "Tsumugi est HS !" // we don't want this value to be displaed in the "last played" screen.
val weekdaysArray : Array<String> = arrayOf( "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
const val newsDateTimePattern = "EEE, d MMM yyyy HH:mm:ss Z"
const val newsDisplayDatePattern = "dd MMM yyyy"

View File

@ -1,16 +1,14 @@
package fr.riff_app.riff.alarm
package fr.forum_thalie.tsumugi.alarm
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.AlarmManagerCompat
import fr.riff_app.riff.BootBroadcastReceiver
import fr.forum_thalie.tsumugi.BootBroadcastReceiver
import androidx.preference.PreferenceManager
import fr.riff_app.riff.*
import fr.forum_thalie.tsumugi.*
import java.util.*
import kotlin.collections.ArrayList
@ -29,31 +27,13 @@ class RadioAlarm {
{
alarmIntent = Intent(c, BootBroadcastReceiver::class.java).let { intent ->
intent.putExtra("action", "$tag.${Actions.PLAY_OR_FALLBACK.name}")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getBroadcast(
c,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
} else {
PendingIntent.getBroadcast(
c,
0,
intent,
0
)
}
PendingIntent.getBroadcast(c, 0, intent, 0)
}
showIntent = Intent(c, ParametersActivity::class.java).let { intent ->
intent.putExtra("action", "alarm")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getActivity(c, 0, intent, PendingIntent.FLAG_IMMUTABLE)
} else {
PendingIntent.getActivity(c, 0, intent, 0)
}
}
}
fun cancelAlarm(c: Context)
@ -63,7 +43,6 @@ class RadioAlarm {
alarmManager.cancel(alarmIntent)
}
@RequiresApi(Build.VERSION_CODES.M)
fun setNextAlarm(c: Context, isForce: Boolean = false, forceTime: Int? = null, forceDays: Set<String>? = null)
{
defineIntents(c)
@ -75,7 +54,7 @@ class RadioAlarm {
val showIntent = Intent(c, ParametersActivity::class.java).let { intent ->
intent.putExtra("action", ActionOpenParam.ALARM.name)
PendingIntent.getActivity(c, 0, intent, PendingIntent.FLAG_IMMUTABLE)
PendingIntent.getActivity(c, 0, intent, 0)
}
val time = findNextAlarmTime(c, forceTime, forceDays)
if (time > 0)
@ -118,7 +97,7 @@ class RadioAlarm {
calendar.isLenient = true
calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) + i, hourOfDay, minute)
//[REMOVE LOG CALLS]Log.d(tag, calendar.toString())
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, calendar.toString())
return calendar.timeInMillis

View File

@ -1,16 +1,14 @@
package fr.riff_app.riff.alarm
package fr.forum_thalie.tsumugi.alarm
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.AlarmManagerCompat
import androidx.lifecycle.MutableLiveData
import androidx.preference.PreferenceManager
import fr.riff_app.riff.*
import fr.forum_thalie.tsumugi.*
class RadioSleeper {
@ -32,20 +30,18 @@ class RadioSleeper {
private lateinit var sleepIntent: PendingIntent
private lateinit var fadeOutIntent: PendingIntent
@RequiresApi(Build.VERSION_CODES.M)
private fun defineIntents(c: Context)
{
sleepIntent = Intent(c, RadioService::class.java).let { intent ->
intent.putExtra("action", Actions.KILL.name)
PendingIntent.getService(c, 99, intent, PendingIntent.FLAG_IMMUTABLE)
PendingIntent.getService(c, 99, intent, 0)
}
fadeOutIntent = Intent(c, RadioService::class.java).let { intent ->
intent.putExtra("action", Actions.FADE_OUT.name)
PendingIntent.getService(c, 98, intent, PendingIntent.FLAG_IMMUTABLE)
PendingIntent.getService(c, 98, intent, 0)
}
}
@RequiresApi(Build.VERSION_CODES.M)
fun setSleep(c: Context, isForce: Boolean = false, forceDuration: Long? = null)
{
defineIntents(c)
@ -65,7 +61,7 @@ class RadioSleeper {
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, currentMillis + (minutes * 60 * 1000) - (1 * 60 * 1000), fadeOutIntent)
sleepAtMillis.value = System.currentTimeMillis() + (minutes * 60 * 1000) - 1 // this -1 allows to round the division for display at the right integer
//[REMOVE LOG CALLS]Log.d(tag, "set sleep to $minutes minutes")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "set sleep to $minutes minutes")
}
}
@ -83,7 +79,7 @@ class RadioSleeper {
c.startService(cancelFadeOutIntent)
}
//[REMOVE LOG CALLS]Log.d(tag, "cancelled sleep")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "cancelled sleep")
sleepAtMillis.value = null
}
}

View File

@ -1,11 +1,11 @@
package fr.riff_app.riff.planning
package fr.forum_thalie.tsumugi.planning
import android.content.Context
import androidx.lifecycle.MutableLiveData
import fr.riff_app.riff.Async
import fr.riff_app.riff.R
import fr.riff_app.riff.noConnectionValue
import fr.riff_app.riff.weekdays
import fr.forum_thalie.tsumugi.Async
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.noConnectionValue
import fr.forum_thalie.tsumugi.weekdays
import org.json.JSONObject
import java.io.IOException
import java.net.URL
@ -64,7 +64,6 @@ class Planning {
if (result.has("planning"))
{
val programList = result.getJSONArray("planning")
programmes.clear()
for (i in 0 until programList.length())
{
val item = programList[i] as JSONObject

View File

@ -1,8 +1,8 @@
package fr.riff_app.riff.planning
package fr.forum_thalie.tsumugi.planning
import android.util.Log
import fr.riff_app.riff.tag
import fr.riff_app.riff.weekdays
import fr.forum_thalie.tsumugi.tag
import fr.forum_thalie.tsumugi.weekdays
import java.util.*
import kotlin.collections.ArrayList
@ -35,7 +35,7 @@ class Programme (val title: String, private val periodicity: Int, private val ho
val isSpanningOverNight =
(((0b1000000 shr ((currentDay - 1) % 7) and (periodicity)) != 0) && hourEnd < hourBegin)
//[REMOVE LOG CALLS]Log.d(tag, "$title is today: $isToday or spanning $isSpanningOverNight")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$title is today: $isToday or spanning $isSpanningOverNight")
// shr = shift-right. It's a binary mask.
// if the program started yesterday, and spanned over night, it means that there could be a chance that it's still active.
@ -87,6 +87,6 @@ class Programme (val title: String, private val periodicity: Int, private val ho
*/
init {
//[REMOVE LOG CALLS]Log.d(tag, this.toString())
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, this.toString())
}
}

View File

@ -0,0 +1,81 @@
package fr.forum_thalie.tsumugi.playerstore
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import androidx.lifecycle.MutableLiveData
import fr.forum_thalie.tsumugi.*
class PlayerStore {
val isPlaying: MutableLiveData<Boolean> = MutableLiveData()
val isServiceStarted: MutableLiveData<Boolean> = MutableLiveData()
val volume: MutableLiveData<Int> = MutableLiveData()
val playbackState: MutableLiveData<Int> = MutableLiveData()
val currentTime: MutableLiveData<Long> = MutableLiveData()
val streamerPicture: MutableLiveData<Bitmap> = MutableLiveData()
val streamerName: MutableLiveData<String> = MutableLiveData()
val currentSong : Song = Song()
val currentSongBackup: Song = Song()
val lp : ArrayList<Song> = ArrayList()
val queue : ArrayList<Song> = ArrayList()
val isQueueUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isLpUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isMuted : MutableLiveData<Boolean> = MutableLiveData()
val listenersCount: MutableLiveData<Int> = MutableLiveData()
var latencyCompensator : Long = 0
var isInitialized: Boolean = false
init {
playbackState.value = PlaybackStateCompat.STATE_STOPPED
isPlaying.value = false
isServiceStarted.value = false
streamerName.value = ""
volume.value = preferenceStore.getInt("volume", 100)
currentTime.value = System.currentTimeMillis()
isQueueUpdated.value = false
isLpUpdated.value = false
isMuted.value = false
currentSong.title.value = noConnectionValue
currentSongBackup.title.value = noConnectionValue
listenersCount.value = 0
}
// ##################################################
// ############## QUEUE / LP FUNCTIONS ##############
// ##################################################
fun updateLp() {
// note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
//if (lp.isNotEmpty()){
val n = Song()
n.copy(currentSongBackup)
if (n.title.value != noConnectionValue && n.title.value != streamDownValue)
lp.add(0, n)
currentSongBackup.copy(currentSong)
isLpUpdated.value = true
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + lp.toString())
//}
}
// ##################################################
// ############## PICTURE FUNCTIONS #################
// ##################################################
fun initPicture(c: Context) {
streamerPicture.value = BitmapFactory.decodeResource(c.resources,
R.drawable.logo_roundsquare
)
}
private val playerStoreTag = "====PlayerStore===="
companion object {
val instance by lazy {
PlayerStore()
}
}
}

View File

@ -1,8 +1,7 @@
package fr.riff_app.riff.playerstore
package fr.forum_thalie.tsumugi.playerstore
import androidx.core.text.HtmlCompat
import androidx.lifecycle.MutableLiveData
import fr.riff_app.riff.noConnectionValue
import fr.forum_thalie.tsumugi.noConnectionValue
class Song(artistTitle: String = "", _id : Int = 0) {
@ -25,9 +24,8 @@ class Song(artistTitle: String = "", _id : Int = 0) {
return "id=$id | ${artist.value} - ${title.value} | type=${type.value} | times ${startTime.value} - ${stopTime.value}\n"
}
fun setTitleArtist(dataHtml: String)
fun setTitleArtist(data: String)
{
val data = HtmlCompat.fromHtml(dataHtml, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
val hyphenPos = data.indexOf(" - ")
try {
if (hyphenPos < 0)
@ -48,12 +46,12 @@ class Song(artistTitle: String = "", _id : Int = 0) {
override fun equals(other: Any?) : Boolean
{
val song: Song = other as Song
return this.title.value === song.title.value && this.artist.value === song.artist.value
return this.title.value == song.title.value && this.artist.value == song.artist.value
}
fun copy(song: Song) {
this.artist.value = song.artist.value
this.title.value = song.title.value
this.artist.value = song.artist.value
this.startTime.value = song.startTime.value
this.stopTime.value = song.stopTime.value
this.type.value = song.type.value

View File

@ -1,38 +1,16 @@
package fr.riff_app.riff.preferences
package fr.forum_thalie.tsumugi.preferences
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.media.AudioManager
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.preference.*
import fr.riff_app.riff.*
import fr.riff_app.riff.R
import fr.riff_app.riff.alarm.RadioAlarm
import fr.riff_app.riff.playerstore.PlayerStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope.coroutineContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import fr.forum_thalie.tsumugi.*
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.max
import kotlin.math.min
class AlarmFragment : PreferenceFragmentCompat() {
override fun onAttach(context: Context) {
(activity as AppCompatActivity).supportActionBar?.title = context.getString(R.string.setupAlarmClock)
super.onAttach(context)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.alarm_preferences, rootKey)
@ -47,7 +25,7 @@ class AlarmFragment : PreferenceFragmentCompat() {
fun updateIsWakingUpSummary(preference: SwitchPreferenceCompat?, newValue: Boolean? = true, forceTime: Int? = null, forceDays: Set<String>? = null)
{
val dateLong = RadioAlarm.instance.findNextAlarmTime(requireContext(), forceTime, forceDays)
val dateLong = RadioAlarm.instance.findNextAlarmTime(context!!, forceTime, forceDays)
val calendar = Calendar.getInstance()
calendar.timeInMillis = dateLong
if (newValue == true && calendar.timeInMillis > 0)
@ -65,14 +43,14 @@ class AlarmFragment : PreferenceFragmentCompat() {
val hourOfDayDefault = 7
val minuteDefault = 0
if (!PreferenceManager.getDefaultSharedPreferences(requireContext()).contains("alarmTimeFromMidnight"))
if (!PreferenceManager.getDefaultSharedPreferences(context!!).contains("alarmTimeFromMidnight"))
{
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit {
PreferenceManager.getDefaultSharedPreferences(context!!).edit {
putInt("alarmTimeFromMidnight", (60*hourOfDayDefault+minuteDefault))
commit()
}
}
val time = PreferenceManager.getDefaultSharedPreferences(requireContext()).getInt("alarmTimeFromMidnight", (60*hourOfDayDefault+minuteDefault))
val time = PreferenceManager.getDefaultSharedPreferences(context!!).getInt("alarmTimeFromMidnight", (60*hourOfDayDefault+minuteDefault))
val hourOfDay = time / 60
val minute = time % 60
timeSet?.summary = "${if (hourOfDay < 10) "0" else ""}$hourOfDay:${if (minute < 10) "0" else ""}$minute"
@ -80,16 +58,16 @@ class AlarmFragment : PreferenceFragmentCompat() {
timeSet?.setOnPreferenceClickListener {
val timePickerOnTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute ->
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit {
PreferenceManager.getDefaultSharedPreferences(context!!).edit {
putInt("alarmTimeFromMidnight", (60*hourOfDay+minute))
commit()
}
timeSet.summary = "${if (hourOfDay < 10) "0" else ""}$hourOfDay:${if (minute < 10) "0" else ""}$minute"
RadioAlarm.instance.cancelAlarm(requireContext())
RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true, forceTime = hourOfDay*60+minute)
RadioAlarm.instance.cancelAlarm(context!!)
RadioAlarm.instance.setNextAlarm(context!!, isForce = true, forceTime = hourOfDay*60+minute)
updateIsWakingUpSummary(isWakingUp, isWakingUp?.isChecked, forceTime = hourOfDay*60+minute)
}
val timeNew = PreferenceManager.getDefaultSharedPreferences(requireContext()).getInt("alarmTimeFromMidnight", 7*60)
val timeNew = PreferenceManager.getDefaultSharedPreferences(context!!).getInt("alarmTimeFromMidnight", 7*60)
val hourOfDayNew = timeNew / 60
val minuteNew = timeNew % 60
val timePicker = TimePickerDialog(context, timePickerOnTimeSetListener, hourOfDayNew, minuteNew, true)
@ -103,8 +81,8 @@ class AlarmFragment : PreferenceFragmentCompat() {
val fullWeek = weekdays.toSet()
val workingWeek = weekdays.toSet().minusElement(weekdays.last()).minusElement(weekdays.elementAt(weekdays.size - 2))
val listOfDays = when (newValue) {
fullWeek -> requireContext().getString(R.string.every_day)
workingWeek -> requireContext().getString(R.string.working_days)
fullWeek -> context!!.getString(R.string.every_day)
workingWeek -> context!!.getString(R.string.working_days)
else -> {
// build ORDERED LIST of days... I don't know why the original one is in shambles!!
val fullWeekOrdered = weekdays
@ -119,12 +97,12 @@ class AlarmFragment : PreferenceFragmentCompat() {
preference?.summary = listOfDays
}
updateDays(alarmDays, PreferenceManager.getDefaultSharedPreferences(requireContext()).getStringSet("alarmDays", setOf()))
updateDays(alarmDays, PreferenceManager.getDefaultSharedPreferences(context).getStringSet("alarmDays", setOf()))
alarmDays?.setOnPreferenceChangeListener { preference, newValue ->
@Suppress("UNCHECKED_CAST")
updateDays(preference as MultiSelectListPreference, newValue as Set<String>)
RadioAlarm.instance.cancelAlarm(requireContext())
RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true, forceDays = newValue)
RadioAlarm.instance.cancelAlarm(context!!)
RadioAlarm.instance.setNextAlarm(context!!, isForce = true, forceDays = newValue)
updateIsWakingUpSummary(isWakingUp, isWakingUp?.isChecked, forceDays = newValue)
true
}
@ -133,9 +111,9 @@ class AlarmFragment : PreferenceFragmentCompat() {
isWakingUp?.setOnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean)
RadioAlarm.instance.setNextAlarm(requireContext(), isForce = true)
RadioAlarm.instance.setNextAlarm(context!!, isForce = true)
else
RadioAlarm.instance.cancelAlarm(requireContext())
RadioAlarm.instance.cancelAlarm(context!!)
timeSet?.isEnabled = newValue
alarmDays?.isEnabled = newValue
snoozeDuration?.isEnabled = newValue
@ -143,10 +121,12 @@ class AlarmFragment : PreferenceFragmentCompat() {
true
}
alarmDays?.isEnabled = isWakingUp?.isChecked ?: false
timeSet?.isEnabled = isWakingUp?.isChecked ?: false
snoozeDuration?.isEnabled = isWakingUp?.isChecked ?: false
}
}

View File

@ -1,25 +1,21 @@
package fr.riff_app.riff.preferences
package fr.forum_thalie.tsumugi.preferences
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.*
import fr.riff_app.riff.R
import fr.riff_app.riff.preferenceStore
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.preferenceStore
class CustomizeFragment : PreferenceFragmentCompat() {
override fun onAttach(context: Context) {
(activity as AppCompatActivity).supportActionBar?.title = context.getString(R.string.customizeAppBehavior)
super.onAttach(context)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.customize_preferences, rootKey)
val snackbarPersistent = preferenceScreen.findPreference<SwitchPreferenceCompat>("snackbarPersistent")
snackbarPersistent!!.summary = if (preferenceStore.getBoolean("snackbarPersistent", false))
snackbarPersistent!!.summary = if (preferenceStore.getBoolean("snackbarPersistent", true))
getString(R.string.snackbarPersistent)
else
getString(R.string.snackbarNonPersistent)
@ -44,25 +40,5 @@ class CustomizeFragment : PreferenceFragmentCompat() {
true
}
val fetchPeriod = preferenceScreen.findPreference<ListPreference>("fetchPeriod")
fetchPeriod?.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
fetchPeriod?.setOnPreferenceChangeListener { _, newValue ->
val builder1 = AlertDialog.Builder(context!!)
if (Integer.parseInt(newValue as String) == 0)
builder1.setMessage(R.string.restart_the_app)
else
builder1.setMessage(R.string.restart_the_app)
builder1.setCancelable(true)
builder1.setPositiveButton("Close" ) { dialog, _ ->
dialog.cancel()
}
val alert11 = builder1.create()
alert11.show()
true
}
}
}

View File

@ -1,18 +1,13 @@
package fr.riff_app.riff.preferences
package fr.forum_thalie.tsumugi.preferences
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.R
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.*
class MainPreferenceFragment : PreferenceFragmentCompat() {
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).supportActionBar?.title = context?.getString(R.string.settings)
}
@SuppressLint("ApplySharedPref")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View File

@ -1,20 +1,13 @@
package fr.riff_app.riff.preferences
package fr.forum_thalie.tsumugi.preferences
import android.content.Context
import android.os.Bundle
import androidx.preference.*
import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.R
import android.text.InputType
import androidx.appcompat.app.AppCompatActivity
import fr.riff_app.riff.alarm.RadioSleeper
import fr.forum_thalie.tsumugi.alarm.RadioSleeper
class SleepFragment : PreferenceFragmentCompat() {
override fun onAttach(context: Context) {
(activity as AppCompatActivity).supportActionBar?.title = context.getString(R.string.setSleepTimer)
super.onAttach(context)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.sleep_preference, rootKey)

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui
package fr.forum_thalie.tsumugi.ui
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.news
package fr.forum_thalie.tsumugi.ui.news
import java.util.*

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.news
package fr.forum_thalie.tsumugi.ui.news
import android.annotation.SuppressLint
import android.app.Activity
@ -20,8 +20,8 @@ import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView
import fr.riff_app.riff.R
import fr.riff_app.riff.newsDisplayDatePattern
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.newsDisplayDatePattern
import java.io.IOException
import java.io.InputStream
import java.net.URL
@ -32,7 +32,7 @@ import java.util.*
// but without Picasso (just fetching image by myself.)
class ImageGetterAsyncTask(
private val context: Context,
private val source: String?,
private val source: String,
private val levelListDrawable: LevelListDrawable
) :
AsyncTask<TextView?, Void?, Bitmap?>() {

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.news
package fr.forum_thalie.tsumugi.ui.news
import android.os.Build
import android.os.Bundle
@ -12,7 +12,7 @@ import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import fr.riff_app.riff.R
import fr.forum_thalie.tsumugi.R
class NewsFragment : Fragment() {
@ -43,9 +43,9 @@ class NewsFragment : Fragment() {
}
newsViewModel.isWebViewLoaded = true
//[REMOVE LOG CALLS]Log.d(tag, "webview created")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "webview created")
} else {
//[REMOVE LOG CALLS]Log.d(tag, "webview already created!?")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "webview already created!?")
}
newsViewModel.root.addOnLayoutChangeListener(orientationLayoutListener)
@ -101,7 +101,7 @@ class NewsFragment : Fragment() {
ViewModelProviders.of(this).get(NewsViewModel::class.java)
newsViewModel.fetch(c = context!!, isPreloading = true)
//[REMOVE LOG CALLS]Log.d(tag, "news fetched onCreate")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "news fetched onCreate")
super.onCreate(savedInstanceState)
}
}

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.news
package fr.forum_thalie.tsumugi.ui.news
import android.content.Context
import android.os.Build
@ -8,9 +8,9 @@ import android.webkit.WebView
import androidx.lifecycle.ViewModel
import androidx.recyclerview.widget.RecyclerView
import com.prof.rssparser.Parser
import fr.riff_app.riff.R
import fr.riff_app.riff.newsDateTimePattern
import fr.riff_app.riff.tag
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.newsDateTimePattern
import fr.forum_thalie.tsumugi.tag
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -43,14 +43,14 @@ class NewsViewModel : ViewModel() {
val maxNumberOfArticles = 5
coroutineScope.launch(Dispatchers.Main) {
//[REMOVE LOG CALLS]Log.d(tag, "launching coroutine")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "launching coroutine")
val parser = Parser()
try {
val articleList = parser.getArticles(urlToScrape)
newsArray.clear()
for (i in 0 until min(articleList.size, maxNumberOfArticles)) {
val item = articleList[i]
//[REMOVE LOG CALLS]Log.d(tag, "i = $i / ${articleList.size}")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "i = $i / ${articleList.size}")
val news = News()
news.title = item.title ?: ""
news.link = item.link ?: urlToScrape
@ -60,7 +60,7 @@ class NewsViewModel : ViewModel() {
val formatter6 = SimpleDateFormat(newsDateTimePattern, Locale.ENGLISH)
val dateString = item.pubDate.toString()
//[REMOVE LOG CALLS]Log.d(tag, "$news --- $dateString")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "$news --- $dateString")
news.date = formatter6.parse(dateString) ?: Date(0)

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.news
package fr.forum_thalie.tsumugi.ui.news
import android.annotation.SuppressLint
import android.content.Intent

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.nowplaying
package fr.forum_thalie.tsumugi.ui.nowplaying
import android.annotation.SuppressLint
import android.content.ClipboardManager
@ -6,6 +6,8 @@ import android.content.Context
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import android.util.TypedValue
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
@ -13,14 +15,16 @@ import android.view.ViewGroup
import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.widget.TextViewCompat
import androidx.lifecycle.Observer
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import fr.riff_app.riff.*
import fr.riff_app.riff.alarm.RadioSleeper
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.playerstore.PlayerStore
import fr.riff_app.riff.playerstore.Song
import fr.forum_thalie.tsumugi.*
import fr.forum_thalie.tsumugi.alarm.RadioSleeper
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.playerstore.Song
import kotlinx.android.synthetic.main.fragment_nowplaying.*
class NowPlayingFragment : Fragment() {
@ -45,12 +49,12 @@ class NowPlayingFragment : Fragment() {
val volumeText: TextView = root.findViewById(R.id.volume_text)
val progressBar: ProgressBar = root.findViewById(R.id.progressBar)
val volumeIconImage : ImageView = root.findViewById(R.id.volume_icon)
val currentProgrammeText: TextView = root.findViewById(R.id.current_programme)
val streamerPictureImageView: ImageView = root.findViewById(R.id.streamerPicture)
// 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)
@ -65,14 +69,15 @@ 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 {
val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song(noConnectionValue) // (it.peekFirst != null ? it.peekFirst : Song() )
val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song("No queue - ") // (it.peekFirst != null ? it.peekFirst : Song() )
songTitleNextText.text = t.title.value
songArtistNextText.text = t.artist.value
})
/*
PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
streamerNameText.text = it
})
@ -87,12 +92,10 @@ class NowPlayingFragment : Fragment() {
songTitleText.text = it
})
Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer {
currentProgrammeText.text = "${requireContext().getString(R.string.current_programme)} $it"
songTitleNextText.text = it
})
PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer {
songArtistText.text = it
})
@ -166,10 +169,10 @@ class NowPlayingFragment : Fragment() {
}
})
seekBarVolume.progress = PlayerStore.instance.volume.value!!
seekBarVolume.setOnSeekBarChangeListener(nowPlayingViewModel.seekBarChangeListener)
progressBar.max = 100
seekBarVolume.setOnSeekBarChangeListener(nowPlayingViewModel.seekBarChangeListener)
seekBarVolume.progress = PlayerStore.instance.volume.value!!
progressBar.max = 1000
progressBar.progress = 0
syncPlayPauseButtonImage(root)
@ -184,10 +187,10 @@ class NowPlayingFragment : Fragment() {
val setClipboardListener: View.OnLongClickListener = View.OnLongClickListener {
val text = PlayerStore.instance.currentSong.artist.value + " - " + PlayerStore.instance.currentSong.title.value
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipboard = context!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = android.content.ClipData.newPlainText("Copied Text", text)
clipboard.setPrimaryClip(clip)
val snackBarLength = if (preferenceStore.getBoolean("snackbarPersistent", false))
val snackBarLength = if (preferenceStore.getBoolean("snackbarPersistent", true))
Snackbar.LENGTH_INDEFINITE
else Snackbar.LENGTH_LONG
val snackBar = Snackbar.make(it, "", snackBarLength)
@ -270,7 +273,7 @@ class NowPlayingFragment : Fragment() {
(viewHeight*100)/viewWidth
else
100
//[REMOVE LOG CALLS]Log.d(tag, "orientation set")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "orientation set")
}
override fun onResume() {

View File

@ -1,11 +1,9 @@
package fr.riff_app.riff.ui.nowplaying
package fr.forum_thalie.tsumugi.ui.nowplaying
import android.widget.SeekBar
import androidx.core.content.edit
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import fr.riff_app.riff.playerstore.PlayerStore
import fr.riff_app.riff.preferenceStore
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
class NowPlayingViewModel: ViewModel() {
@ -23,10 +21,6 @@ class NowPlayingViewModel: ViewModel() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
// updated continuously as the user slides the thumb
PlayerStore.instance.volume.value = progress
preferenceStore.edit {
putInt("volume", progress)
commit()
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {

View File

@ -1,18 +1,18 @@
package fr.riff_app.riff.ui.programme
package fr.forum_thalie.tsumugi.ui.programme
import android.annotation.SuppressLint
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView
import fr.riff_app.riff.R
import fr.riff_app.riff.colorAccent
import fr.riff_app.riff.colorWhited
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.planning.Programme
import fr.riff_app.riff.weekdaysSundayFirst
import fr.forum_thalie.tsumugi.*
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.planning.Programme
import java.util.*
import kotlin.collections.ArrayList
class ProgrammeAdapter(private val dataSet: ArrayList<Programme>, private val day: String
/*,

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.programme
package fr.forum_thalie.tsumugi.ui.programme
import android.os.Bundle
import android.view.LayoutInflater
@ -9,10 +9,10 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.riff_app.riff.R
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.planning.Programme
import fr.riff_app.riff.weekdays
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.planning.Programme
import fr.forum_thalie.tsumugi.weekdays
class ProgrammeDayFragment : Fragment() {
@ -32,13 +32,13 @@ class ProgrammeDayFragment : Fragment() {
viewModel.day = arguments?.getString("day") ?: ""
val root = inflater.inflate(R.layout.fragment_programme_day, container, false)
Planning.instance.programmes.forEach {
if (it.isThisDay(day = weekdays.indexOf(viewModel.day)))
programmeOfTheDay.add(it)
}
viewManager = LinearLayoutManager(context)
viewAdapter = ProgrammeAdapter(programmeOfTheDay, viewModel.day)
viewAdapter =
ProgrammeAdapter(programmeOfTheDay, viewModel.day)
recyclerView = root.findViewById<RecyclerView>(R.id.programme_recycler).apply {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.programme
package fr.forum_thalie.tsumugi.ui.programme
import androidx.lifecycle.ViewModel

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.programme
package fr.forum_thalie.tsumugi.ui.programme
import android.os.Bundle
import android.util.Log
@ -9,10 +9,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import fr.riff_app.riff.R
import fr.riff_app.riff.planning.Planning
import fr.riff_app.riff.ui.APagerAdapter
import fr.riff_app.riff.weekdays
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.planning.Planning
import fr.forum_thalie.tsumugi.ui.APagerAdapter
import fr.forum_thalie.tsumugi.weekdays
import java.util.*
class ProgrammeFragment : Fragment() {
@ -37,11 +37,11 @@ class ProgrammeFragment : Fragment() {
viewPager.adapter = adapter
val todaySundayFirst = Calendar.getInstance(Planning.instance.timeZone).get(Calendar.DAY_OF_WEEK) - 1
viewPager.currentItem = (todaySundayFirst - 1 + 7)%7 // don't do modulos on negative, seems like it's weird
viewPager.currentItem = (todaySundayFirst - 1)%7
val tabLayout : TabLayout = root.findViewById(R.id.dayTabLayout)
tabLayout.setupWithViewPager(viewPager)
//[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created")
return root
}

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.songs
package fr.forum_thalie.tsumugi.ui.songs
import android.os.Bundle
import android.util.Log
@ -9,10 +9,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import fr.riff_app.riff.R
import fr.riff_app.riff.ui.APagerAdapter
import fr.riff_app.riff.ui.programme.ProgrammeDayFragment
import fr.riff_app.riff.ui.songs.queuelp.LastPlayedFragment
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.ui.APagerAdapter
import fr.forum_thalie.tsumugi.ui.programme.ProgrammeDayFragment
import fr.forum_thalie.tsumugi.ui.songs.queuelp.LastPlayedFragment
class SongsFragment : Fragment() {
@ -39,7 +39,7 @@ class SongsFragment : Fragment() {
val tabLayout : TabLayout = root.findViewById(R.id.tabLayout)
tabLayout.setupWithViewPager(viewPager)
//[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created")
return root
}

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.songs.queuelp
package fr.forum_thalie.tsumugi.ui.songs.queuelp
import android.os.Bundle
import android.util.Log
@ -9,8 +9,8 @@ import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.riff_app.riff.R
import fr.riff_app.riff.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
/**
* A simple [Fragment] subclass.
@ -31,7 +31,7 @@ class LastPlayedFragment : Fragment() {
private val queueObserver = Observer<Boolean> {
//[REMOVE LOG CALLS]Log.d(tag, lastPlayedFragmentTag + "queue changed")
//[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(tag, lastPlayedFragmentTag + "queue changed")
viewAdapter.notifyDataSetChanged()
}

View File

@ -1,4 +1,4 @@
package fr.riff_app.riff.ui.songs.queuelp
package fr.forum_thalie.tsumugi.ui.songs.queuelp
import android.annotation.SuppressLint
import android.graphics.Color
@ -8,11 +8,11 @@ import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import fr.riff_app.riff.R
import fr.riff_app.riff.colorBlue
import fr.riff_app.riff.colorWhited
import fr.riff_app.riff.playerstore.Song
import fr.riff_app.riff.tag
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.colorBlue
import fr.forum_thalie.tsumugi.colorWhited
import fr.forum_thalie.tsumugi.playerstore.Song
import fr.forum_thalie.tsumugi.tag
import kotlinx.android.synthetic.main.song_view.view.*
import kotlin.collections.ArrayList

View File

@ -1,21 +0,0 @@
package fr.riff_app.riff
import android.support.v4.media.session.PlaybackStateCompat
import fr.riff_app.riff.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

@ -1,277 +0,0 @@
package fr.riff_app.riff.playerstore
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import androidx.lifecycle.MutableLiveData
import fr.riff_app.riff.*
import fr.riff_app.riff.planning.Planning
import org.json.JSONObject
import java.net.URL
import java.text.ParseException
import java.text.SimpleDateFormat
import java.time.format.TextStyle
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()
val playbackState: MutableLiveData<Int> = MutableLiveData()
val currentTime: MutableLiveData<Long> = MutableLiveData()
val streamerPicture: MutableLiveData<Bitmap> = MutableLiveData()
val streamerName: MutableLiveData<String> = MutableLiveData()
val currentSong : Song = Song()
val currentSongBackup: Song = Song()
val lp : ArrayList<Song> = ArrayList()
val queue : ArrayList<Song> = ArrayList()
val isQueueUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isLpUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isMuted : MutableLiveData<Boolean> = MutableLiveData()
val listenersCount: MutableLiveData<Int> = MutableLiveData()
var latencyCompensator : Long = 0
var isInitialized: Boolean = false
var isStreamDown: Boolean = false
init {
playbackState.value = PlaybackStateCompat.STATE_STOPPED
isPlaying.value = false
isServiceStarted.value = false
streamerName.value = ""
volume.value = preferenceStore.getInt("volume", 100)
currentTime.value = System.currentTimeMillis()
isQueueUpdated.value = false
isLpUpdated.value = false
isMuted.value = false
currentSong.setTitleArtist(noConnectionValue)
currentSongBackup.setTitleArtist(noConnectionValue)
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 z", Locale.getDefault())
try {
val t: Date? = dateFormat.parse("$s ${Planning.instance.timeZone.getDisplayName(Planning.instance.timeZone.useDaylightTime(), TimeZone.SHORT)}")
//[REMOVE LOG CALLS]Log.d(tag, "date: $s -> $t")
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")
val s = extractSong(resMain)
if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty()
|| currentSong.title.value == noConnectionValue)
currentSong.setTitleArtist("${s.artist.value} - ${s.title.value}")
val starts = s.startTime.value
val ends = s.stopTime.value
if (currentSong.startTime.value != starts)
currentSong.startTime.value = starts
currentSong.stopTime.value = ends
val apiTime = getTimestamp(res.getJSONObject("station").getString("schedulerTime"))
// 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 = apiTime - (currentSong.startTime.value!!)
//[REMOVE LOG CALLS]Log.d(tag, "latency compensator set to ${(latencyCompensator).toFloat() / 1000} s")
}
currentTime.value = apiTime - (latencyCompensator)
/*
val listeners = resMain.getInt("listeners")
listenersCount.value = listeners
//[REMOVE LOG CALLS]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)
fetchLastRequest()
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("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
}
// ##################################################
// ############## QUEUE / LP FUNCTIONS ##############
// ##################################################
fun updateQueue() {
//[REMOVE LOG CALLS]Log.d(tag, queue.toString())
fetchLastRequest()
}
fun updateLp() {
// note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
//if (lp.isNotEmpty()){
val n = Song()
n.copy(currentSongBackup)
if (n != Song(noConnectionValue) && n != Song(streamDownValue))
lp.add(0, n)
currentSongBackup.copy(currentSong)
isLpUpdated.value = true
//[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + lp.toString())
//}
}
private fun fetchLastRequest()
{
isQueueUpdated.value = false
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")) {
val queueJSON =
resMain.getJSONObject("next")
val t = extractSong(queueJSON)
if (queue.isNotEmpty() && (t == queue.last() || t == currentSong) && isQueueUpdated.value == false)
{
//[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + "Song already in there: $t\nQueue:$queue")
Async(sleepScrape, post)
} else {
if (queue.isNotEmpty())
queue.remove(queue.first())
queue.add(queue.size, t)
//[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + "added last queue song: $t")
isQueueUpdated.value = true
return // FUUUCK IT WAS CALLING THE ASYNC ONE MORE TIME AFTERWARDS !?
}
}
}
}
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 #################
// ##################################################
fun initPicture(c: Context) {
streamerPicture.value = BitmapFactory.decodeResource(c.resources,
R.drawable.logo_roundsquare
)
}
private val playerStoreTag = "====PlayerStore===="
companion object {
val instance by lazy {
PlayerStore()
}
}
}

View File

@ -1,129 +0,0 @@
package fr.riff_app.riff.preferences
import android.content.Context
import android.content.Intent
import android.media.AudioManager
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
import fr.riff_app.riff.Actions
import fr.riff_app.riff.R
import fr.riff_app.riff.RadioService
import fr.riff_app.riff.playerstore.PlayerStore
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.max
import kotlin.math.min
class AlarmAdjustVolumeFragment : PreferenceFragmentCompat() {
override fun onAttach(context: Context) {
(activity as AppCompatActivity).supportActionBar?.title = context.getString(R.string.test_alarm_volume)
super.onAttach(context)
}
// get previous state: if it's playing, we'll resume playing as multimedia; if it was stopped, we'll stop
private var isPlayingMultimedia: Boolean = PlayerStore.instance.isPlaying.value ?: false
override fun onStop() {
if (isPlayingMultimedia)
{
actionOnService(Actions.PLAY)
} else {
actionOnService(Actions.PAUSE)
}
PlayerStore.instance.volume.value = PreferenceManager.getDefaultSharedPreferences(requireContext()).getInt("volume", 100)
super.onStop()
}
override fun onResume() {
isPlayingMultimedia = PlayerStore.instance.isPlaying.value ?: false
// start as alarm
actionOnService(Actions.PLAY_OR_FALLBACK)
super.onResume()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fun <T> debounce(delayMs: Long = 500L,
coroutineContext: CoroutineContext,
f: (T) -> Unit): (T) -> Unit {
var debounceJob: Job? = null
return { param: T ->
if (debounceJob?.isCompleted != false) {
debounceJob = CoroutineScope(coroutineContext).launch {
delay(delayMs)
f(param)
}
}
}
}
val adjustAlarmVolume: (Int) -> Unit = debounce<Int>(50, GlobalScope.coroutineContext) {
android.util.Log.d(tag, "button $it pushed")
val keyCode = it
val audioManager = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.apply {
val currentVolume = this.getStreamVolume(AudioManager.STREAM_ALARM)
val minVolume = 0 // audioManager.getStreamMinVolume(AudioManager.STREAM_ALARM) <- require API28+
val maxVolume = this.getStreamMaxVolume(AudioManager.STREAM_ALARM)
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
this.setStreamVolume(AudioManager.STREAM_ALARM, max(currentVolume - 1, minVolume), AudioManager.FLAG_SHOW_UI)
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP){
this.setStreamVolume(AudioManager.STREAM_ALARM, min(currentVolume + 1, maxVolume), AudioManager.FLAG_SHOW_UI)
} else {
}
}
}
view.isFocusableInTouchMode = true;
view.requestFocus();
view.setOnKeyListener { _, i, event ->
if (i == KeyEvent.KEYCODE_VOLUME_DOWN || i == KeyEvent.KEYCODE_VOLUME_UP) {
adjustAlarmVolume(i)
true
} else {
false
}
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.alarm_adjust_volume_preferences, rootKey)
val alarmVolumeSeekBar = findPreference<SeekBarPreference>("alarmVolume")
alarmVolumeSeekBar?.apply {
min = 0
max = 100
updatesContinuously = true
setOnPreferenceChangeListener { _, newValue ->
actionOnService(Actions.VOLUME, newValue as Int)
true
}
}
}
private fun actionOnService(a: Actions, v: Int = 0)
{
val i = Intent(requireContext(), RadioService::class.java)
i.putExtra("action", a.name)
i.putExtra("value", v)
//[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}")
requireContext().startService(i)
}
}

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 B

View File

@ -1,11 +1,11 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@color/black"/>
<item android:drawable="@color/colorAccent"/>
<!-- Your product logo - 144dp color version of your app icon -->
<item
android:drawable="@mipmap/ic_launcher"
android:drawable="@drawable/ic_tsumugi_lineart"
android:gravity="center"
android:height="144dp"
android:width="144dp"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
>
<path
android:strokeWidth="1"
android:pathData="M-22.4,-18.02h249.46v189.63h-249.46z"
android:fillColor="#000000"
android:strokeColor="#ffffff"/>
android:fillColor="@color/colorAccent"
android:pathData="M0,0h108v108h-108z" />
</vector>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,13 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@color/black"/>
<item android:drawable="@color/colorPrimaryDark"/>
<!-- Your product logo - 144dp color version of your app icon -->
<item
>
<bitmap
android:src="@mipmap/ic_launcher"
android:src="@drawable/splash_logo"
android:gravity="center"
/>
</item>

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -42,7 +42,7 @@
android:id="@+id/logo_persistent"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
app:srcCompat="@mipmap/ic_launcher"
app:srcCompat="@drawable/logo_roundsquare"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:padding="12dp"

View File

@ -36,10 +36,10 @@
android:contentDescription="dj-image"
android:scaleType="fitStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/scrollViewMetadataNext"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/imageGuideline"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@mipmap/ic_launcher"
app:srcCompat="@drawable/logo_roundsquare"
android:layout_margin="8dp"
tools:ignore="HardcodedText" />
<!-- app:layout_constraintBottom_toTopOf="@id/imageLeftGuideline" -->
@ -49,7 +49,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.71" />
app:layout_constraintGuide_percent="0.68" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/imageLeftGuideline"
@ -93,6 +93,13 @@
android:visibility="gone"
/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topInfoGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.95" />
<TextView
android:id="@+id/sleepInfo"
android:layout_width="0dp"
@ -140,7 +147,7 @@
<ImageView
android:id="@+id/volume_icon"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_height="24dp"
android:contentDescription="@string/volume"
android:src="@drawable/ic_volume_high"
android:textSize="12sp"
@ -155,12 +162,10 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:fillViewport="true"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toTopOf="@id/scrollProgramme"
app:layout_constraintBottom_toTopOf="@id/topInfoGuideline"
app:layout_constraintEnd_toStartOf="@id/streamerPicture"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/seek_bar_volume"
>
app:layout_constraintTop_toBottomOf="@id/seek_bar_volume">
<androidx.constraintlayout.widget.ConstraintLayout
@ -179,8 +184,8 @@
android:layout_gravity="top"
android:text="@string/up_next"
android:textAlignment="center"
android:textColor="@color/whited5"
android:layout_marginTop="0dp"
android:textColor="@color/whited3"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -190,94 +195,32 @@
android:id="@+id/text_song_artist_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom|center_horizontal"
android:gravity="bottom"
android:text=""
android:textAlignment="center"
android:textColor="@color/whited3"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/upNext"
android:visibility="visible"/>
android:visibility="gone"/>
<TextView
android:id="@+id/text_song_title_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom|center_horizontal"
android:gravity="bottom"
android:text=""
android:textAlignment="center"
android:textColor="@color/whited"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_song_artist_next"
android:visibility="visible" />
app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<ScrollView
android:id="@+id/scrollProgramme"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fillViewport="true"
android:layout_marginBottom="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/scrollViewMetadataNext"
app:layout_constraintBottom_toBottomOf="@id/topInfoGuideline"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="visible"
>
<TextView
android:id="@+id/current_programme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top|center_horizontal"
android:text="@string/current_programme"
android:textAlignment="center"
android:textColor="@color/whited3"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="visible" />
<TextView
android:id="@+id/text_current_programme"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:layout_marginStart="8sp"
android:layout_marginLeft="8sp"
android:gravity="start|center_horizontal"
android:textAlignment="textStart"
android:textColor="@color/whited"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@id/current_programme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topInfoGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.97" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
@ -321,7 +264,7 @@
android:id="@+id/text_song_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom|center_horizontal"
android:gravity="bottom"
android:text=""
android:textAlignment="center"
android:textColor="@color/whited"
@ -333,7 +276,7 @@
android:id="@+id/text_song_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom|center_horizontal"
android:gravity="bottom"
android:text=""
android:textAlignment="center"
android:textColor="@color/whited3"
@ -362,7 +305,7 @@
android:progressDrawable="@drawable/progress_bar_progress"
app:layout_constraintBottom_toTopOf="@id/play_pause"
tools:layout_editor_absoluteX="0dp"
android:visibility="visible"/>
android:visibility="gone"/>
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
<TextView
@ -375,7 +318,7 @@
android:textAlignment="textEnd"
app:layout_constraintEnd_toEndOf="@id/progressBar"
app:layout_constraintTop_toBottomOf="@id/progressBar"
android:visibility="visible"/>
android:visibility="gone"/>
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
<TextView
@ -388,7 +331,7 @@
android:textAlignment="textStart"
app:layout_constraintStart_toStartOf="@id/progressBar"
app:layout_constraintTop_toBottomOf="@id/progressBar"
android:visibility="visible"/>
android:visibility="gone"/>
@ -403,7 +346,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.63" />
app:layout_constraintGuide_percent="0.58" />
<ImageButton
@ -438,7 +381,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.38" />
app:layout_constraintGuide_percent="0.33" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/splitHorizontalLayout"

View File

@ -15,22 +15,23 @@
app:showAsAction="ifRoom"/>
<!--
<item
android:id="@+id/action_refresh"
android:title="@string/action_refresh"
app:showAsAction="never"/>
-->
<!--
<item
android:id="@+id/action_bug_submit"
android:title="@string/action_bug_submit"
app:showAsAction="never"/>
-->
<item
android:id="@+id/action_refresh"
android:title="@string/action_refresh"
app:showAsAction="never"/>
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:icon="@drawable/ic_settings"
app:showAsAction="never"/>
app:showAsAction="ifRoom"/>
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Some files were not shown because too many files have changed in this diff Show More