Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ dependencies {
implementation(libs.composeUiTooling)
implementation(libs.androidx.palette.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.window)
// implementation(libs.androidx.navigation.fragment.ktx)
// implementation(libs.androidx.navigation.ui.ktx)

Expand Down
14 changes: 12 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<!-- required with FOREGROUND_SERVICE_CONNECTED_DEVICE-->
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>

<!-- Samsung specific permissions -->
<uses-permission android:name="com.samsung.android.permission.SAMSUNG_COVER_WINDOW"/>
<uses-permission android:name="com.samsung.android.app.cover.permission.COVER"/>
<uses-permission android:name="com.samsung.android.permission.DISPLAY_COVER"/>


<!--android:dataExtractionRules="@xml/data_extraction"-->
<queries>
Expand All @@ -37,7 +42,7 @@
<package android:name="net.bitstamp.app"/>
<package android:name="com.revolut.revolut"/>
</queries>

<!-- fixme: remove usesCleartextTraffic when secured web server-->
<application
android:name="uk.co.sentinelweb.cuer.app.CuerApp"
android:allowBackup="true"
Expand All @@ -46,6 +51,7 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
>
<!-- android:usesCleartextTraffic="true"-->
<!-- android:networkSecurityConfig="@xml/network_security_config"-->
Expand Down Expand Up @@ -167,7 +173,11 @@
android:name="uk.co.sentinelweb.cuer.app.ui.exoplayer.ExoPlayerActivity"
android:label="@string/title_activity_fullscreen"
android:screenOrientation="landscape"
tools:ignore="LockedOrientationActivity"/>
tools:ignore="LockedOrientationActivity">
<meta-data
android:name="com.samsung.android.support.cover"
android:value="true"/>
</activity>

<service
android:name="uk.co.sentinelweb.cuer.app.service.cast.CastService"
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/assets/default-dbinit.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
},
"config": {
"deletable": false
}
},
"type": "USER"
},
{
"id": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class WifiStateReceiver(

private val _wifiStateFlow: MutableStateFlow<WifiState> = MutableStateFlow(WifiState())
override val wifiState: WifiState
get() = _wifiStateFlow.value.also { log.d("wifiState.value = $it") }
get() = _wifiStateFlow.value//.also { log.d("wifiState.value = $it") }

override val wifiStateFlow = _wifiStateFlow

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.android.scope.AndroidScopeComponent
import org.koin.core.scope.Scope
import rewriteIdsToSource
import uk.co.sentinelweb.cuer.app.orchestrator.OrchestratorContract.Identifier
import uk.co.sentinelweb.cuer.app.orchestrator.OrchestratorContract.Source.LOCAL
import uk.co.sentinelweb.cuer.app.orchestrator.OrchestratorContract.Source.MEMORY
import uk.co.sentinelweb.cuer.app.orchestrator.PlaylistOrchestrator
import uk.co.sentinelweb.cuer.app.orchestrator.deepOptions
Expand Down Expand Up @@ -78,6 +80,7 @@ class ExoPlayerActivity : FragmentActivity(), AndroidScopeComponent {
super.onCreate(savedInstanceState)
castListener.listen()
hideStatusBarWrapper.hide(this)
showOnLockScreen(true)
mviView = MviViewImpl()
enableEdgeToEdge()
setContent {
Expand Down Expand Up @@ -114,7 +117,7 @@ class ExoPlayerActivity : FragmentActivity(), AndroidScopeComponent {

override val renderer: ViewRenderer<Model> = object : ViewRenderer<Model> {
override fun render(model: Model) {
Log.d("ExoActivity","playState=${model.playState}, title=${model.texts.title}")
Log.d("ExoActivity", "playState=${model.playState}, title=${model.texts.title}")
_model.value = model
}
}
Expand All @@ -140,32 +143,37 @@ class ExoPlayerActivity : FragmentActivity(), AndroidScopeComponent {
}

companion object {

// todo cleanup
fun start(c: Context, playlistAndItem: PlaylistAndItemDomain) =
CoroutineScope(Dispatchers.Main).launch {
val use: PlaylistAndItemDomain = if (playlistAndItem.playlistId==null) {
val use: PlaylistAndItemDomain = if (playlistAndItem.playlistId == null) {
// make temporary queue playlist
val queuePlaylist = PlaylistDomain(
id = Identifier(QueueTemp.id, MEMORY),
title = "Empty",
title = "Temporary Queue",
type = PlaylistDomain.PlaylistTypeDomain.APP,
items = listOf(playlistAndItem.item)
items = listOf(
// todo just leave as local when databse is is saving.
if (playlistAndItem.item.id?.source == LOCAL) {
playlistAndItem.item.rewriteIdsToSource(MEMORY, null)
} else playlistAndItem.item
)
)
// save to memory
getKoin().get<PlaylistOrchestrator>()
.save(queuePlaylist, queuePlaylist.id!!.deepOptions())

getKoin().get<PlaylistOrchestrator>().save(queuePlaylist, queuePlaylist.id!!.deepOptions())
playlistAndItem.copy(
playlistId = queuePlaylist.id,
playlistTitle = queuePlaylist.title
)
} else playlistAndItem
} else playlistAndItem

c.startActivity(
Intent(c, ExoPlayerActivity::class.java).apply {
putExtra(PLAYLIST_AND_ITEM.toString(), use.serialise())
flags = FLAG_ACTIVITY_NEW_TASK
})
}


}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package uk.co.sentinelweb.cuer.app.ui.exoplayer

import android.app.Activity
import android.util.Log
import android.view.WindowManager
import androidx.annotation.OptIn
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
Expand All @@ -11,16 +14,21 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import httpLocalNetworkUrl
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import kotlinx.coroutines.delay
import mapNetworkOrLocalStreamingPath
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import uk.co.sentinelweb.cuer.app.ui.common.compose.CuerSharedTheme
Expand All @@ -31,6 +39,7 @@
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract.PlayerCommand
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract.PlayerCommand.*
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract.View.Event.*
import uk.co.sentinelweb.cuer.app.util.disk.FullTruncatedPathMapper
import uk.co.sentinelweb.cuer.domain.PlayerStateDomain.*
import uk.co.sentinelweb.cuer.remote.server.LocalRepository

Expand All @@ -41,7 +50,9 @@
object ExoPlayerComposebles : KoinComponent {

private val localRepository: LocalRepository by inject()
private val fullTruncatedPathMapper: FullTruncatedPathMapper by inject()

@OptIn(UnstableApi::class)
@Composable
fun ExoPlayerUi(view: ExoPlayerActivity.MviViewImpl) {
val context = LocalContext.current
Expand All @@ -55,13 +66,33 @@
var volumeVisible by remember { mutableStateOf(true) }

val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
// fixme this may not be need now as the android manifeest useClearTextTraaffic flag fixed it.
val httpDataSourceFactory = DefaultHttpDataSource.Factory()
.setUserAgent("ExoPlayer")
.setConnectTimeoutMs(15000) // 15 seconds timeout

Check warning

Code scanning / detekt

Report magic numbers. Magic number is a numeric literal that is not defined as a constant and hence it's unclear what the purpose of this number is. It's better to declare such numbers as constants and give them a proper name. By default, -1, 0, 1, and 2 are not considered to be magic numbers. Warning

This expression contains a magic number. Consider defining it to a well named constant.
.setReadTimeoutMs(15000)

Check warning

Code scanning / detekt

Report magic numbers. Magic number is a numeric literal that is not defined as a constant and hence it's unclear what the purpose of this number is. It's better to declare such numbers as constants and give them a proper name. By default, -1, 0, 1, and 2 are not considered to be magic numbers. Warning

This expression contains a magic number. Consider defining it to a well named constant.
// .setAllowCrossProtocolRedirects(true)
.setCrossProtocolRedirectsForceOriginal(true)

// Create a DefaultDataSource.Factory that wraps the HTTP data source factory
// This also handles other types of URIs (like file://, asset://, etc.)
val dataSourceFactory = DefaultDataSource.Factory(
context,
httpDataSourceFactory
)

ExoPlayer.Builder(context)
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
.build().apply {
prepare()
playWhenReady = true
repeatMode = Player.REPEAT_MODE_ONE
addListener(exoPlayerListener(view, this))
addListener(LoggingPlayerListener())
addListener(object : Player.Listener {

override fun onVideoSizeChanged(size: VideoSize) {
Log.d(LOG_TAG, "onVideoSizeChanged = ${state.value.playState}")
if (size.height != 0) {
aspectRatioState =
(size.width.toFloat() / size.height.toFloat()) * size.pixelWidthHeightRatio
Expand Down Expand Up @@ -116,13 +147,18 @@

DisposableEffect(lifecycleOwner, exoPlayer) {
val lifecycleObserver = LifecycleEventObserver { _, event ->
Log.d(LOG_TAG, "lifecycle change = ${event.name}")
when (event) {
Lifecycle.Event.ON_PAUSE ->
Lifecycle.Event.ON_STOP -> {
view.dispatch(PlayPauseClicked(true))
exoPlayer.pause()
}

Lifecycle.Event.ON_RESUME -> {
Lifecycle.Event.ON_START -> {
// fixme not resuming properly stte needs to be updted but doesnt seem to .. caching?
view.dispatch(PlayPauseClicked(exoPlayer.isPlaying))
view.dispatch(PlayPauseClicked(false))
exoPlayer.play()

}

Lifecycle.Event.ON_DESTROY -> exoPlayer.release()
Expand All @@ -144,6 +180,7 @@
.fillMaxSize()
.background(Color.Black)
) {
KeepScreenOnHandler(keepScreenOn = exoPlayer.isPlaying)
PlayerSurface(
player = exoPlayer,
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
Expand Down Expand Up @@ -174,10 +211,25 @@
}
}

@Composable
fun KeepScreenOnHandler(keepScreenOn: Boolean) {
val view = LocalView.current

LaunchedEffect(keepScreenOn) {
val window = (view.context as Activity).window
if (keepScreenOn) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}


fun processCommand(command: PlayerCommand, exoPlayer: ExoPlayer) {
Log.d(LOG_TAG, "command = $command")
when (command) {
is Load -> command.item.httpLocalNetworkUrl(localRepository)
is Load -> command.item.mapNetworkOrLocalStreamingPath(localRepository, fullTruncatedPathMapper)
?.also { Log.d(LOG_TAG, "setMedia = $it") }
?.also { exoPlayer.setMediaItem(MediaItem.fromUri(it)) }

Expand All @@ -200,7 +252,6 @@
Player.STATE_READY -> {
Log.d(LOG_TAG, "Media is loaded and ready to play")
view.dispatch(DurationReceived(player.duration))
// view.dispatch(PlayerStateChanged(VIDEO_CUED))
}

Player.STATE_BUFFERING -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import uk.co.sentinelweb.cuer.app.ui.share.ShareNavigationHack
import uk.co.sentinelweb.cuer.app.ui.ytplayer.ItemLoader
import uk.co.sentinelweb.cuer.app.ui.ytplayer.LocalPlayerCastListener
import uk.co.sentinelweb.cuer.app.ui.ytplayer.PlayerModule
import uk.co.sentinelweb.cuer.app.util.wrapper.ShowOnLockScreenWrapper

interface ExoPlayerContract {
companion object{
Expand Down Expand Up @@ -80,6 +81,7 @@ interface ExoPlayerContract {
factory<AlertDialogContract.Creator> { AlertDialogCreator(get(), get()) }
scoped { LocalPlayerCastListener(get(), get()) }
scoped { ShareNavigationHack() }
scoped { (ShowOnLockScreenWrapper(a = get<ExoPlayerActivity>())) }
}
}
}
Expand Down
Loading
Loading