Skip to content

Commit dc64fab

Browse files
committed
feat: update state collection to use collectAsStateWithLifecycle for better lifecycle management, force to kill app in service
1 parent 2043eef commit dc64fab

30 files changed

Lines changed: 178 additions & 130 deletions

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ A FOSS YouTube Music client for Android with many features from<br>Spotify, Spon
3838
3939
> **Warning**
4040
> This app is in the beta stage, so it may have many bugs and make it crash. If you find any bugs,
41-
> please create an issue or contact me via email or Discord server.
41+
> please create an issue or contact me via email or Discord server.
4242
4343
## Screenshots
4444
<p align="center">
@@ -96,7 +96,7 @@ sometimes, some songs or videos get the wrong lyrics
9696
## Developer/Team
9797
- [maxrave-dev](https://github.com/maxrave-dev/SimpMusic): Founder/Developer/Designer
9898
- [Owen Connor](https://github.com/owencz1998): Discord Server Admin.
99-
- [Ilian Delagrange](https://github.com/ilianoKokoro): Discord Server Admin.
99+
- [ilianoKokoro](https://github.com/ilianoKokoro): Discord Server Admin.
100100

101101
We're looking for more contributors, all contributions are welcome!
102102
See our [CODE OF CONDUCT](https://github.com/maxrave-dev/SimpMusic/blob/jetpack_compose/CODE_OF_CONDUCT.md)
@@ -144,3 +144,5 @@ Crowdin and Sentry both have a free enterprise plan for Open-source projects. Fo
144144

145145
Check out the Vercel open-source program:
146146
- https://vercel.com/open-source-program
147+
148+
*This project is a part of SimpMusic.org Open-source project by me [maxrave-dev](https://github.com/maxrave-dev)*
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
package com.maxrave.simpmusic.data.db.entities
22

3+
import android.os.Parcelable
34
import androidx.room.Entity
45
import androidx.room.PrimaryKey
56
import com.maxrave.simpmusic.data.type.ArtistType
67
import com.maxrave.simpmusic.data.type.RecentlyType
8+
import kotlinx.parcelize.Parcelize
79
import java.time.LocalDateTime
810

911
@Entity(tableName = "artist")
12+
@Parcelize
1013
data class ArtistEntity(
1114
@PrimaryKey(autoGenerate = false)
1215
val channelId: String,
1316
val name: String,
1417
val thumbnails: String?,
1518
val followed: Boolean = false,
1619
val inLibrary: LocalDateTime = LocalDateTime.now(),
17-
) : RecentlyType, ArtistType {
20+
) : RecentlyType,
21+
ArtistType,
22+
Parcelable {
1823
override fun objectType() = RecentlyType.Type.ARTIST
1924
}

app/src/main/java/com/maxrave/simpmusic/di/MediaServiceModule.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import androidx.media3.exoplayer.audio.AudioSink
2929
import androidx.media3.exoplayer.audio.DefaultAudioSink
3030
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor
3131
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
32-
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager
3332
import androidx.media3.exoplayer.util.EventLogger
3433
import androidx.media3.extractor.ExtractorsFactory
3534
import androidx.media3.extractor.mkv.MatroskaExtractor
@@ -39,7 +38,6 @@ import com.maxrave.simpmusic.common.Config.CANVAS_CACHE
3938
import com.maxrave.simpmusic.common.Config.DOWNLOAD_CACHE
4039
import com.maxrave.simpmusic.common.Config.MAIN_PLAYER
4140
import com.maxrave.simpmusic.common.Config.PLAYER_CACHE
42-
import com.maxrave.simpmusic.common.Config.SECONDARY_PLAYER
4341
import com.maxrave.simpmusic.common.Config.SERVICE_SCOPE
4442
import com.maxrave.simpmusic.data.dataStore.DataStoreManager
4543
import com.maxrave.simpmusic.data.repository.MainRepository
@@ -53,7 +51,7 @@ import kotlinx.coroutines.Dispatchers
5351
import kotlinx.coroutines.SupervisorJob
5452
import kotlinx.coroutines.flow.first
5553
import kotlinx.coroutines.flow.firstOrNull
56-
import kotlinx.coroutines.flow.singleOrNull
54+
import kotlinx.coroutines.flow.lastOrNull
5755
import kotlinx.coroutines.launch
5856
import kotlinx.coroutines.runBlocking
5957
import okhttp3.OkHttpClient
@@ -254,7 +252,7 @@ private fun provideResolvingDataSourceFactory(
254252
.getStream(
255253
id,
256254
true,
257-
).singleOrNull()
255+
).lastOrNull()
258256
?.let {
259257
Log.d("Stream", it)
260258
Log.w("Stream", "Video")
@@ -277,7 +275,7 @@ private fun provideResolvingDataSourceFactory(
277275
.getStream(
278276
mediaId,
279277
isVideo = false,
280-
).singleOrNull()
278+
).lastOrNull()
281279
?.let {
282280
Log.d("Stream", it)
283281
Log.w("Stream", "Audio")

app/src/main/java/com/maxrave/simpmusic/service/SimpleMediaService.kt

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import android.content.Intent
77
import android.os.Binder
88
import android.os.IBinder
99
import android.util.Log
10-
import androidx.media3.common.Player
1110
import androidx.media3.common.util.UnstableApi
1211
import androidx.media3.exoplayer.ExoPlayer
1312
import androidx.media3.session.DefaultMediaNotificationProvider
@@ -19,13 +18,16 @@ import com.google.common.util.concurrent.MoreExecutors
1918
import com.maxrave.simpmusic.R
2019
import com.maxrave.simpmusic.common.Config.MAIN_PLAYER
2120
import com.maxrave.simpmusic.common.MEDIA_NOTIFICATION
21+
import com.maxrave.simpmusic.di.mediaServiceModule
2222
import com.maxrave.simpmusic.service.test.CoilBitmapLoader
2323
import com.maxrave.simpmusic.ui.MainActivity
2424
import com.maxrave.simpmusic.ui.widget.BasicWidget
2525
import kotlinx.coroutines.runBlocking
2626
import org.koin.core.component.KoinComponent
2727
import org.koin.core.component.inject
28+
import org.koin.core.context.loadKoinModules
2829
import org.koin.core.qualifier.named
30+
import kotlin.system.exitProcess
2931

3032
@UnstableApi
3133
class SimpleMediaService :
@@ -42,25 +44,20 @@ class SimpleMediaService :
4244

4345
private val binder = MusicBinder()
4446

45-
inner class MusicBinder : Binder()
47+
inner class MusicBinder : Binder() {
48+
val service: SimpleMediaService
49+
get() = this@SimpleMediaService
50+
}
4651

4752
override fun onBind(intent: Intent?): IBinder {
48-
super.onBind(intent)
4953
Log.w("Service", "Simple Media Service Bound")
50-
return binder
51-
}
52-
53-
override fun onUnbind(intent: Intent?): Boolean {
54-
if (simpleMediaServiceHandler.shouldReleaseOnTaskRemoved()) {
55-
release()
56-
}
57-
Log.w("Service", "Simple Media Service Unbound")
58-
return super.onUnbind(intent)
54+
return super.onBind(intent) ?: binder
5955
}
6056

6157
@UnstableApi
6258
override fun onCreate() {
6359
super.onCreate()
60+
loadKoinModules(mediaServiceModule)
6461
Log.w("Service", "Simple Media Service Created")
6562
setMediaNotificationProvider(
6663
DefaultMediaNotificationProvider(
@@ -132,24 +129,20 @@ class SimpleMediaService :
132129
Log.w("Service", "Starting release process")
133130
runBlocking {
134131
try {
135-
// Release handler first (contains coroutines and jobs)
136-
simpleMediaServiceHandler.release()
137-
138132
// Release MediaSession and Player
139133
mediaSession?.run {
134+
this.player.pause()
135+
this.player.playWhenReady = false
136+
this.player.release()
140137
this.release()
141-
if (this.player.playbackState != Player.STATE_IDLE) {
142-
this.player.seekTo(0)
143-
this.player.playWhenReady = false
144-
this.player.stop()
145-
}
146138
}
139+
// Release handler first (contains coroutines and jobs)
140+
simpleMediaServiceHandler.release()
147141
mediaSession = null
148142
Log.w("Service", "Simple Media Service Released")
149143
} catch (e: Exception) {
150144
Log.e("Service", "Error during release", e)
151145
}
152-
stopSelf()
153146
}
154147
}
155148

@@ -170,6 +163,7 @@ class SimpleMediaService :
170163
if (simpleMediaServiceHandler.shouldReleaseOnTaskRemoved()) {
171164
release()
172165
super.onTaskRemoved(rootIntent)
166+
exitProcess(0)
173167
}
174168
}
175169

app/src/main/java/com/maxrave/simpmusic/service/SimpleMediaServiceHandler.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,8 +1530,6 @@ class SimpleMediaServiceHandler(
15301530
mayBeSavePlaybackState()
15311531

15321532
// Stop and release player
1533-
player.stop()
1534-
player.playWhenReady = false
15351533
player.removeListener(this)
15361534

15371535
// Release audio effects
@@ -1581,9 +1579,6 @@ class SimpleMediaServiceHandler(
15811579
// Cancel coroutine scope
15821580
coroutineScope.cancel()
15831581

1584-
// Clear callbacks
1585-
setNotificationLayout = null
1586-
15871582
Log.w("ServiceHandler", "Handler released successfully. Scope active: ${coroutineScope.isActive}")
15881583
} catch (e: Exception) {
15891584
Log.e("ServiceHandler", "Error during release", e)

app/src/main/java/com/maxrave/simpmusic/ui/MainActivity.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import androidx.compose.material3.Scaffold
3232
import androidx.compose.material3.Text
3333
import androidx.compose.material3.TextButton
3434
import androidx.compose.runtime.LaunchedEffect
35-
import androidx.compose.runtime.collectAsState
3635
import androidx.compose.runtime.getValue
3736
import androidx.compose.runtime.mutableStateOf
3837
import androidx.compose.runtime.saveable.rememberSaveable
@@ -106,6 +105,18 @@ class MainActivity : AppCompatActivity() {
106105
}
107106
}
108107

108+
override fun onStart() {
109+
super.onStart()
110+
startMusicService()
111+
}
112+
113+
override fun onStop() {
114+
super.onStop()
115+
if (shouldUnbind) {
116+
unbindService(serviceConnection)
117+
}
118+
}
119+
109120
override fun onNewIntent(intent: Intent) {
110121
super.onNewIntent(intent)
111122
Log.d("MainActivity", "onNewIntent: $intent")
@@ -215,9 +226,9 @@ class MainActivity : AppCompatActivity() {
215226
val navController = rememberNavController()
216227

217228
val sleepTimerState by viewModel.sleepTimerState.collectAsStateWithLifecycle()
218-
val nowPlayingData by viewModel.nowPlayingState.collectAsState()
219-
val githubResponse by viewModel.githubResponse.collectAsState()
220-
val intent by viewModel.intent.collectAsState()
229+
val nowPlayingData by viewModel.nowPlayingState.collectAsStateWithLifecycle()
230+
val githubResponse by viewModel.githubResponse.collectAsStateWithLifecycle()
231+
val intent by viewModel.intent.collectAsStateWithLifecycle()
221232

222233
val isTranslucentBottomBar by viewModel.getTranslucentBottomBar().collectAsStateWithLifecycle(DataStoreManager.FALSE)
223234
// MiniPlayer visibility logic
@@ -551,12 +562,12 @@ class MainActivity : AppCompatActivity() {
551562
Log.w("MainActivity", "onDestroy: Should stop service $shouldStopMusicService")
552563

553564
// Always unbind service if it was bound to prevent MusicBinder leak
554-
if (shouldStopMusicService && shouldUnbind) {
555-
unbindService(serviceConnection)
565+
if (shouldStopMusicService && shouldUnbind && isFinishing) {
556566
viewModel.isServiceRunning = false
557567
}
558568
unloadKoinModules(viewModelModule)
559569
super.onDestroy()
570+
Log.d("MainActivity", "onDestroy: ")
560571
}
561572

562573
override fun onRestart() {

app/src/main/java/com/maxrave/simpmusic/ui/component/LyricsView.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ import androidx.compose.material3.rememberModalBottomSheetState
5656
import androidx.compose.runtime.Composable
5757
import androidx.compose.runtime.CompositionLocalProvider
5858
import androidx.compose.runtime.LaunchedEffect
59-
import androidx.compose.runtime.collectAsState
6059
import androidx.compose.runtime.getValue
6160
import androidx.compose.runtime.mutableFloatStateOf
6261
import androidx.compose.runtime.mutableIntStateOf
@@ -83,6 +82,7 @@ import androidx.compose.ui.text.style.TextAlign
8382
import androidx.compose.ui.unit.Dp
8483
import androidx.compose.ui.unit.DpSize
8584
import androidx.compose.ui.unit.dp
85+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
8686
import androidx.media3.common.util.UnstableApi
8787
import androidx.navigation.NavController
8888
import coil3.compose.AsyncImage
@@ -132,7 +132,7 @@ fun LyricsView(
132132
mutableIntStateOf(0)
133133
}
134134
val listState = rememberLazyListState()
135-
val current by timeLine.collectAsState()
135+
val current by timeLine.collectAsStateWithLifecycle()
136136
var currentLineIndex by rememberSaveable {
137137
mutableIntStateOf(-1)
138138
}
@@ -302,9 +302,9 @@ fun FullscreenLyricsSheet(
302302
) {
303303
val context = LocalContext.current
304304

305-
val screenDataState by sharedViewModel.nowPlayingScreenData.collectAsState()
306-
val timelineState by sharedViewModel.timeline.collectAsState()
307-
val controllerState by sharedViewModel.controllerState.collectAsState()
305+
val screenDataState by sharedViewModel.nowPlayingScreenData.collectAsStateWithLifecycle()
306+
val timelineState by sharedViewModel.timeline.collectAsStateWithLifecycle()
307+
val controllerState by sharedViewModel.controllerState.collectAsStateWithLifecycle()
308308

309309
val sheetState =
310310
rememberModalBottomSheetState(

app/src/main/java/com/maxrave/simpmusic/ui/component/ModalBottomSheet.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import androidx.compose.ui.text.style.TextDecoration
110110
import androidx.compose.ui.text.withLink
111111
import androidx.compose.ui.unit.dp
112112
import androidx.core.text.isDigitsOnly
113+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
113114
import androidx.media3.common.util.UnstableApi
114115
import androidx.navigation.NavController
115116
import coil3.compose.AsyncImage
@@ -176,10 +177,10 @@ fun InfoPlayerBottomSheet(
176177
}
177178
}
178179

179-
val screenDataState by sharedViewModel.nowPlayingScreenData.collectAsState()
180+
val screenDataState by sharedViewModel.nowPlayingScreenData.collectAsStateWithLifecycle()
180181
val songEntity by sharedViewModel.nowPlayingState.map { it?.songEntity }.collectAsState(null)
181182
val format by sharedViewModel.format.collectAsState(null)
182-
val downloadProgress by sharedViewModel.downloadFileProgress.collectAsState()
183+
val downloadProgress by sharedViewModel.downloadFileProgress.collectAsStateWithLifecycle()
183184

184185
if (downloadProgress != DownloadProgress.INIT) {
185186
Box(modifier = Modifier.fillMaxSize()) {
@@ -710,12 +711,12 @@ fun QueueBottomSheet(
710711
var overscrollJob by remember { mutableStateOf<Job?>(null) }
711712
var shouldShowQueueItemBottomSheet by rememberSaveable { mutableStateOf(false) }
712713
var clickMoreIndex by rememberSaveable { mutableIntStateOf(0) }
713-
val screenDataState by sharedViewModel.nowPlayingScreenData.collectAsState()
714+
val screenDataState by sharedViewModel.nowPlayingScreenData.collectAsStateWithLifecycle()
714715
val songEntity by sharedViewModel.nowPlayingState.map { it?.songEntity }.collectAsState(null)
715716
val queue by musicServiceHandler.queueData
716717
.mapLatest { it?.listTracks?.toList() ?: emptyList() }
717718
.collectAsState(emptyList())
718-
val loadMoreState by musicServiceHandler.stateFlow.collectAsState()
719+
val loadMoreState by musicServiceHandler.stateFlow.collectAsStateWithLifecycle()
719720
val endlessQueueEnable by dataStoreManager.endlessQueue.map { it == DataStoreManager.TRUE }.collectAsState(false)
720721

721722
val shouldLoadMore =
@@ -1181,7 +1182,7 @@ fun NowPlayingBottomSheet(
11811182
) {
11821183
val context = LocalContext.current
11831184

1184-
val uiState by viewModel.uiState.collectAsState()
1185+
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
11851186
val coroutineScope = rememberCoroutineScope()
11861187
val modelBottomSheetState =
11871188
rememberModalBottomSheetState(

0 commit comments

Comments
 (0)