Address grill feedback: BT.709 tagging, per-job cancel, frameRateCap clamp

B1: Android encoder format now sets KEY_COLOR_STANDARD = BT709,
KEY_COLOR_TRANSFER = SDR_VIDEO, KEY_COLOR_RANGE = LIMITED (API 24+).
Previously the encoder inherited or emitted default color metadata,
which meant HDR sources produced incorrectly-tagged output on Android.
iOS already had AVVideoColorPropertiesKey set correctly.

B2: Replace single currentCompressor reference with a per-job map
keyed by jobId. The cancel function now takes a jobId and only cancels
that specific job. Prevents the previous race where a second compress
call overwrote the reference and made the first job uncancellable.

B3: Clamp frameRateCap to >= 1 at the native module boundary. Previously
a value of 0 from JS would cause divide-by-zero (Android Long division
ArithmeticException, iOS CMTime Infinity / Int32(0) frameDuration trap).
This commit is contained in:
vineyardbovines
2026-06-18 17:54:08 -04:00
parent 3d28b11ae3
commit 434d56ad00
5 changed files with 40 additions and 18 deletions
@@ -2,7 +2,8 @@ import AVFoundation
import ExpoModulesCore
public class ExpoBlueskyVideoCompressModule: Module {
private var currentCompressor: VideoCompressor?
private var activeCompressors: [Int: VideoCompressor] = [:]
private let activeCompressorsLock = NSLock()
public func definition() -> ModuleDefinition {
Name("ExpoBlueskyVideoCompress")
@@ -19,7 +20,7 @@ public class ExpoBlueskyVideoCompressModule: Module {
let targetBitrate = options["targetBitrate"] as? Int ?? 0
let maxSize = options["maxSize"] as? Int ?? 1920
let codecPref = options["codec"] as? String ?? "auto"
let frameRateCap = options["frameRateCap"] as? Int ?? 30
let frameRateCap = max(1, options["frameRateCap"] as? Int ?? 30)
let jobId = options["jobId"] as? Int ?? 0
let compressor = VideoCompressor(
@@ -37,21 +38,37 @@ public class ExpoBlueskyVideoCompressModule: Module {
}
)
self.currentCompressor = compressor
self.setCompressor(jobId, compressor)
do {
let result = try await compressor.compress()
self.currentCompressor = nil
self.setCompressor(jobId, nil)
return result
} catch {
self.currentCompressor = nil
self.setCompressor(jobId, nil)
throw error
}
}
Function("cancel") {
self.currentCompressor?.cancel()
self.currentCompressor = nil
Function("cancel") { (jobId: Int) in
self.cancelCompressor(jobId)
}
}
private func setCompressor(_ jobId: Int, _ compressor: VideoCompressor?) {
activeCompressorsLock.lock()
defer { activeCompressorsLock.unlock() }
if let compressor = compressor {
activeCompressors[jobId] = compressor
} else {
activeCompressors.removeValue(forKey: jobId)
}
}
private func cancelCompressor(_ jobId: Int) {
activeCompressorsLock.lock()
let compressor = activeCompressors.removeValue(forKey: jobId)
activeCompressorsLock.unlock()
compressor?.cancel()
}
}