Files
buzz/mobile/ios/Runner/MP4Canonicalizer.swift
klopez4212 6f5ec4a731 Add mobile voice notes (#7121)
## Summary
- add iOS and Android voice-note recording and preview directly in the
mobile composer
- add waveform playback, scrubbing, speed controls, haptics, and
one-shot playback in chat
- package recordings in a canonical H.264/AAC MP4 envelope on both
platforms so existing relays accept them
- preserve the shared composer interaction and attachment-card treatment
across mobile platforms

Mobile counterpart to #6978.

## Testing
- `just ci`
- `just mobile-check`
- `just mobile-test` (2,026 tests)
- Android debug build compiled, installed, launched, and Voice note
verified in the attachment menu on Pixel 10
- signed iOS device build installed on iPhone

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
2026-09-03 08:07:04 +00:00

74 lines
2.4 KiB
Swift

import Foundation
enum MP4Canonicalizer {
static func neutralizeSampleDependencyBoxes(at url: URL) throws {
var data = try Data(contentsOf: url)
try neutralizeSampleDependencyBoxes(in: &data, start: 0, end: data.count)
try data.write(to: url, options: .atomic)
}
private static func neutralizeSampleDependencyBoxes(
in data: inout Data,
start: Int,
end: Int
) throws {
let containers: Set<[UInt8]> = [
Array("moov".utf8), Array("trak".utf8), Array("mdia".utf8),
Array("minf".utf8), Array("stbl".utf8), Array("edts".utf8),
Array("dinf".utf8), Array("sinf".utf8), Array("schi".utf8),
]
let sampleDependencyType = Array("sdtp".utf8)
let freeType = Array("free".utf8)
var offset = start
while offset < end {
guard end - offset >= 8 else { throw invalidMp4BoxError() }
let compactSize = Int(readBigEndianUInt32(data, at: offset))
var headerSize = 8
let boxSize: Int
if compactSize == 1 {
guard end - offset >= 16 else { throw invalidMp4BoxError() }
let extendedSize = readBigEndianUInt64(data, at: offset + 8)
guard extendedSize <= UInt64(Int.max) else { throw invalidMp4BoxError() }
boxSize = Int(extendedSize)
headerSize = 16
} else if compactSize == 0 {
boxSize = end - offset
} else {
boxSize = compactSize
}
guard boxSize >= headerSize, offset + boxSize <= end else {
throw invalidMp4BoxError()
}
let type = Array(data[(offset + 4)..<(offset + 8)])
if type == sampleDependencyType {
data.replaceSubrange((offset + 4)..<(offset + 8), with: freeType)
} else if containers.contains(type) {
try neutralizeSampleDependencyBoxes(
in: &data,
start: offset + headerSize,
end: offset + boxSize
)
}
offset += boxSize
}
}
private static func readBigEndianUInt32(_ data: Data, at offset: Int) -> UInt32 {
data[offset..<(offset + 4)].reduce(0) { ($0 << 8) | UInt32($1) }
}
private static func readBigEndianUInt64(_ data: Data, at offset: Int) -> UInt64 {
data[offset..<(offset + 8)].reduce(0) { ($0 << 8) | UInt64($1) }
}
private static func invalidMp4BoxError() -> NSError {
NSError(
domain: "BuzzVideoTranscode",
code: 1,
userInfo: [NSLocalizedDescriptionKey: "Invalid MP4 box structure."]
)
}
}