add exoplayer
This commit is contained in:
parent
46134589ae
commit
1c01609af9
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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" }
|
||||
|
|
Loading…
Reference in New Issue