Skip to content

Commit feccf4e

Browse files
klopez4212leaderbrow
authored
Polish mobile inbox and media flows (#4512)
## Summary - make mobile unread state visible with bold channel names, an animated Inbox badge, and swipe-to-toggle Inbox rows - add directional transitions for top-level mobile navigation - let mobile send while media uploads, with cancellable progress UI - normalize iOS and Android video uploads, attach poster frames, and improve native video playback ## Validation - `just mobile-check` - `just mobile-test` - `cargo test -p buzz-media` - Pixel smoke test - iPhone smoke test Desktop background uploads moved to #4522 so the two platforms can be reviewed independently. --------- Signed-off-by: kenny lopez <[email protected]> Signed-off-by: Tom Brow <[email protected]> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> Co-authored-by: Tom Brow <[email protected]>
1 parent 631b05c commit feccf4e

79 files changed

Lines changed: 5228 additions & 1924 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎crates/buzz-media/src/validation.rs‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ fn validate_mp4_metadata_free(path: &Path) -> Result<(), MediaError> {
858858
*b"ftyp", *b"moov", *b"mdat", *b"free", *b"skip", *b"wide", *b"trak", *b"mdia", *b"minf",
859859
*b"stbl", *b"edts", *b"dinf", *b"sinf", *b"schi", *b"udta", *b"mvhd", *b"tkhd", *b"mdhd",
860860
*b"hdlr", *b"vmhd", *b"smhd", *b"dref", *b"url ", *b"urn ", *b"stsd", *b"stts", *b"stss",
861-
*b"ctts", *b"stsc", *b"stsz", *b"stco", *b"co64", *b"sgpd", *b"sbgp", *b"elst",
861+
*b"ctts", *b"stsc", *b"stsz", *b"stco", *b"co64", *b"sgpd", *b"sbgp", *b"sdtp", *b"elst",
862862
];
863863
fn walk(
864864
file: &mut std::fs::File,
@@ -2337,6 +2337,31 @@ mod tests {
23372337
assert!(validate_mp4_metadata_free(tmp.path()).is_ok());
23382338
}
23392339

2340+
#[test]
2341+
fn test_accepts_standard_sample_dependency_table() {
2342+
let bytes = [
2343+
box_wrap(b"ftyp", b"isom\0\0\0\0isom"),
2344+
box_wrap(
2345+
b"moov",
2346+
&box_wrap(
2347+
b"trak",
2348+
&box_wrap(
2349+
b"mdia",
2350+
&box_wrap(
2351+
b"minf",
2352+
&box_wrap(b"stbl", &box_wrap(b"sdtp", &[0x20, 0x10])),
2353+
),
2354+
),
2355+
),
2356+
),
2357+
box_wrap(b"mdat", b""),
2358+
]
2359+
.concat();
2360+
let tmp = tempfile::NamedTempFile::new().unwrap();
2361+
std::fs::write(tmp.path(), bytes).unwrap();
2362+
assert!(validate_mp4_metadata_free(tmp.path()).is_ok());
2363+
}
2364+
23402365
#[test]
23412366
fn test_rejects_excessive_mp4_box_nesting() {
23422367
let mut nested = box_wrap(b"free", b"");

‎mobile/android/app/src/main/kotlin/xyz/block/buzz/mobile/MainActivity.kt‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.graphics.Canvas
66
import android.graphics.ColorSpace
77
import android.graphics.ImageDecoder
88
import android.media.MediaExtractor
9+
import android.media.MediaMetadataRetriever
910
import android.media.MediaMuxer
1011
import android.os.Build
1112
import androidx.annotation.RequiresApi
@@ -97,6 +98,9 @@ class MainActivity : FlutterActivity() {
9798
TRANSCODE_VIDEO_TO_MP4_METHOD -> {
9899
handleTranscodeVideoToMp4(call.arguments, result)
99100
}
101+
GENERATE_VIDEO_POSTER_METHOD -> {
102+
handleGenerateVideoPoster(call.arguments, result)
103+
}
100104
REQUIRES_LEGACY_MEDIA_STORAGE_PERMISSION_METHOD -> {
101105
result.success(Build.VERSION.SDK_INT <= Build.VERSION_CODES.P)
102106
}
@@ -275,6 +279,55 @@ class MainActivity : FlutterActivity() {
275279
}.start()
276280
}
277281

282+
private fun handleGenerateVideoPoster(
283+
arguments: Any?,
284+
result: MethodChannel.Result,
285+
) {
286+
val sourcePath = arguments as? String ?: run {
287+
invalidArguments(result, "Expected source file path as String.")
288+
return
289+
}
290+
291+
Thread {
292+
val retriever = MediaMetadataRetriever()
293+
try {
294+
retriever.setDataSource(sourcePath)
295+
val source = retriever.getFrameAtTime(
296+
0,
297+
MediaMetadataRetriever.OPTION_CLOSEST_SYNC,
298+
) ?: retriever.getFrameAtTime(
299+
100_000,
300+
MediaMetadataRetriever.OPTION_CLOSEST_SYNC,
301+
) ?: throw IllegalArgumentException("Unable to decode a video frame.")
302+
val scale = minOf(1f, 720f / maxOf(source.width, source.height))
303+
val frame = if (scale < 1f) {
304+
Bitmap.createScaledBitmap(
305+
source,
306+
(source.width * scale).toInt(),
307+
(source.height * scale).toInt(),
308+
true,
309+
).also { source.recycle() }
310+
} else {
311+
source
312+
}
313+
val bytes = AndroidImageProcessor.encodeAndScrub(
314+
frame,
315+
Bitmap.CompressFormat.JPEG,
316+
) ?: throw IllegalArgumentException("Unable to encode a video preview.")
317+
frame.recycle()
318+
result.success(bytes)
319+
} catch (e: Exception) {
320+
result.error(
321+
"poster_failed",
322+
"Unable to create a video preview.",
323+
e.message,
324+
)
325+
} finally {
326+
retriever.release()
327+
}
328+
}.start()
329+
}
330+
278331
private fun invalidArguments(
279332
result: MethodChannel.Result,
280333
message: String,
@@ -287,6 +340,7 @@ class MainActivity : FlutterActivity() {
287340
private const val SANITIZE_IMAGE_FOR_UPLOAD_METHOD = "sanitizeImageForUpload"
288341
private const val TRANSCODE_IMAGE_TO_JPEG_METHOD = "transcodeImageToJpeg"
289342
private const val TRANSCODE_VIDEO_TO_MP4_METHOD = "transcodeVideoToMp4"
343+
private const val GENERATE_VIDEO_POSTER_METHOD = "generateVideoPoster"
290344
private const val REQUIRES_LEGACY_MEDIA_STORAGE_PERMISSION_METHOD =
291345
"requiresLegacyMediaStoragePermission"
292346
}

0 commit comments

Comments
 (0)