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.hilt.android)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.foundation)
ksp(libs.hilt.android.compiler)
ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose)

View File

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

View File

@ -1,22 +1,40 @@
package net.newpipe.newplayer.model
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import dagger.hilt.android.lifecycle.HiltViewModel
import net.newpipe.newplayer.R
import javax.inject.Inject
interface VideoPlayerViewModel {
val player: Player?
}
@HiltViewModel
class VideoPlayerViewModel @Inject constructor(
class VideoPlayerViewModelImpl @Inject constructor(
private val savedStateHandle: SavedStateHandle,
val player: Player
): ViewModel() {
override val player: Player,
application: Application
) : AndroidViewModel(application), VideoPlayerViewModel {
val app = getApplication<Application>()
init {
player.prepare()
player.setMediaItem(MediaItem.fromUri(app.getString(R.string.ccc_6502_video)))
}
override fun onCleared() {
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
fun VideoPlayerControllerUI() {
Surface(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
modifier = Modifier.fillMaxSize(), color = Color.Transparent
) {
Box() {
Box(modifier = Modifier.background(Color.Transparent)) {
TopUI(
modifier = Modifier
.align(Alignment.TopStart)
@ -127,23 +125,18 @@ private fun TopUI(modifier: Modifier) {
onClick = { /*TODO*/ },
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = video_player_onSurface
containerColor = Color.Transparent, contentColor = video_player_onSurface
),
) {
Text(
"1080p",
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(0.dp)
"1080p", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
)
}
IconButton(
onClick = { /*TODO*/ },
) {
Text(
"1x",
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(0.dp)
"1x", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
)
}
IconButton(
@ -177,25 +170,22 @@ private fun MainMenu() {
}
Box {
IconButton(onClick = { showMainMenu = true },
modifier = Modifier.onPlaced {
offsetY = with(pixel_density) {
it.size.height.toDp()
}
IconButton(onClick = { showMainMenu = true }, modifier = Modifier.onPlaced {
offsetY = with(pixel_density) {
it.size.height.toDp()
}
}) {
}) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(R.string.menu_item_more_settings)
)
}
DropdownMenu(
modifier = Modifier.align(Alignment.TopStart),
DropdownMenu(modifier = Modifier.align(Alignment.TopStart),
offset = DpOffset(x = 0.dp, y = -offsetY),
expanded = showMainMenu,
onDismissRequest = { showMainMenu = false }) {
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_item_open_in_browser)) },
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_open_in_browser)) },
leadingIcon = {
Icon(
imageVector = Icons.Filled.Language,
@ -203,8 +193,7 @@ private fun MainMenu() {
)
},
onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_item_share_timestamp)) },
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_share_timestamp)) },
leadingIcon = {
Icon(
imageVector = Icons.Filled.Share,
@ -212,17 +201,13 @@ private fun MainMenu() {
)
},
onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem(
text = { Text(stringResource(R.string.mute)) },
leadingIcon = {
Icon(
imageVector = Icons.AutoMirrored.Filled.VolumeUp,
contentDescription = stringResource(R.string.mute)
)
},
onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_item_fit_screen)) },
DropdownMenuItem(text = { Text(stringResource(R.string.mute)) }, leadingIcon = {
Icon(
imageVector = Icons.AutoMirrored.Filled.VolumeUp,
contentDescription = stringResource(R.string.mute)
)
}, onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_fit_screen)) },
leadingIcon = {
Icon(
imageVector = Icons.Filled.FitScreen,
@ -230,8 +215,7 @@ private fun MainMenu() {
)
},
onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_item_sub_titles)) },
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_sub_titles)) },
leadingIcon = {
Icon(
imageVector = Icons.Filled.Subtitles,
@ -239,8 +223,7 @@ private fun MainMenu() {
)
},
onClick = { /*TODO*/ showMainMenu = false })
DropdownMenuItem(
text = { Text(stringResource(R.string.menu_item_language)) },
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_language)) },
leadingIcon = {
Icon(
imageVector = Icons.Filled.Translate,
@ -264,28 +247,22 @@ private fun CenterUI(modifier: Modifier) {
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier
) {
CenterControllButton(
buttonModifier = Modifier.size(80.dp),
CenterControllButton(buttonModifier = Modifier.size(80.dp),
iconModifier = Modifier.size(40.dp),
icon = Icons.Filled.SkipPrevious,
contentDescriptoion = stringResource(R.string.widget_description_previous_stream),
onClick = {}
)
onClick = {})
CenterControllButton(
buttonModifier = Modifier.size(80.dp),
CenterControllButton(buttonModifier = Modifier.size(80.dp),
iconModifier = Modifier.size(60.dp),
icon = Icons.Filled.PlayArrow,
contentDescriptoion = stringResource(R.string.widget_description_play),
onClick = {}
)
CenterControllButton(
buttonModifier = Modifier.size(80.dp),
onClick = {})
CenterControllButton(buttonModifier = Modifier.size(80.dp),
iconModifier = Modifier.size(40.dp),
icon = Icons.Filled.SkipNext,
contentDescriptoion = stringResource(R.string.widget_description_next_stream),
onClick = {}
)
onClick = {})
}
}
@ -301,15 +278,12 @@ private fun CenterControllButton(
onClick = onClick,
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = video_player_onSurface
containerColor = Color.Transparent, contentColor = video_player_onSurface
),
modifier = buttonModifier
) {
Icon(
imageVector = icon,
modifier = iconModifier,
contentDescription = contentDescriptoion
imageVector = icon, modifier = iconModifier, contentDescription = contentDescriptoion
)
}
}
@ -352,26 +326,43 @@ private fun ViewInFullScreen() {
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")
@Composable
fun PlayerUIPreviewEmbeded() {
fun VideoPlayerControllerUIPreviewEmbeded() {
VideoPlayerTheme {
VideoPlayerControllerUI()
PreviewBackgroundSurface {
VideoPlayerControllerUI()
}
}
}
@Preview(device = "spec:width=2340px,height=1080px,dpi=440,orientation=landscape")
@Composable
fun PlayerUIPreviewLandscape() {
fun VideoPlayerControllerUIPreviewLandscape() {
VideoPlayerTheme {
VideoPlayerControllerUI()
PreviewBackgroundSurface {
VideoPlayerControllerUI()
}
}
}
@Preview(device = "spec:width=2340px,height=1080px,dpi=440,orientation=portrait")
@Composable
fun PlayerUIPreviewPortrait() {
fun VideoPlayerControllerUIPreviewPortrait() {
VideoPlayerTheme {
VideoPlayerControllerUI()
PreviewBackgroundSurface {
VideoPlayerControllerUI()
}
}
}

View File

@ -20,12 +20,85 @@
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.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.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.ui.PlayerView
import net.newpipe.newplayer.model.VideoPlayerViewModel
import net.newpipe.newplayer.model.VideoPlayerViewModelImpl
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
@Composable
fun VideoPlayerUI() {
val videoPlayerViewModel: VideoPlayerViewModel = hiltViewModel()
VideoPlayerControllerUI()
fun VideoPlayerUI(
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()
}
}
@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"
appcompat = "1.7.0"
material = "1.12.0"
activity = "1.9.0"
androidx = "1.9.0"
constraintlayout = "2.1.4"
material3 = "1.2.1"
uiTooling = "1.6.8"
@ -36,7 +36,7 @@ hiltAndroid = "2.51.1"
hiltCompiler = "1.2.0"
hiltNavigationCompose = "1.2.0"
lifecycleViewmodelCompose = "2.8.3"
kspVersion = "1.9.10-1.0.13"
kspVersion = "1.9.0-1.0.13"
[libraries]
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-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
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-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-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" }
@ -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" }
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-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "uiTooling" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }