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:
+8
-8
@@ -2,9 +2,10 @@ package expo.modules.blueskyvideocompress
|
||||
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class ExpoBlueskyVideoCompressModule : Module() {
|
||||
private var currentCompressor: VideoCompressor? = null
|
||||
private val activeCompressors = ConcurrentHashMap<Int, VideoCompressor>()
|
||||
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("ExpoBlueskyVideoCompress")
|
||||
@@ -23,7 +24,7 @@ class ExpoBlueskyVideoCompressModule : Module() {
|
||||
val targetBitrate = (options["targetBitrate"] as? Number)?.toInt() ?: 0
|
||||
val maxSize = (options["maxSize"] as? Number)?.toInt() ?: 1920
|
||||
val codecPref = (options["codec"] as? String) ?: "auto"
|
||||
val frameRateCap = (options["frameRateCap"] as? Number)?.toInt() ?: 30
|
||||
val frameRateCap = ((options["frameRateCap"] as? Number)?.toInt() ?: 30).coerceAtLeast(1)
|
||||
val jobId = (options["jobId"] as? Number)?.toInt() ?: 0
|
||||
|
||||
val compressor = VideoCompressor(
|
||||
@@ -42,21 +43,20 @@ class ExpoBlueskyVideoCompressModule : Module() {
|
||||
}
|
||||
)
|
||||
|
||||
currentCompressor = compressor
|
||||
activeCompressors[jobId] = compressor
|
||||
|
||||
try {
|
||||
val result = compressor.compress()
|
||||
currentCompressor = null
|
||||
activeCompressors.remove(jobId)
|
||||
return@AsyncFunction result
|
||||
} catch (e: Exception) {
|
||||
currentCompressor = null
|
||||
activeCompressors.remove(jobId)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
Function("cancel") {
|
||||
currentCompressor?.cancel()
|
||||
currentCompressor = null
|
||||
Function("cancel") { jobId: Int ->
|
||||
activeCompressors.remove(jobId)?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -148,6 +148,11 @@ class VideoCompressor(
|
||||
)
|
||||
setInteger(MediaFormat.KEY_FRAME_RATE, frameRateCap)
|
||||
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT709)
|
||||
setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO)
|
||||
setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
setInteger(MediaFormat.KEY_PRIORITY, 0)
|
||||
setInteger(MediaFormat.KEY_OPERATING_RATE, frameRateCap)
|
||||
|
||||
@@ -63,7 +63,7 @@ export function compress(
|
||||
}
|
||||
|
||||
const abortHandler = () => {
|
||||
NativeModule.cancel()
|
||||
NativeModule.cancel(jobId)
|
||||
subscription?.remove()
|
||||
reject(new AbortError())
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ type ProgressEvent = {id: number; progress: number}
|
||||
interface ExpoBlueskyVideoCompressModule {
|
||||
probe(uri: string): Promise<VideoMetadata>
|
||||
compress(uri: string, options: NativeCompressOptions): Promise<CompressResult>
|
||||
cancel(): void
|
||||
cancel(jobId: number): void
|
||||
addListener(
|
||||
eventName: 'onProgress',
|
||||
listener: (event: ProgressEvent) => void,
|
||||
|
||||
Reference in New Issue
Block a user