diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 29c7265..999dcae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "com.googol.android.apps.photos" minSdk = 24 targetSdk = 35 - versionCode = 22 - versionName = "0.1.0-dev22" + versionCode = 23 + versionName = "0.1.0-dev23" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/foundation/src/main/java/com/zs/foundation/Player.kt b/foundation/src/main/java/com/zs/foundation/Player.kt deleted file mode 100644 index f63f04b..0000000 --- a/foundation/src/main/java/com/zs/foundation/Player.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.zs.foundation - -import android.annotation.SuppressLint -import android.net.Uri -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.media3.common.MediaItem -import androidx.media3.exoplayer.ExoPlayer -import androidx.media3.ui.PlayerControlView -import androidx.media3.ui.PlayerView -import kotlin.math.roundToInt - -private inline fun MediaItem(uri: Uri, id: String) = MediaItem.Builder().setUri(uri).setMediaId(id).build(); - - -@SuppressLint("UnsafeOptInUsageError") -@Composable -fun Player( - uri: Uri, - modifier: Modifier = Modifier, - playOnReady: Boolean = true, - useBuiltInController: Boolean = true, -) { - val density = LocalDensity.current - AndroidView( - modifier = modifier, - factory = { context -> - PlayerView(context).apply { - player = ExoPlayer.Builder(context).build().apply { - setMediaItem(MediaItem(uri, uri.toString())) - prepare() - playWhenReady = playOnReady - useController = useBuiltInController - layoutParams = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT - ) - clipToOutline = true - // Set the Background Color of the player as Solid Black Color. - setBackgroundColor(Color.Black.toArgb()) - val controller = findViewById(androidx.media3.ui.R.id.exo_controller) - val padding = with(density){ - 16.dp.roundToPx() - } - controller.setPadding(padding, padding, padding, padding) - } - } - }, - onRelease = { - it.player?.release() - it.player = null - }, - update = {view -> - view.useController = useBuiltInController - val player = view.player ?: return@AndroidView - if (player.currentMediaItem?.mediaId != uri.toString()) { - player.clearMediaItems() - player.setMediaItem(MediaItem(uri, uri.toString())) - player.prepare() - } - player.playWhenReady = playOnReady - } - ) -} \ No newline at end of file diff --git a/foundation/src/main/java/com/zs/foundation/player/Player.kt b/foundation/src/main/java/com/zs/foundation/player/Player.kt new file mode 100644 index 0000000..b385f93 --- /dev/null +++ b/foundation/src/main/java/com/zs/foundation/player/Player.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2024 Zakir Sheikh + * + * Created by 2024 on 17-09-2024. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zs.foundation.player + +import android.net.Uri +import androidx.compose.runtime.Composable +import androidx.compose.runtime.NonRestartableComposable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.media3.common.C +import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import androidx.media3.exoplayer.ExoPlayer +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.callbackFlow + +/** + * A value class that wraps a [Player] instance and provides convenient access to its properties and methods. + * + * This class is designed to be used as a lightweight wrapper around the `Player` object, allowing you to interact with it + * in a more concise and expressive way. + * + * @property value The underlying [Player] instance. + * @see rememberPlayerController + */ +@JvmInline +value class PlayerController internal constructor(internal val value: Player){ + + companion object { + /** + * Represents an unset time value. + * @see C.TIME_UNSET + */ + val TIME_UNSET = C.TIME_UNSET + + /** + * Player is idle. + * @see Player.STATE_IDLE + */ + val STATE_IDLE = Player.STATE_IDLE + + /** + * Player is buffering. + * @see Player.STATE_BUFFERING + */ + val STATE_BUFFERING = Player.STATE_BUFFERING + + /** + * Player is ready to play. + * @see Player.STATE_READY + */ + val STATE_READY = Player.STATE_READY + + /** + * Playback has ended. + * @see Player.STATE_ENDED + */ + val STATE_ENDED = Player.STATE_ENDED + } + + /** + * @see Player.getDuration + */ + val duration get() = value.duration + + /** + * @see Player.getCurrentPosition + */ + val position get() = value.currentPosition + + /** + * @see Player.getPlaybackState + */ + val state get() = value.playbackState + + /** + * @see Player.getPlayWhenReady + */ + var playWhenReady get() = value.playWhenReady + set(value) { this.value.playWhenReady = value } + + /** + * @see Player.isPlaying + */ + val isPlaying get() = value.isPlaying + + /** + * Emits a Unit value whenever the [Player.getPlaybackState] changes. + * + * **Note:** This property must be stored in a variable to be active, as it creates a + * new [Flow] instance each time it's accessed. + * + * Usage: + * ``` + * val mediaStateFlow = onMediaStateChanged + * ``` + * + * @see state + */ + val onMediaStateChanged: Flow get() = callbackFlow { + // Listener to capture playback state changes + val listener = object : Player.Listener { + override fun onPlaybackStateChanged(playbackState: Int) { + // Emit a unit value whenever the playback state changes + trySend(Unit) + } + } + + // Add the listener to the player + value.addListener(listener) + + // Remove the listener when the flow is closed + awaitClose { value.removeListener(listener) } + } + + + fun load(url: Uri){ + value.setMediaItem(MediaItem.fromUri(url)) + value.prepare() + } + + fun play(playWhenReady: Boolean = true){ + this.playWhenReady = playWhenReady + value.play() + } + + fun pause() = value.pause() + fun stop() = value.stop() +} + +/** + * Creates and remembers a [PlayerController] instance. + */ +@Composable +@NonRestartableComposable +fun rememberPlayerController(): PlayerController { + val context = LocalContext.current + return remember { + PlayerController( + value = ExoPlayer.Builder(context).build() + ) + } +} \ No newline at end of file diff --git a/foundation/src/main/java/com/zs/foundation/player/PlayerView.kt b/foundation/src/main/java/com/zs/foundation/player/PlayerView.kt new file mode 100644 index 0000000..8e49d12 --- /dev/null +++ b/foundation/src/main/java/com/zs/foundation/player/PlayerView.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2024 Zakir Sheikh + * + * Created by 2024 on 17-09-2024. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zs.foundation.player + +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.viewinterop.AndroidView +import androidx.media3.ui.PlayerView + +@Composable +fun PlayerView( + controller: PlayerController, + modifier: Modifier = Modifier +) { + AndroidView( + modifier = modifier, + factory = { + PlayerView(it).apply { + player = controller.value + useController = false + clipToOutline = true + // Set the Background Color of the player as Solid Black Color. + setBackgroundColor(Color.Black.toArgb()) + } + }, + onRelease = { + it.player = null + it.visibility = View.GONE + } + ) +} \ No newline at end of file diff --git a/foundation/src/main/java/com/zs/foundation/toast/Toast.kt b/foundation/src/main/java/com/zs/foundation/toast/Toast.kt index 995b703..12b95bd 100644 --- a/foundation/src/main/java/com/zs/foundation/toast/Toast.kt +++ b/foundation/src/main/java/com/zs/foundation/toast/Toast.kt @@ -239,7 +239,7 @@ internal fun Toast( val dismissState = rememberDismissState( confirmStateChange = { val confirm = !isExpanded // Dismiss only if not expanded - if (confirm) value.action() // Execute action if confirmed + if (confirm) value.dismiss() // Execute action if confirmed confirm } )