Skip to content

Commit

Permalink
Allow changing channels from the player
Browse files Browse the repository at this point in the history
  • Loading branch information
khaled-0 committed Mar 17, 2024
1 parent 386845b commit dedab3e
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 37 deletions.
13 changes: 6 additions & 7 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,20 @@ android {
dependencies {
implementation("androidx.core:core-ktx:1.12.0")

implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.6.3")

implementation("androidx.core:core-splashscreen:1.0.1")

implementation(platform("androidx.compose:compose-bom:2024.02.02"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.material3:material3")
implementation("androidx.tv:tv-material:1.0.0-alpha10")

implementation("androidx.core:core-splashscreen:1.0.1")

implementation("io.coil-kt:coil-compose:2.6.0")
implementation("androidx.navigation:navigation-compose:2.7.7")
implementation("androidx.compose.material:material-icons-extended:1.6.3")

implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.6.3")


implementation("androidx.media3:media3-exoplayer:1.3.0")
implementation("androidx.media3:media3-exoplayer-hls:1.3.0")
implementation("androidx.media3:media3-exoplayer-dash:1.3.0")
Expand Down
14 changes: 9 additions & 5 deletions app/src/main/java/dev/khaled/leanstream/Navigator.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.khaled.leanstream

import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.NavType
Expand All @@ -10,34 +11,37 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import dev.khaled.leanstream.channels.Channel
import dev.khaled.leanstream.channels.ChannelPicker
import dev.khaled.leanstream.channels.ChannelViewModel
import dev.khaled.leanstream.player.Player
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.decodeFromHexString
import kotlinx.serialization.encodeToHexString


@OptIn(ExperimentalSerializationApi::class)
@Composable
fun Navigator() {
val navController = rememberNavController()
val channelViewModel: ChannelViewModel = viewModel()

NavHost(
navController = navController,
startDestination = Route.ChannelPicker.route,
) {

composable(route = Route.ChannelPicker.route) {
ChannelPicker { channel ->
ChannelPicker(channelViewModel) { channel ->
navController.navigateSingleTop(Route.Player.launch(channel))
}
}

composable(route = "${Route.Player.route}/{channel}",
arguments = listOf(navArgument("channel") { type = NavType.StringType })) {
composable(
route = "${Route.Player.route}/{channel}",
arguments = listOf(navArgument("channel") { type = NavType.StringType })
) {
val serializedChannel = it.arguments?.getString("channel") ?: return@composable
val channel = Cbor.decodeFromHexString<Channel>(serializedChannel)
Player(channel = channel) {
Player(channel = channel, channelViewModel.channels) {
navController.popBackStack()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dev.khaled.leanstream.channels

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand Down Expand Up @@ -71,7 +70,6 @@ fun ChannelPicker(


var searchToggled by rememberSaveable { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }

Column {

Expand Down
21 changes: 14 additions & 7 deletions app/src/main/java/dev/khaled/leanstream/player/Player.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.khaled.leanstream.player

import android.content.Context
import android.net.Uri
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
Expand All @@ -10,19 +11,28 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import dev.khaled.leanstream.channels.Channel
import dev.khaled.leanstream.player.controller.PlayerController


@Composable
fun Player(channel: Channel, navigateBack: () -> Unit) {
fun Player(channel: Channel, playlist: List<Channel>, navigateBack: () -> Unit) {
val exoPlayer = rememberExoPlayer(LocalContext.current)
val mediaSource = remember { MediaItem.fromUri(channel.url) }

LaunchedEffect(Unit) {
exoPlayer.setMediaItem(mediaSource)
playlist.forEach {
exoPlayer.addMediaItem(
MediaItem.Builder().setUri(it.url).setMediaMetadata(
MediaMetadata.Builder().setDisplayTitle(it.title)
.setArtworkUri(channel.icon.let { i -> Uri.parse(i) }).build()
).build()
)
}

exoPlayer.seekTo(playlist.indexOf(channel), 0)
exoPlayer.prepare()
}

Expand All @@ -37,10 +47,7 @@ fun Player(channel: Channel, navigateBack: () -> Unit) {


PlayerController(
modifier = Modifier.fillMaxSize(),
channel = channel,
player = exoPlayer,
backHandler = navigateBack
modifier = Modifier.fillMaxSize(), player = exoPlayer, backHandler = navigateBack
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.media3.common.MediaMetadata
import coil.compose.AsyncImage
import dev.khaled.leanstream.channels.Channel

@Composable
fun ChannelInfo(channel: Channel) {
fun ChannelInfo(metadata: MediaMetadata) {
Card {
Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
Text(
text = channel.title ?: channel.url,
text = metadata.displayTitle.toString(),
style = MaterialTheme.typography.titleLarge,
)
Spacer(modifier = Modifier.width(8.dp))
AsyncImage(
model = channel.icon,
model = metadata.artworkUri,
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import android.os.Looper
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.SkipNext
import androidx.compose.material.icons.rounded.SkipPrevious
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
Expand All @@ -21,22 +25,21 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.Player.STATE_BUFFERING
import androidx.media3.exoplayer.ExoPlayer
import dev.khaled.leanstream.channels.Channel
import dev.khaled.leanstream.conditional
import dev.khaled.leanstream.isRunningOnTV
import dev.khaled.leanstream.ui.ScreenOrientation

@Composable
fun PlayerController(
modifier: Modifier = Modifier,
channel: Channel,
player: ExoPlayer,
backHandler: () -> Unit,
) {
Expand All @@ -45,6 +48,7 @@ fun PlayerController(
val isRunningOnTV = isRunningOnTV(LocalContext.current)

var controllerVisible by remember { mutableStateOf(true) }
var isButtonFocused by remember { mutableStateOf(true) }

var isPlaying by remember { mutableStateOf(true) }
var isBuffering by remember { mutableStateOf(true) }
Expand All @@ -53,7 +57,12 @@ fun PlayerController(

val handler = remember { Handler(Looper.getMainLooper()) }
val controllerVisibilityRunnable =
remember { Runnable { if (controllerVisible) controllerVisible = false } }
remember {
Runnable {
if (isButtonFocused) return@Runnable
if (controllerVisible) controllerVisible = false
}
}

fun triggerHideController() = run {
handler.removeCallbacks(controllerVisibilityRunnable)
Expand Down Expand Up @@ -94,13 +103,10 @@ fun PlayerController(
Box(modifier = Modifier
.fillMaxSize()
.conditional(!controllerVisible) {
clickable(
interactionSource = MutableInteractionSource(),
indication = null,
onClick = {
controllerVisible = true
triggerHideController()
})
clickable {
controllerVisible = true
triggerHideController()
}
})


Expand Down Expand Up @@ -133,12 +139,25 @@ fun PlayerController(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(16.dp)
.onFocusEvent {
isButtonFocused = it.hasFocus
if (!it.hasFocus) triggerHideController()
},
verticalAlignment = Alignment.CenterVertically
) {
if (!isRunningOnTV) ExtraControls(backHandler)

Spacer(modifier = Modifier.weight(1f))

ChannelInfo(channel = channel)
FilledTonalIconButton(onClick = { player.seekToPreviousMediaItem() }) {
Icon(Icons.Rounded.SkipPrevious, contentDescription = null)
}

ChannelInfo(player.mediaMetadata)

FilledTonalIconButton(onClick = { player.seekToNextMediaItem() }) {
Icon(Icons.Rounded.SkipNext, contentDescription = null)
}
}
}
}
Expand Down

0 comments on commit dedab3e

Please sign in to comment.