22 Commits

Author SHA1 Message Date
Michachatz f7ddc30127 Merge pull request #72 from Michatec/renovate/freedroidwarn
fix(deps): update dependency com.github.woheller69:freedroidwarn to v1.13
2026-05-11 07:50:32 +02:00
renovate[bot] bc0eb60e04 fix(deps): update dependency com.github.woheller69:freedroidwarn to v1.13 2026-05-10 11:51:08 +00:00
Michatec dcae7bafb5 feat: apply custom theme to ExpandedControllerActivity 2026-05-07 13:49:37 +02:00
Michatec cf3fd0bc56 refactor(media): move MEDIA_BUTTON action to MediaSessionService 2026-05-07 13:43:58 +02:00
Michatec 7d6b0fe136 feat: implement station navigation and update to SDK 37
- Implement `seekToNext` and `seekToPrevious` in `PlayerService` to allow switching between radio stations.
- Update `compileSdk` and `targetSdk` to 37 and bump version to 14.6.
- Modify `PlayerService` to remain active while paused to improve playback resumption.
- Modernize media resumption logic using Media3 `isRecent` check instead of legacy extras.
- Refactor `DefaultRenderersFactory` to use non-nullable `AudioSink` return types.
- General code cleanup using KTX extensions (e.g., `View.isEmpty()`) and Kotlin property access syntax.
2026-05-07 13:40:35 +02:00
Michachatz 0faeea7631 Merge pull request #70 from Michatec/renovate/agp
chore(deps): update agp to v9.2.1
2026-05-07 12:18:23 +02:00
Michachatz c45adf07c8 Merge pull request #71 from Michatec/renovate/media
fix(deps): update dependency androidx.media:media to v1.8.0
2026-05-07 12:18:08 +02:00
renovate[bot] acd1b067b3 fix(deps): update dependency androidx.media:media to v1.8.0 2026-05-06 20:08:16 +00:00
renovate[bot] e3a325f568 chore(deps): update agp to v9.2.1 2026-05-05 17:45:30 +00:00
Michachatz b537df898b Merge pull request #69 from Michatec/renovate/freedroidwarn
fix(deps): update dependency com.github.woheller69:freedroidwarn to v1.12
2026-05-01 13:50:50 +02:00
Michachatz 61675601f3 Merge pull request #68 from Michatec/renovate/gradle-9.x
chore(deps): update gradle to v9.5.0
2026-05-01 13:48:43 +02:00
renovate[bot] 6dc5c47b86 fix(deps): update dependency com.github.woheller69:freedroidwarn to v1.12 2026-04-30 21:46:44 +00:00
renovate[bot] c40ad0cc4a chore(deps): update gradle to v9.5.0 2026-04-28 15:17:44 +00:00
Michachatz 066206c6dc Merge pull request #67 from Michatec/renovate/gson
fix(deps): update dependency com.google.code.gson:gson to v2.14.0
2026-04-26 14:48:57 +02:00
renovate[bot] e8e66c24ef fix(deps): update dependency com.google.code.gson:gson to v2.14.0 2026-04-23 21:10:57 +00:00
Michachatz 56647e7f02 Merge pull request #66 from Michatec/renovate/kotlin-monorepo
chore(deps): update dependency org.jetbrains.kotlin.android to v2.3.21
2026-04-23 17:57:41 +02:00
renovate[bot] a7e0efc913 chore(deps): update dependency org.jetbrains.kotlin.android to v2.3.21 2026-04-23 09:56:51 +00:00
Michachatz 0569bed475 Merge pull request #65 from Michatec/renovate/navigation
fix(deps): update navigation to v2.9.8
2026-04-23 11:52:21 +02:00
renovate[bot] 6e3b187b56 fix(deps): update navigation to v2.9.8 2026-04-22 21:41:22 +00:00
Michachatz c8c0d1783b Merge pull request #64 from Michatec/renovate/agp
chore(deps): update agp to v9.2.0
2026-04-22 06:25:14 +02:00
Michachatz 4974514a81 refactor(ui): Change from normal switch to marquee switches 2026-04-22 06:22:31 +02:00
renovate[bot] afd34c6485 chore(deps): update agp to v9.2.0 2026-04-21 20:38:52 +00:00
14 changed files with 78 additions and 66 deletions
+4 -4
View File
@@ -5,14 +5,14 @@ plugins {
android {
namespace = "com.michatec.radio"
compileSdk = 36
compileSdk = 37
defaultConfig {
applicationId = "com.michatec.radio"
minSdk = 28
targetSdk = 36
versionCode = 145
versionName = "14.5"
targetSdk = 37
versionCode = 146
versionName = "14.6"
}
compileOptions {
+2 -8
View File
@@ -48,6 +48,7 @@
<activity
android:name=".ExpandedControllerActivity"
android:exported="false"
android:theme="@style/CustomCastExpandedControllerStyle"
android:launchMode="singleTask" />
<!-- Main activity for radio station playback on phone and TV -->
@@ -136,6 +137,7 @@
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
<action android:name="android.media.browse.MediaBrowserService" />
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="com.michatec.radio.action.START_PLAYER_SERVICE" />
</intent-filter>
</service>
@@ -148,14 +150,6 @@
</intent-filter>
</receiver>
<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
@@ -120,7 +120,7 @@ class AddStationFragment : Fragment(),
if (searchEditText != null) {
searchEditText.requestFocus()
val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT)
imm.showSoftInput(searchEditText, 0)
}
}
}
@@ -9,7 +9,6 @@ import android.os.Bundle
import android.os.CountDownTimer
import android.util.Log
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT
import androidx.media3.cast.CastPlayer
import androidx.media3.common.*
import androidx.media3.common.util.UnstableApi
@@ -29,6 +28,7 @@ import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.michatec.radio.core.Collection
import com.michatec.radio.core.Station
import com.michatec.radio.helpers.*
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Main
@@ -132,7 +132,7 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
context: Context,
enableFloatOutput: Boolean,
enableAudioTrackPlaybackParams: Boolean
): AudioSink? {
): AudioSink {
return DefaultAudioSink.Builder(context)
.setAudioProcessors(arrayOf(nativeAudioProcessor))
.build()
@@ -170,6 +170,14 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
override fun getDuration(): Long {
return C.TIME_UNSET // this will hide progress bar for HLS stations in the notification
}
override fun seekToNext() {
playNextStation()
}
override fun seekToPrevious() {
playPreviousStation()
}
}
player.addListener(playerListener)
}
@@ -347,6 +355,37 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
}
/* Switches to the next radio station in collection */
private fun playNextStation() {
val currentMediaId = player.currentMediaItem?.mediaId ?: PreferencesHelper.loadLastPlayedStationUuid()
val currentPosition = CollectionHelper.getStationPosition(collection, currentMediaId)
if (currentPosition != -1) {
val nextPosition = if (currentPosition < collection.stations.size - 1) currentPosition + 1 else 0
playStation(collection.stations[nextPosition])
}
}
/* Switches to the previous radio station in collection */
private fun playPreviousStation() {
val currentMediaId = player.currentMediaItem?.mediaId ?: PreferencesHelper.loadLastPlayedStationUuid()
val currentPosition = CollectionHelper.getStationPosition(collection, currentMediaId)
if (currentPosition != -1) {
val previousPosition = if (currentPosition > 0) currentPosition - 1 else collection.stations.size - 1
playStation(collection.stations[previousPosition])
}
}
/* Starts playback of a radio station */
private fun playStation(station: Station) {
val mediaItem = CollectionHelper.buildMediaItem(this, station)
player.setMediaItem(mediaItem)
player.prepare()
player.play()
}
/*
* Custom MediaSession Callback that handles player commands
*/
@@ -407,8 +446,8 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
browser: MediaSession.ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
return if (params?.extras?.containsKey(EXTRA_RECENT) == true) {
// special case: system requested media resumption via EXTRA_RECENT
return if (params?.isRecent == true) {
// special case: system requested media resumption via isRecent
playLastStation = true
Futures.immediateFuture(LibraryResult.ofItem(CollectionHelper.getRecent(this@PlayerService, collection), params))
} else {
@@ -552,8 +591,7 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
stopSelf()
}
Player.STATE_READY -> {
// Playback is paused. For radio, we can stop the service to remove the notification.
stopSelf()
// Playback is paused. For radio, we keep the service running to allow resumption from headphones.
}
Player.STATE_BUFFERING -> {
// DO NOT stop the service while buffering (especially important for Cast)
@@ -562,17 +600,6 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
}
}
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
super.onPlayWhenReadyChanged(playWhenReady, reason)
if (!playWhenReady) {
// Only stop if not buffering and not ready to play (i.e. truly stopped/paused)
if (player.playbackState != Player.STATE_BUFFERING) {
stopSelf()
}
}
}
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
Log.d(TAG, "PlayerError occurred: ${error.errorCodeName}")
@@ -149,7 +149,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
// set up "Buffer Size" preference
val preferenceBufferSize = SwitchPreferenceCompat(activity as Context)
val preferenceBufferSize = MarqueeSwitchPreference(context)
preferenceBufferSize.title = getString(R.string.pref_buffer_size_title)
preferenceBufferSize.setIcon(R.drawable.ic_network_check_24dp)
preferenceBufferSize.key = Keys.PREF_LARGE_BUFFER_SIZE
@@ -159,7 +159,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
// set up "Edit Stream Address" preference
val preferenceEnableEditingStreamUri = SwitchPreferenceCompat(activity as Context)
val preferenceEnableEditingStreamUri = MarqueeSwitchPreference(context)
preferenceEnableEditingStreamUri.title = getString(R.string.pref_edit_station_stream_title)
preferenceEnableEditingStreamUri.setIcon(R.drawable.ic_music_note_24dp)
preferenceEnableEditingStreamUri.key = Keys.PREF_EDIT_STREAMS_URIS
@@ -174,7 +174,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
// set up "Edit Stations" preference
val preferenceEnableEditingGeneral = SwitchPreferenceCompat(activity as Context)
val preferenceEnableEditingGeneral = MarqueeSwitchPreference(context)
preferenceEnableEditingGeneral.title = getString(R.string.pref_edit_station_title)
preferenceEnableEditingGeneral.setIcon(R.drawable.ic_edit_24dp)
preferenceEnableEditingGeneral.key = Keys.PREF_EDIT_STATIONS
@@ -11,6 +11,7 @@ import android.widget.FrameLayout
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.michatec.radio.R
import androidx.core.view.isEmpty
class ExtrasHelper {
companion object {
@@ -69,7 +70,7 @@ class ExtrasHelper {
if (currentParent != container) {
currentParent?.removeView(visualizerView)
// If we injected into a standard preference, don't clear everything, just add
if (container is FrameLayout || container.childCount == 0) {
if (container is FrameLayout || container.isEmpty()) {
container.removeAllViews()
}
container.addView(visualizerView)
@@ -2,7 +2,6 @@ package com.michatec.radio.helpers
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.util.Log
import com.michatec.radio.R
import java.util.Locale
@@ -16,7 +16,7 @@ class MarqueeSwitchPreference(context: Context) : SwitchPreferenceCompat(context
val title = holder.findViewById(android.R.id.title) as? TextView
title?.apply {
ellipsize = TextUtils.TruncateAt.MARQUEE
setSingleLine(true)
isSingleLine = true
marqueeRepeatLimit = -1 // Repeat indefinitely
isSelected = true // Required for marquee to start
setHorizontallyScrolling(true)
@@ -171,7 +171,7 @@ class SearchResultAdapter(
context: Context,
enableFloatOutput: Boolean,
enableAudioTrackPlaybackParams: Boolean
): AudioSink? {
): AudioSink {
return DefaultAudioSink.Builder(context)
.setAudioProcessors(arrayOf(nativeAudioProcessor))
.build()
+6 -6
View File
@@ -1,16 +1,16 @@
[versions]
activityKtx = "1.13.0"
agp = "9.1.1"
agp = "9.2.1"
coreKtx = "1.18.0"
freedroidwarn = "V1.11"
gson = "2.13.2"
kotlin = "2.3.20"
freedroidwarn = "V1.13"
gson = "2.14.0"
kotlin = "2.3.21"
leanback = "1.2.0"
material = "1.13.0"
material3 = "1.4.0"
media = "1.7.1"
media = "1.8.0"
media3 = "1.10.0"
navigation = "2.9.7"
navigation = "2.9.8"
paletteKtx = "1.0.0"
preferenceKtx = "1.2.1"
volley = "1.2.1"
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+1 -1
View File
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
Vendored
+10 -21
View File
@@ -23,8 +23,8 @@
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,7 +65,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@@ -73,21 +73,10 @@ goto fail
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%