From 34ee7bafda28a0ad122255a64c626f6f6ce650c8 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Thu, 18 Jun 2026 18:06:35 -0400 Subject: [PATCH] Address grill follow-ups: dead code, UUID filename, continuation guard, DataRateLimits N1: Delete unused CodecSelector.selectEncoder. VideoCompressor calls findEncoder directly; selectEncoder was dead since the initial commit. N2: Switch Android output filename from System.currentTimeMillis() to UUID.randomUUID(). Eliminates the collision risk when two compressions start in the same millisecond. N3: Guard CheckedContinuation against double-resume in both processVideoTrack and processAudioTrack. AVAssetWriter shouldn't re-invoke the requestMediaDataWhenReady block after markAsFinished, but if it did, the previous code would crash via CheckedContinuation's double-resume detection. Added a `finished` flag plus a local `finish` closure that no-ops on second call. N4: DataRateLimits window duration changed from Int `1` to Double `1.0`. VideoToolbox expects the duration value as a CFNumber with float semantics; Int bridges to NSNumber(int) which works on most iOS versions but is not spec-correct. --- .../blueskyvideocompress/CodecSelector.kt | 9 ----- .../blueskyvideocompress/VideoCompressor.kt | 3 +- .../ios/VideoCompressor.swift | 39 ++++++++++++------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/CodecSelector.kt b/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/CodecSelector.kt index e06ccc99e2..f35698486a 100644 --- a/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/CodecSelector.kt +++ b/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/CodecSelector.kt @@ -23,15 +23,6 @@ object CodecSelector { val isHardware: Boolean ) - fun selectEncoder(preferHevc: Boolean): EncoderInfo? { - if (preferHevc) { - val hevc = findEncoder(MediaFormat.MIMETYPE_VIDEO_HEVC, requireHardware = true) - if (hevc != null) return hevc - } - findEncoder(MediaFormat.MIMETYPE_VIDEO_AVC, requireHardware = true)?.let { return it } - return findEncoder(MediaFormat.MIMETYPE_VIDEO_AVC, requireHardware = false) - } - fun findEncoder(mime: String, requireHardware: Boolean): EncoderInfo? { val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS) val candidates = codecList.codecInfos diff --git a/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/VideoCompressor.kt b/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/VideoCompressor.kt index a4727c3600..f1728945ff 100644 --- a/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/VideoCompressor.kt +++ b/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/VideoCompressor.kt @@ -11,6 +11,7 @@ import android.os.Build import android.util.Log import java.io.File import java.nio.ByteBuffer +import java.util.UUID class VideoCompressor( private val context: Context, @@ -72,7 +73,7 @@ class VideoCompressor( encoderInfo: CodecSelector.EncoderInfo, useHevc: Boolean ): Map { - val outputFile = File(context.cacheDir, "${System.currentTimeMillis()}.mp4") + val outputFile = File(context.cacheDir, "${UUID.randomUUID()}.mp4") var extractor: MediaExtractor? = null var muxer: MediaMuxer? = null diff --git a/modules/expo-bluesky-video-compress/ios/VideoCompressor.swift b/modules/expo-bluesky-video-compress/ios/VideoCompressor.swift index baaf0f7a51..e4a75398ea 100644 --- a/modules/expo-bluesky-video-compress/ios/VideoCompressor.swift +++ b/modules/expo-bluesky-video-compress/ios/VideoCompressor.swift @@ -113,7 +113,7 @@ class VideoCompressor { ] let peakBytesPerSecond = Int(Double(effectiveBitrate) / 8.0 * 1.5) compressionProps[kVTCompressionPropertyKey_DataRateLimits as String] = [ - peakBytesPerSecond, 1 + peakBytesPerSecond, 1.0 ] as CFArray let videoColorProps: [String: Any] = [ @@ -259,20 +259,27 @@ class VideoCompressor { ) async { var lastProgressTime: CFAbsoluteTime = 0 var lastAppendedPTS: CMTime? + var finished = false await withCheckedContinuation { (continuation: CheckedContinuation) in writerInput.requestMediaDataWhenReady( on: DispatchQueue(label: "com.bsky.videocompress.video") ) { - while writerInput.isReadyForMoreMediaData { - if self.isCancelled { + let finish = { + if !finished { + finished = true writerInput.markAsFinished() continuation.resume() + } + } + while writerInput.isReadyForMoreMediaData { + if finished { return } + if self.isCancelled { + finish() return } guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else { - writerInput.markAsFinished() - continuation.resume() + finish() return } @@ -286,8 +293,7 @@ class VideoCompressor { lastAppendedPTS = pts if !writerInput.append(sampleBuffer) { - writerInput.markAsFinished() - continuation.resume() + finish() return } @@ -306,24 +312,31 @@ class VideoCompressor { readerOutput: AVAssetReaderOutput, writerInput: AVAssetWriterInput ) async { + var finished = false + await withCheckedContinuation { (continuation: CheckedContinuation) in writerInput.requestMediaDataWhenReady( on: DispatchQueue(label: "com.bsky.videocompress.audio") ) { - while writerInput.isReadyForMoreMediaData { - if self.isCancelled { + let finish = { + if !finished { + finished = true writerInput.markAsFinished() continuation.resume() + } + } + while writerInput.isReadyForMoreMediaData { + if finished { return } + if self.isCancelled { + finish() return } guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else { - writerInput.markAsFinished() - continuation.resume() + finish() return } if !writerInput.append(sampleBuffer) { - writerInput.markAsFinished() - continuation.resume() + finish() return } }