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: 0 additions & 1 deletion Jetchat/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ dependencies {
implementation(libs.androidx.compose.ui.googlefonts)

implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.exoplayer.hls)
implementation(libs.androidx.media3.ui.compose)

debugImplementation(libs.androidx.compose.ui.test.manifest)
Expand Down
Binary file added Jetchat/app/src/main/assets/delightful_tips.mp4
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ fun ConversationContent(
}

var activeVideoUri by rememberSaveable { mutableStateOf<String?>(null) }
var activeVideoText by rememberSaveable { mutableStateOf<String?>(null) }

Box(modifier = modifier.fillMaxSize()) {
Scaffold(
Expand Down Expand Up @@ -287,7 +288,10 @@ fun ConversationContent(
modifier = Modifier.weight(1f),
scrollState = scrollState,
contentPadding = paddingValues,
onVideoClick = { videoUri -> activeVideoUri = videoUri },
onVideoClick = { videoUri, messageText ->
activeVideoUri = videoUri
activeVideoText = messageText
},
onMessageLikeToggled = onMessageLikeToggled,
)
}
Expand All @@ -301,7 +305,11 @@ fun ConversationContent(
activeVideoUri?.let { uri ->
FullScreenVideoPlayer(
videoUri = uri,
onDismiss = { activeVideoUri = null },
messageText = activeVideoText,
onDismiss = {
activeVideoUri = null
activeVideoText = null
},
)
}
}
Expand Down Expand Up @@ -435,7 +443,7 @@ fun Messages(
scrollState: LazyListState,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
onVideoClick: (String) -> Unit = {},
onVideoClick: (videoUri: String, messageText: String) -> Unit = { _, _ -> },
onMessageLikeToggled: (messageId: String) -> Unit = {},
) {
val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -518,7 +526,7 @@ fun Message(
isUserMe: Boolean,
isFirstMessageByAuthor: Boolean,
isLastMessageByAuthor: Boolean,
onVideoClick: (String) -> Unit = {},
onVideoClick: (videoUri: String, messageText: String) -> Unit = { _, _ -> },
onLikeToggled: (messageId: String) -> Unit = {},
) {
val borderColor = if (isUserMe) {
Expand Down Expand Up @@ -605,7 +613,7 @@ fun AuthorAndTextMessage(
isLastMessageByAuthor: Boolean,
authorClicked: (String) -> Unit,
modifier: Modifier = Modifier,
onVideoClick: (String) -> Unit = {},
onVideoClick: (videoUri: String, messageText: String) -> Unit = { _, _ -> },
onLikeToggled: (messageId: String) -> Unit = {},
) {
Column(
Expand Down Expand Up @@ -698,7 +706,7 @@ fun ChatItemBubble(
message: Message,
isUserMe: Boolean,
authorClicked: (String) -> Unit,
onVideoClick: (String) -> Unit = {},
onVideoClick: (videoUri: String, messageText: String) -> Unit = { _, _ -> },
onLikeToggled: (messageId: String) -> Unit = {},
) {
val isLiked = message.isLiked
Expand Down Expand Up @@ -847,7 +855,7 @@ fun ChatItemBubble(
) {
VideoThumbnail(
videoUri = videoUri,
onClick = { onVideoClick(videoUri) },
onClick = { onVideoClick(videoUri, message.content) },
shape = bubbleShape,
modifier = Modifier
.padding(if (isLiked) 8.dp else 0.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ data class BlurRegionSpec(
val boundsInSurface: RectF = RectF(),
val boundsInWindow: IntRect = IntRect.Zero,
val cornerRadiusPx: Float = 0f,
val cornerRadiiPx: List<Float>? = null,
val blurRadiusPx: Float = 50f,
val alpha: Float = 1.0f,
)
Expand Down Expand Up @@ -97,7 +98,11 @@ object SurfaceViewBlurHelper {
if (spec.boundsInSurface.width() <= 0f || spec.boundsInSurface.height() <= 0f) continue
val roundedRectRegion = RoundedRectBlurRegion().apply {
bounds = spec.boundsInSurface
setCornerRadii(spec.cornerRadiusPx.coerceAtLeast(0f))
if (spec.cornerRadiiPx != null && spec.cornerRadiiPx.size == 8) {
setCornerRadii(spec.cornerRadiiPx.toFloatArray())
} else {
setCornerRadii(spec.cornerRadiusPx.coerceAtLeast(0f))
}
alpha = spec.alpha.coerceIn(0f, 1f)
blurRadius = spec.blurRadiusPx.coerceAtLeast(0f)
}
Expand Down Expand Up @@ -152,6 +157,9 @@ fun Modifier.registerBlurRegion(
),
)
}
} else if (lastBounds != null) {
lastBounds = null
currentOnRemoveRegion(id)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ val initialMessages = listOf(
"Taylor Brooks",
"Here's a demo of the new blur regions feature in action! 🎥",
"8:06 PM",
videoUri = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
videoUri = "asset:///delightful_tips.mp4",
),
Message(
"me",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext

const val DEFAULT_VIDEO_URL =
"https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_5MB.mp4"
const val DEFAULT_VIDEO_URL = "asset:///delightful_tips.mp4"

/**
* Lightweight video thumbnail displayed inside scrollable lists (LazyColumn).
Expand All @@ -100,20 +99,26 @@ fun VideoThumbnail(videoUri: String, onClick: () -> Unit, modifier: Modifier = M

val thumbnailBitmap by produceState<Bitmap?>(initialValue = null, resolvedUri) {
withContext(Dispatchers.IO) {
val retriever = MediaMetadataRetriever()
try {
val retriever = MediaMetadataRetriever()
val uriString = resolvedUri.toString()
if (uriString.startsWith("http://") || uriString.startsWith("https://")) {
retriever.setDataSource(uriString, HashMap<String, String>())
if (uriString.startsWith("asset:///") || uriString.startsWith("file:///android_asset/")) {
val assetPath = uriString
.removePrefix("asset:///")
.removePrefix("file:///android_asset/")
context.assets.openFd(assetPath).use { afd ->
retriever.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
}
} else {
retriever.setDataSource(context, resolvedUri)
}
val bitmap = retriever.getFrameAtTime(1_000_000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
?: retriever.frameAtTime
retriever.release()
value = bitmap
} catch (e: Throwable) {
// Fallback to stylized dark gradient placeholder on network/codec error
// Fallback to stylized dark gradient placeholder on codec error
} finally {
retriever.release()
}
}
}
Expand Down Expand Up @@ -181,7 +186,7 @@ fun VideoThumbnail(videoUri: String, onClick: () -> Unit, modifier: Modifier = M
*/
@OptIn(UnstableApi::class)
@Composable
fun FullScreenVideoPlayer(videoUri: String, onDismiss: () -> Unit, modifier: Modifier = Modifier) {
fun FullScreenVideoPlayer(videoUri: String, onDismiss: () -> Unit, modifier: Modifier = Modifier, messageText: String? = null) {
val context = LocalContext.current
val resolvedUri = remember(videoUri) { resolveVideoUri(videoUri) }

Expand Down Expand Up @@ -252,10 +257,31 @@ fun FullScreenVideoPlayer(videoUri: String, onDismiss: () -> Unit, modifier: Mod
val bottom = boxBottom.coerceIn(0f, surfaceH)

if (right > left && bottom > top) {
val cornerRadius = spec.cornerRadiusPx
val r = spec.cornerRadiusPx.coerceAtLeast(0f)
val clipLeft = boxLeft <= 0f
val clipTop = boxTop <= 0f
val clipRight = boxRight >= surfaceW
val clipBottom = boxBottom >= surfaceH

val topLeft = if (clipLeft || clipTop) 0f else r
val topRight = if (clipRight || clipTop) 0f else r
val bottomLeft = if (clipLeft || clipBottom) 0f else r
val bottomRight = if (clipRight || clipBottom) 0f else r

val allSame = topLeft == topRight && topRight == bottomLeft && bottomLeft == bottomRight
spec.copy(
boundsInSurface = RectF(left, top, right, bottom),
cornerRadiusPx = cornerRadius,
cornerRadiusPx = if (allSame) topLeft else 0f,
cornerRadiiPx = if (allSame) {
null
} else {
listOf(
topLeft, topLeft,
topRight, topRight,
bottomLeft, bottomLeft,
bottomRight, bottomRight,
)
},
)
} else {
null
Expand Down Expand Up @@ -376,6 +402,7 @@ fun FullScreenVideoPlayer(videoUri: String, onDismiss: () -> Unit, modifier: Mod
},
onToggleFullscreen = onDismiss,
onUpdateBlurRegions = { updateBlurRegions() },
messageText = messageText,
modifier = Modifier.fillMaxSize(),
)
}
Expand All @@ -387,13 +414,20 @@ fun FullScreenVideoPlayer(videoUri: String, onDismiss: () -> Unit, modifier: Mod
* Displays a thumbnail in place and launches the fullscreen player screen when tapped.
*/
@Composable
fun VideoPlayer(videoUri: String, modifier: Modifier = Modifier, autoPlay: Boolean = false, shape: Shape = RoundedCornerShape(16.dp)) {
fun VideoPlayer(
videoUri: String,
modifier: Modifier = Modifier,
autoPlay: Boolean = false,
shape: Shape = RoundedCornerShape(16.dp),
messageText: String? = null,
) {
var isPlayerOpen by rememberSaveable { mutableStateOf(autoPlay) }

if (isPlayerOpen) {
FullScreenVideoPlayer(
videoUri = videoUri,
onDismiss = { isPlayerOpen = false },
messageText = messageText,
)
} else {
VideoThumbnail(
Expand Down
Loading
Loading