diff --git a/app/src/main/java/com/zionhuang/music/constants/Dimensions.kt b/app/src/main/java/com/zionhuang/music/constants/Dimensions.kt index fbe8ab473..af7e21e41 100644 --- a/app/src/main/java/com/zionhuang/music/constants/Dimensions.kt +++ b/app/src/main/java/com/zionhuang/music/constants/Dimensions.kt @@ -20,7 +20,7 @@ val AppBarHeight = 64.dp val ListItemHeight = 64.dp val SuggestionItemHeight = 56.dp val SearchFilterHeight = 48.dp -val ListThumbnailSize = 48.dp +val ListThumbnailSize = 40.dp val GridThumbnailHeight = 128.dp val SmallGridThumbnailHeight = 92.dp diff --git a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt index 711999332..07ab2f6da 100644 --- a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt +++ b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt @@ -38,6 +38,7 @@ import com.zionhuang.music.db.entities.Song import com.zionhuang.music.db.entities.SongAlbumMap import com.zionhuang.music.db.entities.SongArtistMap import com.zionhuang.music.db.entities.SongEntity +import android.util.Log import com.zionhuang.music.extensions.reversed import com.zionhuang.music.extensions.toSQLiteQuery import com.zionhuang.music.models.MediaMetadata @@ -600,18 +601,31 @@ interface DatabaseDao { ) fun relatedSongs(songId: String): List - @Query( - """ - UPDATE playlist_song_map SET position = - CASE - WHEN position < :fromPosition THEN position + 1 - WHEN position > :fromPosition THEN position - 1 - ELSE :toPosition - END - WHERE playlistId = :playlistId AND position BETWEEN MIN(:fromPosition, :toPosition) AND MAX(:fromPosition, :toPosition) - """ - ) - fun move(playlistId: String, fromPosition: Int, toPosition: Int) + @Query("SELECT * FROM playlist_song_map WHERE playlistId = :playlistId ORDER BY position ASC") + fun getPlaylistSongMapsOrderedByPosition(playlistId: String): MutableList + + @Transaction + fun moveItemInPlaylist(playlistId: String, fromPosition: Int, toPosition: Int) { + if (fromPosition == toPosition) return + + val items = getPlaylistSongMapsOrderedByPosition(playlistId) + + if (fromPosition < 0 || fromPosition >= items.size) { + Log.e("DatabaseDao", "Invalid fromPosition for move: $fromPosition, current items size: ${items.size}, for playlistId: $playlistId") + return + } + + val movedItem = items.removeAt(fromPosition) + + val actualToPosition = if (toPosition < 0) 0 else if (toPosition > items.size) items.size else toPosition + items.add(actualToPosition, movedItem) + + items.forEachIndexed { index, map -> + if (map.position != index) { + update(map.copy(position = index)) + } + } + } @Query("DELETE FROM playlist_song_map WHERE playlistId = :playlistId") fun clearPlaylist(playlistId: String) diff --git a/app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt b/app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt index bbc53b145..6a86b8beb 100644 --- a/app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt +++ b/app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt @@ -5,6 +5,7 @@ import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey import java.time.LocalDateTime +import android.util.Log @Immutable @Entity( @@ -26,10 +27,14 @@ data class SongEntity( val totalPlayTime: Long = 0, // in milliseconds val inLibrary: LocalDateTime? = null, ) { - fun toggleLike() = copy( - liked = !liked, - inLibrary = if (!liked) inLibrary ?: LocalDateTime.now() else inLibrary - ) + fun toggleLike(): SongEntity { + val newLikedStatus = !liked + Log.d("SongEntity", "SongEntity.toggleLike for id: $id. New liked status: $newLikedStatus. Old liked status: $liked") + return copy( + liked = newLikedStatus, + inLibrary = if (newLikedStatus) inLibrary ?: LocalDateTime.now() else inLibrary + ) + } fun toggleLibrary() = copy(inLibrary = if (inLibrary == null) LocalDateTime.now() else null) } diff --git a/app/src/main/java/com/zionhuang/music/playback/DownloadUtil.kt b/app/src/main/java/com/zionhuang/music/playback/DownloadUtil.kt index 42f75ab9c..0e1e87ef5 100644 --- a/app/src/main/java/com/zionhuang/music/playback/DownloadUtil.kt +++ b/app/src/main/java/com/zionhuang/music/playback/DownloadUtil.kt @@ -22,6 +22,7 @@ import com.zionhuang.music.di.DownloadCache import com.zionhuang.music.di.PlayerCache import com.zionhuang.music.utils.enumPreference import dagger.hilt.android.qualifiers.ApplicationContext +import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -63,7 +64,7 @@ class DownloadUtil @Inject constructor( return@Factory dataSpec } - songUrlCache[mediaId]?.takeIf { it.second < System.currentTimeMillis() }?.let { + songUrlCache[mediaId]?.takeIf { it.second > System.currentTimeMillis() }?.let { return@Factory dataSpec.withUri(it.first.toUri()) } @@ -72,26 +73,36 @@ class DownloadUtil @Inject constructor( YouTube.player(mediaId) }.getOrThrow() if (playerResponse.playabilityStatus.status != "OK") { - throw PlaybackException(playerResponse.playabilityStatus.reason, null, PlaybackException.ERROR_CODE_REMOTE_ERROR) - } + val status = playerResponse.playabilityStatus.status + val reason = playerResponse.playabilityStatus.reason + Log.w("DownloadUtil", "Download failed for mediaId: $mediaId. PlayabilityStatus: $status, Reason: $reason") - val format = - if (playedFormat != null) { - playerResponse.streamingData?.adaptiveFormats?.find { it.itag == playedFormat.itag } + // Potentially map specific statuses to more user-friendly messages or specific exception types in the future. + // For now, use the reason provided by YouTube, or a generic message if reason is blank. + val messageToThrow = if (reason.isNullOrBlank()) { + "Download unavailable: $status" // Or a generic R.string.error_download_unavailable } else { - playerResponse.streamingData?.adaptiveFormats - ?.filter { it.isAudio } - ?.maxByOrNull { - it.bitrate * when (audioQuality) { - AudioQuality.AUTO -> if (connectivityManager.isActiveNetworkMetered) -1 else 1 - AudioQuality.HIGH -> 1 - AudioQuality.LOW -> -1 - } + (if (it.mimeType.startsWith("audio/webm")) 10240 else 0) // prefer opus stream - } - }!!.let { - // Specify range to avoid YouTube's throttling - it.copy(url = "${it.url}&range=0-${it.contentLength ?: 10000000}") + reason } + throw PlaybackException(messageToThrow, null, PlaybackException.ERROR_CODE_REMOTE_ERROR) + } + + val format = (if (playedFormat != null) { + playerResponse.streamingData?.adaptiveFormats?.find { it.itag == playedFormat.itag } + } else { + playerResponse.streamingData?.adaptiveFormats + ?.filter { it.isAudio && !it.url.isNullOrBlank() && it.mimeType.isNotBlank() } // Added checks + ?.maxByOrNull { + it.bitrate * when (audioQuality) { + AudioQuality.AUTO -> if (connectivityManager.isActiveNetworkMetered) -1 else 1 + AudioQuality.HIGH -> 1 + AudioQuality.LOW -> -1 + } + (if (it.mimeType.startsWith("audio/webm")) 10240 else 0) // prefer opus stream + } + })?.let { // Note: changed from !! to ?.let to handle potential null from find or maxByOrNull + // Specify range to avoid YouTube's throttling + it.copy(url = "${it.url}&range=0-${it.contentLength ?: 10000000}") + } ?: throw PlaybackException("No suitable download format found or format missing URL/MimeType.", null, PlaybackException.ERROR_CODE_IO_UNSPECIFIED) // Or a more specific error database.query { upsert( diff --git a/app/src/main/java/com/zionhuang/music/playback/MusicService.kt b/app/src/main/java/com/zionhuang/music/playback/MusicService.kt index dbae89d14..a18067d96 100644 --- a/app/src/main/java/com/zionhuang/music/playback/MusicService.kt +++ b/app/src/main/java/com/zionhuang/music/playback/MusicService.kt @@ -48,6 +48,7 @@ import androidx.media3.session.DefaultMediaNotificationProvider import androidx.media3.session.MediaController import androidx.media3.session.MediaLibraryService import androidx.media3.session.MediaSession +import android.util.Log import androidx.media3.session.SessionToken import com.google.common.util.concurrent.MoreExecutors import com.zionhuang.innertube.YouTube @@ -654,7 +655,15 @@ class MusicService : MediaLibraryService(), } } if (playerResponse.playabilityStatus.status != "OK") { - throw PlaybackException(playerResponse.playabilityStatus.reason, null, PlaybackException.ERROR_CODE_REMOTE_ERROR) + val reason = playerResponse.playabilityStatus.reason + // Log the detailed status and reason for better debugging + Log.w("MusicService", "Playback failed for mediaId: $mediaId. Status: ${playerResponse.playabilityStatus.status}, Reason: $reason") + val messageToUser = if (reason.isNullOrBlank()) { + getString(R.string.error_no_stream) + } else { + reason + } + throw PlaybackException(messageToUser, null, PlaybackException.ERROR_CODE_REMOTE_ERROR) } val format = @@ -665,7 +674,7 @@ class MusicService : MediaLibraryService(), } } else { playerResponse.streamingData?.adaptiveFormats - ?.filter { it.isAudio } + ?.filter { it.isAudio && !it.url.isNullOrBlank() && it.mimeType.isNotBlank() } // Added checks ?.maxByOrNull { it.bitrate * when (audioQuality) { AudioQuality.AUTO -> if (connectivityManager.isActiveNetworkMetered) -1 else 1 diff --git a/app/src/main/java/com/zionhuang/music/ui/player/Player.kt b/app/src/main/java/com/zionhuang/music/ui/player/Player.kt index b8d79f6da..9dcb6e34e 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/Player.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/Player.kt @@ -104,7 +104,9 @@ fun BottomSheetPlayer( useDarkTheme && pureBlack } val backgroundColor = if (useBlackBackground && state.value > state.collapsedBound) { - lerp(MaterialTheme.colorScheme.surfaceContainer, Color.Black, state.progress) + // Use a very dark grey as the starting point for the lerp when pureBlack is active + // to ensure a visible gradient effect during sheet expansion. + lerp(Color(0xFF1F1F1F), Color.Black, state.progress) } else { MaterialTheme.colorScheme.surfaceContainer } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt index 173b54de2..6814069f5 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt @@ -65,6 +65,7 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastForEachIndexed +import android.util.Log import androidx.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel import androidx.media3.exoplayer.offline.Download @@ -233,6 +234,9 @@ fun AlbumScreen( Row { IconButton( onClick = { + val albumId = albumWithSongs.album.id // Assuming albumWithSongs is not null here + val currentBookmarkStatus = albumWithSongs.album.bookmarkedAt + Log.d("AlbumScreen", "Album like toggled for albumId: $albumId. Current bookmarkedAt: $currentBookmarkStatus. New bookmarkedAt will be: ${if (currentBookmarkStatus != null) null else "not null"}") database.query { update(albumWithSongs.album.toggleLike()) } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt index 7352e01a2..c8450d99a 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt @@ -260,113 +260,115 @@ fun ArtistScreen( } artistPage.sections.fastForEach { section -> - item { - NavigationTitle( - title = section.title, - onClick = section.moreEndpoint?.let { - { - navController.navigate("artist/${viewModel.artistId}/items?browseId=${it.browseId}?params=${it.params}") + if (section.items.isNotEmpty()) { // Check if section has items + item { + NavigationTitle( + title = section.title, + onClick = section.moreEndpoint?.let { + { + navController.navigate("artist/${viewModel.artistId}/items?browseId=${it.browseId}?params=${it.params}") + } } - } - ) - } + ) + } - if ((section.items.firstOrNull() as? SongItem)?.album != null) { - items( - items = section.items, - key = { it.id } - ) { song -> - YouTubeListItem( - item = song as SongItem, - isActive = mediaMetadata?.id == song.id, - isPlaying = isPlaying, - trailingContent = { - IconButton( - onClick = { - menuState.show { - YouTubeSongMenu( - song = song, - navController = navController, - onDismiss = menuState::dismiss - ) + if ((section.items.firstOrNull() as? SongItem)?.album != null) { + items( + items = section.items, + key = { it.id } + ) { song -> + YouTubeListItem( + item = song as SongItem, + isActive = mediaMetadata?.id == song.id, + isPlaying = isPlaying, + trailingContent = { + IconButton( + onClick = { + menuState.show { + YouTubeSongMenu( + song = song, + navController = navController, + onDismiss = menuState::dismiss + ) + } } + ) { + Icon( + painter = painterResource(R.drawable.more_vert), + contentDescription = null + ) } - ) { - Icon( - painter = painterResource(R.drawable.more_vert), - contentDescription = null - ) - } - }, - modifier = Modifier - .clickable { - if (song.id == mediaMetadata?.id) { - playerConnection.player.togglePlayPause() - } else { - playerConnection.playQueue(YouTubeQueue.radio(song.toMediaMetadata())) + }, + modifier = Modifier + .clickable { + if (song.id == mediaMetadata?.id) { + playerConnection.player.togglePlayPause() + } else { + playerConnection.playQueue(YouTubeQueue.radio(song.toMediaMetadata())) + } } - } - .animateItem() - ) - } - } else { - item { - LazyRow { - items( - items = section.items, - key = { it.id } - ) { item -> - YouTubeGridItem( - item = item, - isActive = when (item) { - is SongItem -> mediaMetadata?.id == item.id - is AlbumItem -> mediaMetadata?.album?.id == item.id - else -> false - }, - isPlaying = isPlaying, - coroutineScope = coroutineScope, - modifier = Modifier - .combinedClickable( - onClick = { - when (item) { - is SongItem -> playerConnection.playQueue(YouTubeQueue.radio(item.toMediaMetadata())) - is AlbumItem -> navController.navigate("album/${item.id}") - is ArtistItem -> navController.navigate("artist/${item.id}") - is PlaylistItem -> navController.navigate("online_playlist/${item.id}") - } - }, - onLongClick = { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - menuState.show { + .animateItem() + ) + } + } else { + item { + LazyRow { + items( + items = section.items, + key = { it.id } + ) { item -> + YouTubeGridItem( + item = item, + isActive = when (item) { + is SongItem -> mediaMetadata?.id == item.id + is AlbumItem -> mediaMetadata?.album?.id == item.id + else -> false + }, + isPlaying = isPlaying, + coroutineScope = coroutineScope, + modifier = Modifier + .combinedClickable( + onClick = { when (item) { - is SongItem -> YouTubeSongMenu( - song = item, - navController = navController, - onDismiss = menuState::dismiss - ) + is SongItem -> playerConnection.playQueue(YouTubeQueue.radio(item.toMediaMetadata())) + is AlbumItem -> navController.navigate("album/${item.id}") + is ArtistItem -> navController.navigate("artist/${item.id}") + is PlaylistItem -> navController.navigate("online_playlist/${item.id}") + } + }, + onLongClick = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + menuState.show { + when (item) { + is SongItem -> YouTubeSongMenu( + song = item, + navController = navController, + onDismiss = menuState::dismiss + ) - is AlbumItem -> YouTubeAlbumMenu( - albumItem = item, - navController = navController, - onDismiss = menuState::dismiss - ) + is AlbumItem -> YouTubeAlbumMenu( + albumItem = item, + navController = navController, + onDismiss = menuState::dismiss + ) - is ArtistItem -> YouTubeArtistMenu( - artist = item, - onDismiss = menuState::dismiss - ) + is ArtistItem -> YouTubeArtistMenu( + artist = item, + onDismiss = menuState::dismiss + ) - is PlaylistItem -> YouTubePlaylistMenu( - playlist = item, - coroutineScope = coroutineScope, - onDismiss = menuState::dismiss - ) + is PlaylistItem -> YouTubePlaylistMenu( + playlist = item, + coroutineScope = coroutineScope, + onDismiss = menuState::dismiss + ) + } } } - } - ) - .animateItem() - ) + ) + .animateItem() + ) + } } } } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt index ab3bed418..ad4719111 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt @@ -236,10 +236,8 @@ fun LocalPlaylistScreen( LaunchedEffect(reorderableState.isAnyItemDragging) { if (!reorderableState.isAnyItemDragging) { - dragInfo?.let { (from, to) -> - database.transaction { - move(viewModel.playlistId, from, to) - } + dragInfo?.let { (fromValue, toValue) -> + database.moveItemInPlaylist(viewModel.playlistId, fromValue, toValue) dragInfo = null } } diff --git a/app/src/main/java/com/zionhuang/music/utils/DiscordRPC.kt b/app/src/main/java/com/zionhuang/music/utils/DiscordRPC.kt index c1d87c5ae..f8b2d260b 100644 --- a/app/src/main/java/com/zionhuang/music/utils/DiscordRPC.kt +++ b/app/src/main/java/com/zionhuang/music/utils/DiscordRPC.kt @@ -1,6 +1,7 @@ package com.zionhuang.music.utils import android.content.Context +import android.util.Log import com.my.kizzy.rpc.KizzyRPC import com.my.kizzy.rpc.RpcImage import com.zionhuang.music.R @@ -27,6 +28,8 @@ class DiscordRPC( since = System.currentTimeMillis(), applicationId = APPLICATION_ID ) + }.onFailure { exception -> + Log.e("AppDiscordRPC", "Error in updateSong (calling super.setActivity)", exception) } companion object { diff --git a/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt b/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt index ae0775f1d..b5adc6c06 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt @@ -1,5 +1,6 @@ package com.zionhuang.innertube +import android.util.Log import com.zionhuang.innertube.encoder.brotli import com.zionhuang.innertube.models.Context import com.zionhuang.innertube.models.YouTubeClient @@ -79,6 +80,8 @@ class InnerTube { } private fun HttpRequestBuilder.ytClient(client: YouTubeClient, setLogin: Boolean = false) { + Log.d("InnerTubeAuth", "ytClient called. Passed setLogin: $setLogin, Class_cookie_is_present: ${this@InnerTube.cookie != null}, Class_useLoginForBrowse: ${this@InnerTube.useLoginForBrowse}") + contentType(ContentType.Application.Json) headers { append("X-Goog-Api-Format-Version", "1") diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index 62349e442..f908db552 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -430,33 +430,40 @@ object YouTube { } suspend fun player(videoId: String, playlistId: String? = null): Result = runCatching { - var playerResponse: PlayerResponse - if (this.cookie != null) { // if logged in: try ANDROID_MUSIC client first because IOS client does not play age restricted songs - playerResponse = innerTube.player(ANDROID_MUSIC, videoId, playlistId).body() - if (playerResponse.playabilityStatus.status == "OK") { - return@runCatching playerResponse + var responseToReturnOnError: PlayerResponse? = null + + if (this.cookie != null) { // if logged in: try ANDROID_MUSIC client first + val androidResponse = innerTube.player(ANDROID_MUSIC, videoId, playlistId).body() + if (androidResponse.playabilityStatus.status == "OK") { + return@runCatching androidResponse } + responseToReturnOnError = androidResponse // Store failure } - playerResponse = innerTube.player(IOS, videoId, playlistId).body() - if (playerResponse.playabilityStatus.status == "OK") { - return@runCatching playerResponse + + val iosResponse = innerTube.player(IOS, videoId, playlistId).body() + if (iosResponse.playabilityStatus.status == "OK") { + return@runCatching iosResponse } - val safePlayerResponse = innerTube.player(TVHTML5, videoId, playlistId).body() - if (safePlayerResponse.playabilityStatus.status != "OK") { - return@runCatching playerResponse + responseToReturnOnError = iosResponse // Store failure (overwrites previous if any) + + val tvHtml5Response = innerTube.player(TVHTML5, videoId, playlistId).body() + if (tvHtml5Response.playabilityStatus.status != "OK") { + // If TVHTML5 also fails, its response is the most relevant to return. + return@runCatching tvHtml5Response } + + // If TVHTML5 is "OK", proceed with Piped streams logic val audioStreams = innerTube.pipedStreams(videoId).body().audioStreams - safePlayerResponse.copy( - streamingData = safePlayerResponse.streamingData?.copy( - adaptiveFormats = safePlayerResponse.streamingData.adaptiveFormats.mapNotNull { adaptiveFormat -> - audioStreams.find { it.bitrate == adaptiveFormat.bitrate }?.let { - adaptiveFormat.copy( - url = it.url - ) + tvHtml5Response.copy( + streamingData = tvHtml5Response.streamingData?.copy( + adaptiveFormats = tvHtml5Response.streamingData.adaptiveFormats.mapNotNull { adaptiveFormat -> + audioStreams.find { it.bitrate == adaptiveFormat.bitrate }?.let { pipedStream -> + adaptiveFormat.copy(url = pipedStream.url) } } ) ) + // Implicitly returns the modified tvHtml5Response } suspend fun next(endpoint: WatchEndpoint, continuation: String? = null): Result = runCatching { diff --git a/kizzy/src/main/java/com/my/kizzy/rpc/KizzyRPC.kt b/kizzy/src/main/java/com/my/kizzy/rpc/KizzyRPC.kt index 8d3175946..550717977 100644 --- a/kizzy/src/main/java/com/my/kizzy/rpc/KizzyRPC.kt +++ b/kizzy/src/main/java/com/my/kizzy/rpc/KizzyRPC.kt @@ -12,6 +12,7 @@ package com.my.kizzy.rpc +import android.util.Log import com.my.kizzy.gateway.DiscordWebSocket import com.my.kizzy.gateway.entities.presence.Activity import com.my.kizzy.gateway.entities.presence.Assets @@ -57,9 +58,21 @@ open class KizzyRPC(token: String) { status: String? = "online", since: Long? = null, ) { - if (!isRpcRunning()) { - discordWebSocket.connect() + try { + if (!isRpcRunning()) { + try { + discordWebSocket.connect() + } catch (e: Exception) { + Log.e("KizzyRPC", "Error connecting to Discord WebSocket", e) + return // Cannot proceed if connection fails + } + } + } catch (e: Exception) { + // Catching potential errors from isRpcRunning() itself, though less likely + Log.e("KizzyRPC", "Error checking RPC status or connecting", e) + return } + val presence = Presence( activities = listOf( Activity( @@ -84,7 +97,11 @@ open class KizzyRPC(token: String) { since = since, status = status ?: "online" ) - discordWebSocket.sendActivity(presence) + try { + discordWebSocket.sendActivity(presence) + } catch (e: Exception) { + Log.e("KizzyRPC", "Error sending Discord activity", e) + } } enum class Type(val value: Int) {