add exoplayer

This commit is contained in:
Christian Schabesberger 2024-07-09 18:20:30 +02:00
parent 46134589ae
commit 1c01609af9
7 changed files with 163 additions and 74 deletions

View File

@ -82,6 +82,7 @@ dependencies {
implementation(libs.androidx.media3.ui) implementation(libs.androidx.media3.ui)
implementation(libs.hilt.android) implementation(libs.hilt.android)
implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.foundation)
ksp(libs.hilt.android.compiler) ksp(libs.hilt.android.compiler)
ksp(libs.androidx.hilt.compiler) ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.hilt.navigation.compose)

View File

@ -21,7 +21,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".NewPlayerApp" android:name=".NewPlayerApp"
android:allowBackup="true" android:allowBackup="true"

View File

@ -1,22 +1,40 @@
package net.newpipe.newplayer.model package net.newpipe.newplayer.model
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.media3.common.MediaItem
import androidx.media3.common.Player import androidx.media3.common.Player
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import net.newpipe.newplayer.R
import javax.inject.Inject import javax.inject.Inject
interface VideoPlayerViewModel {
val player: Player?
}
@HiltViewModel @HiltViewModel
class VideoPlayerViewModel @Inject constructor( class VideoPlayerViewModelImpl @Inject constructor(
private val savedStateHandle: SavedStateHandle, private val savedStateHandle: SavedStateHandle,
val player: Player override val player: Player,
): ViewModel() { application: Application
) : AndroidViewModel(application), VideoPlayerViewModel {
val app = getApplication<Application>()
init { init {
player.prepare() player.prepare()
player.setMediaItem(MediaItem.fromUri(app.getString(R.string.ccc_6502_video)))
} }
override fun onCleared() { override fun onCleared() {
player.release() player.release()
} }
companion object {
val dummy = object : VideoPlayerViewModel {
override val player = null
}
}
} }

View File

@ -80,11 +80,9 @@ import net.newpipe.newplayer.ui.theme.video_player_onSurface
@Composable @Composable
fun VideoPlayerControllerUI() { fun VideoPlayerControllerUI() {
Surface( Surface(
modifier = Modifier modifier = Modifier.fillMaxSize(), color = Color.Transparent
.fillMaxSize()
.background(Color.White)
) { ) {
Box() { Box(modifier = Modifier.background(Color.Transparent)) {
TopUI( TopUI(
modifier = Modifier modifier = Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
@ -127,23 +125,18 @@ private fun TopUI(modifier: Modifier) {
onClick = { /*TODO*/ }, onClick = { /*TODO*/ },
contentPadding = PaddingValues(0.dp), contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent, containerColor = Color.Transparent, contentColor = video_player_onSurface
contentColor = video_player_onSurface
), ),
) { ) {
Text( Text(
"1080p", "1080p", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(0.dp)
) )
} }
IconButton( IconButton(
onClick = { /*TODO*/ }, onClick = { /*TODO*/ },
) { ) {
Text( Text(
"1x", "1x", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(0.dp)
) )
} }
IconButton( IconButton(
@ -177,8 +170,7 @@ private fun MainMenu() {
} }
Box { Box {
IconButton(onClick = { showMainMenu = true }, IconButton(onClick = { showMainMenu = true }, modifier = Modifier.onPlaced {
modifier = Modifier.onPlaced {
offsetY = with(pixel_density) { offsetY = with(pixel_density) {
it.size.height.toDp() it.size.height.toDp()
} }
@ -189,13 +181,11 @@ private fun MainMenu() {
contentDescription = stringResource(R.string.menu_item_more_settings) contentDescription = stringResource(R.string.menu_item_more_settings)
) )
} }
DropdownMenu( DropdownMenu(modifier = Modifier.align(Alignment.TopStart),
modifier = Modifier.align(Alignment.TopStart),
offset = DpOffset(x = 0.dp, y = -offsetY), offset = DpOffset(x = 0.dp, y = -offsetY),
expanded = showMainMenu, expanded = showMainMenu,
onDismissRequest = { showMainMenu = false }) { onDismissRequest = { showMainMenu = false }) {
DropdownMenuItem( DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_open_in_browser)) },
text = { Text(stringResource(R.string.menu_item_open_in_browser)) },
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Filled.Language, imageVector = Icons.Filled.Language,
@ -203,8 +193,7 @@ private fun MainMenu() {
) )
}, },
onClick = { /*TODO*/ showMainMenu = false }) onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem( DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_share_timestamp)) },
text = { Text(stringResource(R.string.menu_item_share_timestamp)) },
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Filled.Share, imageVector = Icons.Filled.Share,
@ -212,17 +201,13 @@ private fun MainMenu() {
) )
}, },
onClick = { /*TODO*/ showMainMenu = false }) onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem( DropdownMenuItem(text = { Text(stringResource(R.string.mute)) }, leadingIcon = {
text = { Text(stringResource(R.string.mute)) },
leadingIcon = {
Icon( Icon(
imageVector = Icons.AutoMirrored.Filled.VolumeUp, imageVector = Icons.AutoMirrored.Filled.VolumeUp,
contentDescription = stringResource(R.string.mute) contentDescription = stringResource(R.string.mute)
) )
}, }, onClick = { /*TODO*/ showMainMenu = false })
onClick = { /*TODO*/ showMainMenu = false }) DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_fit_screen)) },
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_item_fit_screen)) },
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Filled.FitScreen, imageVector = Icons.Filled.FitScreen,
@ -230,8 +215,7 @@ private fun MainMenu() {
) )
}, },
onClick = { /*TODO*/ showMainMenu = false }) onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem( DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_sub_titles)) },
text = { Text(stringResource(R.string.menu_item_sub_titles)) },
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Filled.Subtitles, imageVector = Icons.Filled.Subtitles,
@ -239,8 +223,7 @@ private fun MainMenu() {
) )
}, },
onClick = { /*TODO*/ showMainMenu = false }) onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem( DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_language)) },
text = { Text(stringResource(R.string.menu_item_language)) },
leadingIcon = { leadingIcon = {
Icon( Icon(
imageVector = Icons.Filled.Translate, imageVector = Icons.Filled.Translate,
@ -264,28 +247,22 @@ private fun CenterUI(modifier: Modifier) {
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier modifier = modifier
) { ) {
CenterControllButton( CenterControllButton(buttonModifier = Modifier.size(80.dp),
buttonModifier = Modifier.size(80.dp),
iconModifier = Modifier.size(40.dp), iconModifier = Modifier.size(40.dp),
icon = Icons.Filled.SkipPrevious, icon = Icons.Filled.SkipPrevious,
contentDescriptoion = stringResource(R.string.widget_description_previous_stream), contentDescriptoion = stringResource(R.string.widget_description_previous_stream),
onClick = {} onClick = {})
)
CenterControllButton( CenterControllButton(buttonModifier = Modifier.size(80.dp),
buttonModifier = Modifier.size(80.dp),
iconModifier = Modifier.size(60.dp), iconModifier = Modifier.size(60.dp),
icon = Icons.Filled.PlayArrow, icon = Icons.Filled.PlayArrow,
contentDescriptoion = stringResource(R.string.widget_description_play), contentDescriptoion = stringResource(R.string.widget_description_play),
onClick = {} onClick = {})
) CenterControllButton(buttonModifier = Modifier.size(80.dp),
CenterControllButton(
buttonModifier = Modifier.size(80.dp),
iconModifier = Modifier.size(40.dp), iconModifier = Modifier.size(40.dp),
icon = Icons.Filled.SkipNext, icon = Icons.Filled.SkipNext,
contentDescriptoion = stringResource(R.string.widget_description_next_stream), contentDescriptoion = stringResource(R.string.widget_description_next_stream),
onClick = {} onClick = {})
)
} }
} }
@ -301,15 +278,12 @@ private fun CenterControllButton(
onClick = onClick, onClick = onClick,
contentPadding = PaddingValues(0.dp), contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent, containerColor = Color.Transparent, contentColor = video_player_onSurface
contentColor = video_player_onSurface
), ),
modifier = buttonModifier modifier = buttonModifier
) { ) {
Icon( Icon(
imageVector = icon, imageVector = icon, modifier = iconModifier, contentDescription = contentDescriptoion
modifier = iconModifier,
contentDescription = contentDescriptoion
) )
} }
} }
@ -352,26 +326,43 @@ private fun ViewInFullScreen() {
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
} }
@Composable
fun PreviewBackgroundSurface(
content: @Composable () -> Unit
) {
Surface(
modifier = Modifier.fillMaxSize(), color = Color.Black
) {
content()
}
}
@Preview(device = "spec:width=1080px,height=700px,dpi=440,orientation=landscape") @Preview(device = "spec:width=1080px,height=700px,dpi=440,orientation=landscape")
@Composable @Composable
fun PlayerUIPreviewEmbeded() { fun VideoPlayerControllerUIPreviewEmbeded() {
VideoPlayerTheme { VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI() VideoPlayerControllerUI()
} }
} }
}
@Preview(device = "spec:width=2340px,height=1080px,dpi=440,orientation=landscape") @Preview(device = "spec:width=2340px,height=1080px,dpi=440,orientation=landscape")
@Composable @Composable
fun PlayerUIPreviewLandscape() { fun VideoPlayerControllerUIPreviewLandscape() {
VideoPlayerTheme { VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI() VideoPlayerControllerUI()
} }
} }
}
@Preview(device = "spec:width=2340px,height=1080px,dpi=440,orientation=portrait") @Preview(device = "spec:width=2340px,height=1080px,dpi=440,orientation=portrait")
@Composable @Composable
fun PlayerUIPreviewPortrait() { fun VideoPlayerControllerUIPreviewPortrait() {
VideoPlayerTheme { VideoPlayerTheme {
PreviewBackgroundSurface {
VideoPlayerControllerUI() VideoPlayerControllerUI()
} }
} }
}

View File

@ -20,12 +20,85 @@
package net.newpipe.newplayer.ui package net.newpipe.newplayer.ui
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.ui.PlayerView
import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
@Composable @Composable
fun VideoPlayerUI() { fun VideoPlayerUI(
val videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel() viewModel: VideoPlayerViewModel = hiltViewModel<VideoPlayerViewModelImpl>()
) {
var lifecycle by remember {
mutableStateOf(Lifecycle.Event.ON_CREATE)
}
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
lifecycle = event
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.Black
) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
PlayerView(context).also {
it.player = viewModel.player
it.useController = false
}
}, update = {
when (lifecycle) {
Lifecycle.Event.ON_PAUSE -> {
}
Lifecycle.Event.ON_RESUME -> {
}
Lifecycle.Event.ON_START -> {
it.player?.play()
}
else -> Unit
}
})
VideoPlayerControllerUI() VideoPlayerControllerUI()
} }
}
@Preview(device = "spec:width=1080px,height=700px,dpi=440,orientation=landscape")
@Composable
fun PlayerUIPreviewEmbeded() {
VideoPlayerTheme {
VideoPlayerUI(viewModel = VideoPlayerViewModelImpl.dummy)
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ccc_6502_video">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/mp4-h264-HQ/27c3-4159-en-reverse_engineering_mos_6502.mp4</string>
<string name="ccc_6502_audio">https://ftp.fau.de/cdn.media.ccc.de/congress/2010/ogg-audio-only/27c3-4159-en-reverse_engineering_mos_6502.ogg</string>
</resources>

View File

@ -26,7 +26,7 @@ junitVersion = "1.2.1"
espressoCore = "3.6.1" espressoCore = "3.6.1"
appcompat = "1.7.0" appcompat = "1.7.0"
material = "1.12.0" material = "1.12.0"
activity = "1.9.0" androidx = "1.9.0"
constraintlayout = "2.1.4" constraintlayout = "2.1.4"
material3 = "1.2.1" material3 = "1.2.1"
uiTooling = "1.6.8" uiTooling = "1.6.8"
@ -36,7 +36,7 @@ hiltAndroid = "2.51.1"
hiltCompiler = "1.2.0" hiltCompiler = "1.2.0"
hiltNavigationCompose = "1.2.0" hiltNavigationCompose = "1.2.0"
lifecycleViewmodelCompose = "2.8.3" lifecycleViewmodelCompose = "2.8.3"
kspVersion = "1.9.10-1.0.13" kspVersion = "1.9.0-1.0.13"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -45,9 +45,9 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" } material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "androidx" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidx" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" }
androidx-material-icons-extended-android = { group = "androidx.compose.material", name = "material-icons-extended-android", version.ref = "materialIconsExtendedAndroid" } androidx-material-icons-extended-android = { group = "androidx.compose.material", name = "material-icons-extended-android", version.ref = "materialIconsExtendedAndroid" }
@ -58,6 +58,7 @@ androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", vers
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" } androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hiltAndroid" } hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hiltAndroid" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "uiTooling" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }