From 75b32e2c759119028376b86f4d14567f178e5dfb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 8 Nov 2025 23:08:29 +0000 Subject: [PATCH 1/3] feat: Implement auto-play for channel deep links This commit introduces auto-play functionality for deep links. When a user opens a `twitch.tv/` URL, the app will now check if the streamer is live. If they are, the stream will begin playing automatically. If the streamer is offline, the app will fall back to displaying the streamer's channel page. --- .../xtra/ui/main/MainActivity.kt | 12 +++++- .../xtra/ui/main/MainViewModel.kt | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainActivity.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainActivity.kt index e55223259..95ab26a53 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainActivity.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainActivity.kt @@ -520,13 +520,23 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { else -> { val login = url.substringAfter("twitch.tv/").takeIf { it.isNotBlank() }?.let { it.substringBefore("?", it.substringBefore("/")) } if (!login.isNullOrBlank()) { - viewModel.loadUser( + viewModel.handleChannelLink( login, prefs.getString(C.NETWORK_LIBRARY, "OkHttp"), TwitchApiHelper.getGQLHeaders(this), TwitchApiHelper.getHelixHeaders(this), prefs.getBoolean(C.ENABLE_INTEGRITY, false), ) + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.stream.collectLatest { stream -> + if (stream != null) { + startStream(stream) + viewModel.stream.value = null + } + } + } + } lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.user.collectLatest { user -> diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt index 6636a2c6c..73e256ff1 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt @@ -22,6 +22,7 @@ import com.github.andreyasadchy.xtra.R import com.github.andreyasadchy.xtra.model.VideoPosition import com.github.andreyasadchy.xtra.model.ui.Clip import com.github.andreyasadchy.xtra.model.ui.OfflineVideo +import com.github.andreyasadchy.xtra.model.ui.Stream import com.github.andreyasadchy.xtra.model.ui.User import com.github.andreyasadchy.xtra.model.ui.Video import com.github.andreyasadchy.xtra.repository.AuthRepository @@ -96,6 +97,8 @@ class MainViewModel @Inject constructor( val video = MutableStateFlow(null) val clip = MutableStateFlow(null) val user = MutableStateFlow(null) + private val _stream = MutableStateFlow(null) + val stream: MutableStateFlow = _stream val updateUrl = MutableSharedFlow() @@ -933,4 +936,38 @@ class MainViewModel @Inject constructor( offlineRepository.deleteOldImages() } } + + fun handleChannelLink(login: String?, networkLibrary: String?, gqlHeaders: Map, helixHeaders: Map, enableIntegrity: Boolean) { + if (stream.value == null && user.value == null) { + viewModelScope.launch { + try { + val response = graphQLRepository.loadQueryStream(networkLibrary, gqlHeaders, login) + if (enableIntegrity && integrity.value == null) { + response.errors?.find { it.message == "failed integrity check" }?.let { + integrity.value = "refresh" + return@launch + } + } + val streamData = response.data?.user?.stream + if (streamData != null) { + _stream.value = Stream( + channelId = streamData.broadcaster?.id, + channelLogin = streamData.broadcaster?.login, + channelName = streamData.broadcaster?.displayName, + profileImageUrl = streamData.broadcaster?.profileImageURL, + title = streamData.title, + gameId = streamData.game?.id, + gameName = streamData.game?.name, + viewerCount = streamData.viewersCount, + thumbnailUrl = streamData.previewImageURL, + ) + } else { + loadUser(login, networkLibrary, gqlHeaders, helixHeaders, enableIntegrity) + } + } catch (e: Exception) { + loadUser(login, networkLibrary, gqlHeaders, helixHeaders, enableIntegrity) + } + } + } + } } \ No newline at end of file From 3bdd6ae0950aeda381a3416a9445bd2b0551460e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 8 Nov 2025 23:19:19 +0000 Subject: [PATCH 2/3] feat: Implement auto-play for channel deep links This commit introduces auto-play functionality for deep links. When a user opens a `twitch.tv/` URL, the app will now check if the streamer is live. If they are, the stream will begin playing automatically. If the streamer is offline, the app will fall back to displaying the streamer's channel page. --- .../com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt index 73e256ff1..cdc95c664 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt @@ -941,14 +941,14 @@ class MainViewModel @Inject constructor( if (stream.value == null && user.value == null) { viewModelScope.launch { try { - val response = graphQLRepository.loadQueryStream(networkLibrary, gqlHeaders, login) + val response = graphQLRepository.loadQueryUsersStream(networkLibrary, gqlHeaders, logins = listOf(login!!)) if (enableIntegrity && integrity.value == null) { response.errors?.find { it.message == "failed integrity check" }?.let { integrity.value = "refresh" return@launch } } - val streamData = response.data?.user?.stream + val streamData = response.data?.users?.firstOrNull()?.stream if (streamData != null) { _stream.value = Stream( channelId = streamData.broadcaster?.id, From 0e94b226243a4b916a6d6cc9382d10ab1af0737a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 8 Nov 2025 23:28:02 +0000 Subject: [PATCH 3/3] feat: Implement auto-play for channel deep links This commit introduces auto-play functionality for deep links. When a user opens a `twitch.tv/` URL, the app will now check if the streamer is live. If they are, the stream will begin playing automatically. If the streamer is offline, the app will fall back to displaying the streamer's channel page. --- .../andreyasadchy/xtra/ui/main/MainViewModel.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt index cdc95c664..3cc8a3037 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt @@ -948,16 +948,17 @@ class MainViewModel @Inject constructor( return@launch } } - val streamData = response.data?.users?.firstOrNull()?.stream + val userData = response.data?.users?.firstOrNull() + val streamData = userData?.stream if (streamData != null) { _stream.value = Stream( - channelId = streamData.broadcaster?.id, - channelLogin = streamData.broadcaster?.login, - channelName = streamData.broadcaster?.displayName, - profileImageUrl = streamData.broadcaster?.profileImageURL, - title = streamData.title, + channelId = userData?.id, + channelLogin = userData?.login, + channelName = userData?.displayName, + profileImageUrl = userData?.profileImageURL, + title = streamData.broadcaster?.broadcastSettings?.title, gameId = streamData.game?.id, - gameName = streamData.game?.name, + gameName = streamData.game?.displayName, viewerCount = streamData.viewersCount, thumbnailUrl = streamData.previewImageURL, )