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.
This commit is contained in:
vineyardbovines
2026-06-18 18:06:35 -04:00
parent 434d56ad00
commit 34ee7bafda
3 changed files with 28 additions and 23 deletions
@@ -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
@@ -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<String, Any> {
val outputFile = File(context.cacheDir, "${System.currentTimeMillis()}.mp4")
val outputFile = File(context.cacheDir, "${UUID.randomUUID()}.mp4")
var extractor: MediaExtractor? = null
var muxer: MediaMuxer? = null
@@ -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<Void, Never>) 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<Void, Never>) 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
}
}