diff --git a/app/README.md b/app/README.md new file mode 100644 index 00000000..bfb4d850 --- /dev/null +++ b/app/README.md @@ -0,0 +1,12 @@ +# Demo app + +This app demonstrates the Open Video UI for Android, with a phone/tablet UI in [`MainActivity`](src/main/java/com/theoplayer/android/ui/demo/MainActivity.kt) and an Android TV UI in [`TvActivity`](src/main/java/com/theoplayer/android/ui/demo/tv/TvActivity.kt). + +## Credits + +The Android TV home screen uses the following images: + +* Bip Bop: ["Oscilloscope sine square.jpg"](https://commons.wikimedia.org/wiki/File:Oscilloscope_sine_square.jpg) by Xato, Public domain. +* Elephant's Dream: ["Elephants Dream s5 both.jpg"](https://commons.wikimedia.org/wiki/File:Elephants_Dream_s5_both.jpg) by © Blender Foundation / Ton Roosendaal et al., [CC BY 2.5](https://creativecommons.org/licenses/by/2.5). +* Sintel: ["Sintel-screenshot-1.jpg"](https://commons.wikimedia.org/wiki/File:Sintel-screenshot-1.jpg) by © Blender Foundation (www.sintel.org), [CC BY 3.0](https://creativecommons.org/licenses/by/3.0). +* DASH-IF Livesim and Test card: Frames of the DASH-IF Livesim test stream, captured in the demo app. diff --git a/app/src/main/java/com/theoplayer/android/ui/demo/Streams.kt b/app/src/main/java/com/theoplayer/android/ui/demo/Streams.kt index 159e9922..2c5f1cdb 100644 --- a/app/src/main/java/com/theoplayer/android/ui/demo/Streams.kt +++ b/app/src/main/java/com/theoplayer/android/ui/demo/Streams.kt @@ -1,18 +1,25 @@ package com.theoplayer.android.ui.demo +import androidx.annotation.DrawableRes import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.SaverScope import com.theoplayer.android.api.source.SourceDescription import com.theoplayer.android.api.source.TypedSource import com.theoplayer.android.api.source.addescription.GoogleImaAdDescription -data class Stream(val title: String, val description: String = "", val source: SourceDescription) +data class Stream( + val title: String, + val description: String = "", + @DrawableRes val image: Int, + val source: SourceDescription, +) val streams by lazy { listOf( Stream( title = "Bip Bop (HLS)", description = "HLS · VOD", + image = R.drawable.stream_bipbop, source = SourceDescription.Builder( TypedSource.Builder( @@ -25,6 +32,7 @@ val streams by lazy { Stream( title = "Elephant's Dream (HLS)", description = "HLS · VOD", + image = R.drawable.stream_elephants, source = SourceDescription.Builder( TypedSource.Builder( @@ -37,6 +45,7 @@ val streams by lazy { Stream( title = "Sintel (DASH) with preroll ad", description = "DASH · VOD · Preroll ad", + image = R.drawable.stream_sintel, source = SourceDescription.Builder( TypedSource.Builder( @@ -56,6 +65,7 @@ val streams by lazy { Stream( title = "DASH-IF Livesim", description = "DASH · Live", + image = R.drawable.stream_livesim, source = SourceDescription.Builder( TypedSource.Builder( @@ -68,6 +78,7 @@ val streams by lazy { Stream( title = "Test card (with CEA tracks)", description = "DASH · VOD · CEA-608 captions", + image = R.drawable.stream_testcard, source = SourceDescription.Builder( TypedSource.Builder("https://livesim2.dashif.org/vod/testpic_2s/cea608.mpd") diff --git a/app/src/main/java/com/theoplayer/android/ui/demo/tv/TvHomeScreen.kt b/app/src/main/java/com/theoplayer/android/ui/demo/tv/TvHomeScreen.kt index 5109b83d..9b5b2369 100644 --- a/app/src/main/java/com/theoplayer/android/ui/demo/tv/TvHomeScreen.kt +++ b/app/src/main/java/com/theoplayer/android/ui/demo/tv/TvHomeScreen.kt @@ -1,5 +1,6 @@ package com.theoplayer.android.ui.demo.tv +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.focusGroup import androidx.compose.foundation.layout.Arrangement @@ -28,6 +29,8 @@ import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.tv.material3.Button @@ -99,7 +102,6 @@ private fun StreamCarousel( ) { index -> StreamSlide( stream = streams[index], - background = slideBackgrounds[index % slideBackgrounds.size], onPlay = { onSelectStream(streams[index]) }, ) } @@ -125,10 +127,19 @@ private fun Modifier.keepHorizontalFocusInside(): Modifier = } .focusGroup() -/** A single carousel slide, with the stream's title, description and a play button. */ +/** + * A single carousel slide, with the stream's background image, title, description and play button. + */ @Composable -private fun StreamSlide(stream: Stream, background: Brush, onPlay: () -> Unit) { - Box(modifier = Modifier.fillMaxSize().background(background)) { +private fun StreamSlide(stream: Stream, onPlay: () -> Unit) { + Box(modifier = Modifier.fillMaxSize()) { + Image( + painter = painterResource(stream.image), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + SlideScrim() Column(modifier = Modifier.align(Alignment.BottomStart).padding(32.dp)) { SlideText(stream) Spacer(Modifier.height(16.dp)) @@ -141,6 +152,26 @@ private fun StreamSlide(stream: Stream, background: Brush, onPlay: () -> Unit) { } } +/** Dark gradients keep the white slide text readable. */ +@Composable +private fun SlideScrim() { + Box( + Modifier.fillMaxSize() + .background( + Brush.horizontalGradient( + 0f to Color.Black.copy(alpha = 0.8f), + 0.6f to Color.Transparent, + ) + ) + .background( + Brush.verticalGradient( + 0.4f to Color.Transparent, + 1f to Color.Black.copy(alpha = 0.6f), + ) + ) + ) +} + /** The title and description of a carousel slide. */ @Composable private fun ColumnScope.SlideText(stream: Stream) { @@ -177,17 +208,6 @@ private fun ThemeRow(currentTheme: TvPlayerTheme, onSelectTheme: (TvPlayerTheme) } } -// Dark gradients, so the white slide text stays readable. -private val slideBackgrounds = - listOf( - Color(0xFF1A237E) to Color(0xFF4A148C), - Color(0xFF004D40) to Color(0xFF01579B), - Color(0xFFB71C1C) to Color(0xFF4E342E), - Color(0xFF263238) to Color(0xFF1B5E20), - Color(0xFF311B92) to Color(0xFF880E4F), - ) - .map { (start, end) -> Brush.linearGradient(listOf(start, end)) } - // Keep content inside the overscan-safe area of TVs. private val safeAreaHorizontalPadding = 48.dp private val safeAreaVerticalPadding = 27.dp diff --git a/app/src/main/res/drawable-nodpi/stream_bipbop.jpg b/app/src/main/res/drawable-nodpi/stream_bipbop.jpg new file mode 100644 index 00000000..cf9c9e81 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/stream_bipbop.jpg differ diff --git a/app/src/main/res/drawable-nodpi/stream_elephants.jpg b/app/src/main/res/drawable-nodpi/stream_elephants.jpg new file mode 100644 index 00000000..9a064325 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/stream_elephants.jpg differ diff --git a/app/src/main/res/drawable-nodpi/stream_livesim.jpg b/app/src/main/res/drawable-nodpi/stream_livesim.jpg new file mode 100644 index 00000000..1040bbf1 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/stream_livesim.jpg differ diff --git a/app/src/main/res/drawable-nodpi/stream_sintel.jpg b/app/src/main/res/drawable-nodpi/stream_sintel.jpg new file mode 100644 index 00000000..1bae9371 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/stream_sintel.jpg differ diff --git a/app/src/main/res/drawable-nodpi/stream_testcard.jpg b/app/src/main/res/drawable-nodpi/stream_testcard.jpg new file mode 100644 index 00000000..048446ff Binary files /dev/null and b/app/src/main/res/drawable-nodpi/stream_testcard.jpg differ diff --git a/docs/assets/tv-demo-home.jpg b/docs/assets/tv-demo-home.jpg new file mode 100644 index 00000000..c54e937c Binary files /dev/null and b/docs/assets/tv-demo-home.jpg differ diff --git a/docs/assets/tv-demo-home.png b/docs/assets/tv-demo-home.png deleted file mode 100644 index dbd08580..00000000 Binary files a/docs/assets/tv-demo-home.png and /dev/null differ diff --git a/docs/guides/android-tv/getting-started.md b/docs/guides/android-tv/getting-started.md index 10fca0b0..91317c45 100644 --- a/docs/guides/android-tv/getting-started.md +++ b/docs/guides/android-tv/getting-started.md @@ -174,6 +174,6 @@ The TV UI has no Chromecast, fullscreen, picture-in-picture, or mute controls. T The demo app's [`TvActivity`](https://github.com/THEOplayer/android-ui/blob/main/app/src/main/java/com/theoplayer/android/ui/demo/tv/TvActivity.kt) opens a home screen with a stream carousel and theme chips. -![Android TV demo home screen](../../assets/tv-demo-home.png) +![Android TV demo home screen](../../assets/tv-demo-home.jpg) To run it, create a **Television (1080p)** Android Virtual Device, run the `app` module, and open the app from the TV launcher. See the [TV demo source](https://github.com/THEOplayer/android-ui/tree/main/app/src/main/java/com/theoplayer/android/ui/demo/tv) for the full example.