Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic ad support #29

Merged
merged 15 commits into from
Aug 12, 2024
Merged
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
> - 🏠 Internal
> - 💅 Polish

## Unreleased

* 💥 Updated to Jetpack Compose version 1.6.8 ([BOM](https://developer.android.com/jetpack/compose/bom) 2024.06.00).
* 🚀 Added basic support for advertisements. (Requires THEOplayer SDK version 7.10.0 or higher.)

## v1.6.0 (2024-04-16)

* 🚀 Added support for THEOplayer Android SDK version 7.
Expand Down
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ dependencies {
"mavenImplementation"("com.theoplayer.android-ui:android-ui:1.+")

implementation(libs.theoplayer)
implementation(libs.theoplayer.ads)
implementation(libs.theoplayer.ads.ima)
}
153 changes: 127 additions & 26 deletions app/src/main/java/com/theoplayer/android/ui/demo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@ package com.theoplayer.android.ui.demo
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Brush
import androidx.compose.material.icons.rounded.Movie
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.theoplayer.android.api.THEOplayerConfig
import com.theoplayer.android.api.source.SourceDescription
import com.theoplayer.android.api.source.TypedSource
import com.theoplayer.android.api.ads.ima.GoogleImaIntegrationFactory
import com.theoplayer.android.ui.DefaultUI
import com.theoplayer.android.ui.demo.nitflex.NitflexUI
import com.theoplayer.android.ui.demo.nitflex.theme.NitflexTheme
Expand All @@ -37,14 +45,17 @@ class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainContent() {
val source = SourceDescription.Builder(
TypedSource.Builder("https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8")
.build()
).build()
var stream by rememberSaveable(stateSaver = StreamSaver) { mutableStateOf(streams.first()) }
var streamMenuOpen by remember { mutableStateOf(false) }

val player = rememberPlayer()
LaunchedEffect(player, source) {
player.source = source
LaunchedEffect(player) {
player.theoplayerView?.let { theoplayerView ->
theoplayerView.player.addIntegration(GoogleImaIntegrationFactory.createGoogleImaIntegration(theoplayerView))
}
}
LaunchedEffect(player, stream) {
player.source = stream.source
}

var themeMenuOpen by remember { mutableStateOf(false) }
Expand All @@ -61,27 +72,20 @@ fun MainContent() {
},
actions = {
IconButton(onClick = {
player.source = source
player.source = stream.source
player.play()
}) {
Icon(Icons.Rounded.Refresh, contentDescription = "Reload")
}
IconButton(onClick = { streamMenuOpen = true }) {
Icon(Icons.Rounded.Movie, contentDescription = "Stream")
}
IconButton(onClick = { themeMenuOpen = true }) {
Icon(Icons.Rounded.Brush, contentDescription = "Theme")
}
DropdownMenu(
expanded = themeMenuOpen,
onDismissRequest = { themeMenuOpen = false }) {
DropdownMenuItem(
text = { Text(text = "Default theme") },
onClick = { theme = PlayerTheme.Default })
DropdownMenuItem(
text = { Text(text = "Nitflex theme") },
onClick = { theme = PlayerTheme.Nitflex })
}
}
)
}, content = { padding ->
}) { padding ->
val playerModifier = Modifier
.padding(padding)
.fillMaxSize(1f)
Expand All @@ -90,7 +94,7 @@ fun MainContent() {
DefaultUI(
modifier = playerModifier,
player = player,
title = "Elephant's Dream"
title = stream.title
)
}

Expand All @@ -99,18 +103,115 @@ fun MainContent() {
NitflexUI(
modifier = playerModifier,
player = player,
title = "Elephant's Dream"
title = stream.title
)
}
}
}

if (streamMenuOpen) {
SelectStreamDialog(
streams = streams,
currentStream = stream,
onSelectStream = {
stream = it
streamMenuOpen = false
},
onDismissRequest = { streamMenuOpen = false }
)
}
if (themeMenuOpen) {
SelectThemeDialog(
currentTheme = theme,
onSelectTheme = {
theme = it
themeMenuOpen = false
},
onDismissRequest = { themeMenuOpen = false }
)
}
}
}
}

enum class PlayerTheme(val title: String) {
Default(title = "Default theme"),
Nitflex(title = "Nitflex theme")
}

@Composable
fun SelectStreamDialog(
streams: List<Stream>,
currentStream: Stream,
onSelectStream: (Stream) -> Unit,
onDismissRequest: () -> Unit,
) {
Dialog(onDismissRequest = onDismissRequest) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Select a stream",
style = MaterialTheme.typography.headlineSmall
)
LazyColumn {
items(items = streams) {
ListItem(
headlineContent = { Text(text = it.title) },
leadingContent = {
RadioButton(
selected = (it == currentStream),
onClick = null
)
},
modifier = Modifier.clickable(onClick = {
onSelectStream(it)
})
)
}
}
}
})
}
}
}

enum class PlayerTheme {
Default,
Nitflex
@Composable
fun SelectThemeDialog(
currentTheme: PlayerTheme,
onSelectTheme: (PlayerTheme) -> Unit,
onDismissRequest: () -> Unit,
) {
Dialog(onDismissRequest = onDismissRequest) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Select a theme",
style = MaterialTheme.typography.headlineSmall
)
LazyColumn {
items(items = PlayerTheme.values()) {
ListItem(
headlineContent = { Text(text = it.title) },
leadingContent = {
RadioButton(
selected = (it == currentTheme),
onClick = null
)
},
modifier = Modifier.clickable(onClick = {
onSelectTheme(it)
})
)
}
}
}
}
}
}

@Preview(showBackground = true)
Expand Down
37 changes: 37 additions & 0 deletions app/src/main/java/com/theoplayer/android/ui/demo/Streams.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.theoplayer.android.ui.demo

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 source: SourceDescription)

val streams by lazy {
listOf(
Stream(
title = "Elephant's Dream (HLS)",
source = SourceDescription.Builder(
TypedSource.Builder("https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8")
.build()
).build()
),
Stream(
title = "Sintel (DASH) with preroll ad",
source = SourceDescription.Builder(
TypedSource.Builder("https://cdn.theoplayer.com/video/dash/webvtt-embedded-in-isobmff/Manifest.mpd")
.build()
).ads(
GoogleImaAdDescription.Builder("https://cdn.theoplayer.com/demos/ads/vast/dfp-preroll-no-skip.xml")
.timeOffset("start")
.build()
).build()
)
)
}

object StreamSaver : Saver<Stream, Int> {
override fun restore(value: Int): Stream? = streams.getOrNull(value)
override fun SaverScope.save(value: Stream): Int = streams.indexOf(value)
}
18 changes: 10 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[versions]
gradle = "8.3.2"
kotlin-gradle-plugin = "1.8.10"
ktx = "1.12.0"
lifecycle-runtime = "2.7.0"
activity-compose = "1.8.2"
appcompat = "1.6.1"
compose-bom = "2024.04.00"
ktx = "1.13.1"
lifecycle-runtime = "2.8.4"
activity-compose = "1.9.1"
appcompat = "1.7.0"
compose-bom = "2024.06.00"
junit4 = "4.13.2"
ui-test-junit4 = "1.6.5" # ...not in BOM for some reason?
androidx-junit = "1.1.5"
androidx-espresso = "3.5.1"
ui-test-junit4 = "1.6.8" # ...not in BOM for some reason?
androidx-junit = "1.2.1"
androidx-espresso = "3.6.1"
dokka = "1.9.20"
theoplayer = "7.8.0"

Expand All @@ -35,6 +35,8 @@ dokka-plugin = { group = "org.jetbrains.dokka", name = "android-documentation-pl
kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin-gradle-plugin" }
junit4 = { group = "junit", name = "junit", version.ref = "junit4" }
theoplayer = { group = "com.theoplayer.theoplayer-sdk-android", name = "core", version.ref = "theoplayer" }
theoplayer-ads = { group = "com.theoplayer.theoplayer-sdk-android", name = "integration-ads", version.ref = "theoplayer" }
theoplayer-ads-ima = { group = "com.theoplayer.theoplayer-sdk-android", name = "integration-ads-ima", version.ref = "theoplayer" }

[plugins]
android-application = { id = "com.android.application", version.ref = "gradle" }
Expand Down
Loading