Чат: вертикальное центрирование bubble вложений, tap-to-download аватар и мгновенный показ call-attachment
This commit is contained in:
74
tools/webrtc-custom-ios/README.md
Normal file
74
tools/webrtc-custom-ios/README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Custom WebRTC for Rosetta iOS (Audio E2EE Timestamp)
|
||||
|
||||
This setup builds a custom `WebRTC.xcframework` for iOS and patches audio E2EE so
|
||||
`FrameEncryptor/FrameDecryptor` receive non-empty `additional_data` with RTP timestamp bytes.
|
||||
|
||||
## Why
|
||||
|
||||
Stock `stasel/WebRTC` M146 calls audio frame encryptor with empty
|
||||
`additional_data` (`ad=0`), so nonce derivation based on timestamp is unavailable.
|
||||
|
||||
Desktop uses frame timestamp for nonce. This patch aligns iOS with that approach by passing
|
||||
an 8-byte big-endian timestamp payload in `additional_data` (absolute RTP timestamp,
|
||||
including sender start offset):
|
||||
|
||||
- bytes `0..3` = `0`
|
||||
- bytes `4..7` = RTP timestamp (big-endian)
|
||||
|
||||
Same patch as Android: `mobile-android/tools/webrtc-custom/patches/0001-...`
|
||||
|
||||
## Files
|
||||
|
||||
- `build_custom_webrtc_ios.sh` — reproducible build script
|
||||
- `patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch` — WebRTC patch (identical to Android)
|
||||
|
||||
## Build
|
||||
|
||||
Requirements: macOS, Xcode, `depot_tools` in PATH.
|
||||
|
||||
Bootstrap depot_tools first (if not installed):
|
||||
|
||||
```bash
|
||||
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ~/depot_tools
|
||||
export PATH=$PATH:$HOME/depot_tools
|
||||
```
|
||||
|
||||
Then build:
|
||||
|
||||
```bash
|
||||
cd /path/to/Rosetta/tools/webrtc-custom-ios
|
||||
./build_custom_webrtc_ios.sh
|
||||
```
|
||||
|
||||
Optional env vars:
|
||||
|
||||
- `WEBRTC_ROOT` — checkout root (default: `$HOME/webrtc_ios`)
|
||||
- `WEBRTC_BRANCH` — default `branch-heads/6422` (M125, same as Android)
|
||||
- `WEBRTC_TAG` — use a specific tag/commit instead of branch
|
||||
- `OUT_FRAMEWORK` — output xcframework path (default: `Frameworks/WebRTC.xcframework`)
|
||||
- `SYNC_JOBS` — `gclient sync` jobs (default: `1`)
|
||||
- `SYNC_RETRIES` — sync retry attempts (default: `8`)
|
||||
|
||||
## Integration in Xcode
|
||||
|
||||
1. Remove `stasel/WebRTC` SPM dependency from Xcode project
|
||||
2. Add `Frameworks/WebRTC.xcframework` as Embedded Framework
|
||||
3. Build and verify: diagnostic logs should show `ad=8` instead of `ad=0`
|
||||
|
||||
## Verification
|
||||
|
||||
After building and integrating:
|
||||
|
||||
```
|
||||
ENC frame#0 sz=53 ad=8 mode=raw-abs nonce=[00000000XXXXXXXX]
|
||||
DEC frame#0 sz=48 ad=8 mode=raw-abs ok=1 nonce=[00000000XXXXXXXX]
|
||||
```
|
||||
|
||||
Where `XXXXXXXX` is the RTP timestamp in hex. This matches Desktop and Android behavior.
|
||||
|
||||
## Maintenance
|
||||
|
||||
- Keep patch small: only `audio/channel_send.cc` + `audio/channel_receive.cc`
|
||||
- Pin WebRTC branch/tag for releases (M125 = `branch-heads/6422`)
|
||||
- Rebuild xcframework on version bumps
|
||||
- Verify `ad=8` in diagnostic logs after each rebuild
|
||||
153
tools/webrtc-custom-ios/build_custom_webrtc_ios.sh
Executable file
153
tools/webrtc-custom-ios/build_custom_webrtc_ios.sh
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Reproducible custom WebRTC xcframework build for Rosetta iOS.
|
||||
# Applies the same E2EE timestamp patch as the Android custom build.
|
||||
#
|
||||
# Requirements:
|
||||
# - macOS with Xcode (Command Line Tools)
|
||||
# - depot_tools in PATH (run bootstrap_depot_tools.sh first if needed)
|
||||
# - python3, git
|
||||
#
|
||||
# Usage:
|
||||
# cd /path/to/Rosetta/tools/webrtc-custom-ios
|
||||
# ./build_custom_webrtc_ios.sh
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROSETTA_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
PATCH_FILE="${SCRIPT_DIR}/patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch"
|
||||
|
||||
# Default: WebRTC M125 family — same as Android (branch-heads/6422).
|
||||
WEBRTC_BRANCH="${WEBRTC_BRANCH:-branch-heads/6422}"
|
||||
WEBRTC_TAG="${WEBRTC_TAG:-}"
|
||||
|
||||
# Source checkout root (contains src/).
|
||||
WEBRTC_ROOT="${WEBRTC_ROOT:-$HOME/webrtc_ios}"
|
||||
WEBRTC_SRC="${WEBRTC_SRC:-${WEBRTC_ROOT}/src}"
|
||||
|
||||
# Output framework consumed by Xcode project.
|
||||
OUT_FRAMEWORK="${OUT_FRAMEWORK:-${ROSETTA_DIR}/Frameworks/WebRTC.xcframework}"
|
||||
|
||||
# Sync tuning (chromium.googlesource rate limits).
|
||||
SYNC_JOBS="${SYNC_JOBS:-1}"
|
||||
SYNC_RETRIES="${SYNC_RETRIES:-8}"
|
||||
SYNC_RETRY_BASE_SEC="${SYNC_RETRY_BASE_SEC:-20}"
|
||||
|
||||
echo "[webrtc-ios] root: ${WEBRTC_ROOT}"
|
||||
echo "[webrtc-ios] src: ${WEBRTC_SRC}"
|
||||
echo "[webrtc-ios] out: ${OUT_FRAMEWORK}"
|
||||
echo "[webrtc-ios] sync jobs: ${SYNC_JOBS}, retries: ${SYNC_RETRIES}"
|
||||
|
||||
export DEPOT_TOOLS_UPDATE=0
|
||||
|
||||
retry_cmd() {
|
||||
local max_attempts="$1"
|
||||
shift
|
||||
local attempt=1
|
||||
local backoff="${SYNC_RETRY_BASE_SEC}"
|
||||
|
||||
while true; do
|
||||
if "$@"; then
|
||||
return 0
|
||||
fi
|
||||
if (( attempt >= max_attempts )); then
|
||||
return 1
|
||||
fi
|
||||
echo "[webrtc-ios] attempt ${attempt}/${max_attempts} failed, retrying in ${backoff}s: $*"
|
||||
sleep "${backoff}"
|
||||
backoff=$(( backoff * 2 ))
|
||||
if (( backoff > 300 )); then
|
||||
backoff=300
|
||||
fi
|
||||
attempt=$(( attempt + 1 ))
|
||||
done
|
||||
}
|
||||
|
||||
sync_with_retry() {
|
||||
local attempt=1
|
||||
while true; do
|
||||
if gclient sync -D --jobs "${SYNC_JOBS}"; then
|
||||
return 0
|
||||
fi
|
||||
if (( attempt >= SYNC_RETRIES )); then
|
||||
echo "[webrtc-ios] ERROR: gclient sync failed after ${SYNC_RETRIES} attempts"
|
||||
return 1
|
||||
fi
|
||||
local wait_sec=$(( SYNC_RETRY_BASE_SEC * attempt ))
|
||||
if (( wait_sec > 300 )); then
|
||||
wait_sec=300
|
||||
fi
|
||||
echo "[webrtc-ios] gclient sync failed (attempt ${attempt}/${SYNC_RETRIES}), sleeping ${wait_sec}s..."
|
||||
sleep "${wait_sec}"
|
||||
attempt=$(( attempt + 1 ))
|
||||
done
|
||||
}
|
||||
|
||||
if ! command -v fetch >/dev/null 2>&1; then
|
||||
echo "[webrtc-ios] ERROR: depot_tools 'fetch' not found in PATH"
|
||||
echo "[webrtc-ios] Run: git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git"
|
||||
echo "[webrtc-ios] Then: export PATH=\$PATH:/path/to/depot_tools"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 1: Checkout WebRTC source for iOS
|
||||
if [[ ! -d "${WEBRTC_SRC}/.git" ]]; then
|
||||
echo "[webrtc-ios] checkout not found, fetching webrtc_ios..."
|
||||
mkdir -p "${WEBRTC_ROOT}"
|
||||
pushd "${WEBRTC_ROOT}" >/dev/null
|
||||
retry_cmd "${SYNC_RETRIES}" fetch --nohooks --no-history webrtc_ios
|
||||
sync_with_retry
|
||||
popd >/dev/null
|
||||
fi
|
||||
|
||||
pushd "${WEBRTC_SRC}" >/dev/null
|
||||
|
||||
# Step 2: Sync to target branch
|
||||
echo "[webrtc-ios] syncing source..."
|
||||
retry_cmd "${SYNC_RETRIES}" git fetch --all --tags
|
||||
|
||||
if [[ -n "${WEBRTC_TAG}" ]]; then
|
||||
retry_cmd "${SYNC_RETRIES}" git checkout "${WEBRTC_TAG}"
|
||||
else
|
||||
if git show-ref --verify --quiet "refs/remotes/origin/${WEBRTC_BRANCH}"; then
|
||||
retry_cmd "${SYNC_RETRIES}" git checkout -B "${WEBRTC_BRANCH}" "origin/${WEBRTC_BRANCH}"
|
||||
else
|
||||
retry_cmd "${SYNC_RETRIES}" git checkout "${WEBRTC_BRANCH}"
|
||||
fi
|
||||
if git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then
|
||||
retry_cmd "${SYNC_RETRIES}" git pull --ff-only
|
||||
fi
|
||||
fi
|
||||
|
||||
sync_with_retry
|
||||
|
||||
# Step 3: Apply E2EE patch
|
||||
echo "[webrtc-ios] applying Rosetta E2EE patch..."
|
||||
git reset --hard
|
||||
echo "[webrtc-ios] apply $(basename "${PATCH_FILE}")"
|
||||
git apply --check "${PATCH_FILE}"
|
||||
git apply "${PATCH_FILE}"
|
||||
echo "[webrtc-ios] patch applied successfully"
|
||||
|
||||
# Step 4: Build iOS framework
|
||||
echo "[webrtc-ios] building xcframework (this takes 30-60 minutes)..."
|
||||
mkdir -p "$(dirname "${OUT_FRAMEWORK}")"
|
||||
|
||||
# Remove previous build if exists
|
||||
rm -rf "${OUT_FRAMEWORK}"
|
||||
|
||||
python3 tools_webrtc/ios/build_ios_libs.py \
|
||||
--build-dir out_rosetta_ios \
|
||||
--output-dir "$(dirname "${OUT_FRAMEWORK}")" \
|
||||
--arch arm64 x86_64 \
|
||||
--extra-gn-args "is_debug=false is_component_build=false rtc_include_tests=false rtc_build_examples=false"
|
||||
|
||||
echo "[webrtc-ios] done"
|
||||
echo "[webrtc-ios] framework: ${OUT_FRAMEWORK}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Remove stasel/WebRTC SPM dependency from Xcode"
|
||||
echo " 2. Add ${OUT_FRAMEWORK} as Embedded Framework"
|
||||
echo " 3. Build and verify: logs should show ad=8 (not ad=0)"
|
||||
|
||||
popd >/dev/null
|
||||
@@ -0,0 +1,56 @@
|
||||
diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc
|
||||
index 17cf859ed8..b9d9ab14c8 100644
|
||||
--- a/audio/channel_receive.cc
|
||||
+++ b/audio/channel_receive.cc
|
||||
@@ -693,10 +693,20 @@ void ChannelReceive::ReceivePacket(const uint8_t* packet,
|
||||
|
||||
const std::vector<uint32_t> csrcs(header.arrOfCSRCs,
|
||||
header.arrOfCSRCs + header.numCSRCs);
|
||||
+ const uint8_t additional_data_bytes[8] = {
|
||||
+ 0,
|
||||
+ 0,
|
||||
+ 0,
|
||||
+ 0,
|
||||
+ static_cast<uint8_t>((header.timestamp >> 24) & 0xff),
|
||||
+ static_cast<uint8_t>((header.timestamp >> 16) & 0xff),
|
||||
+ static_cast<uint8_t>((header.timestamp >> 8) & 0xff),
|
||||
+ static_cast<uint8_t>(header.timestamp & 0xff),
|
||||
+ };
|
||||
const FrameDecryptorInterface::Result decrypt_result =
|
||||
frame_decryptor_->Decrypt(
|
||||
cricket::MEDIA_TYPE_AUDIO, csrcs,
|
||||
- /*additional_data=*/nullptr,
|
||||
+ /*additional_data=*/additional_data_bytes,
|
||||
rtc::ArrayView<const uint8_t>(payload, payload_data_length),
|
||||
decrypted_audio_payload);
|
||||
|
||||
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
|
||||
index 4a2700177b..7ebb501704 100644
|
||||
--- a/audio/channel_send.cc
|
||||
+++ b/audio/channel_send.cc
|
||||
@@ -320,10 +320,23 @@ int32_t ChannelSend::SendRtpAudio(AudioFrameType frameType,
|
||||
|
||||
// Encrypt the audio payload into the buffer.
|
||||
size_t bytes_written = 0;
|
||||
+ const uint32_t additional_data_timestamp =
|
||||
+ rtp_timestamp_without_offset + rtp_rtcp_->StartTimestamp();
|
||||
+ const uint8_t additional_data_bytes[8] = {
|
||||
+ 0,
|
||||
+ 0,
|
||||
+ 0,
|
||||
+ 0,
|
||||
+ static_cast<uint8_t>((additional_data_timestamp >> 24) & 0xff),
|
||||
+ static_cast<uint8_t>((additional_data_timestamp >> 16) & 0xff),
|
||||
+ static_cast<uint8_t>((additional_data_timestamp >> 8) & 0xff),
|
||||
+ static_cast<uint8_t>(additional_data_timestamp & 0xff),
|
||||
+ };
|
||||
+
|
||||
int encrypt_status = frame_encryptor_->Encrypt(
|
||||
cricket::MEDIA_TYPE_AUDIO, rtp_rtcp_->SSRC(),
|
||||
- /*additional_data=*/nullptr, payload, encrypted_audio_payload,
|
||||
- &bytes_written);
|
||||
+ /*additional_data=*/additional_data_bytes, payload,
|
||||
+ encrypted_audio_payload, &bytes_written);
|
||||
if (encrypt_status != 0) {
|
||||
RTC_DLOG(LS_ERROR)
|
||||
<< "Channel::SendData() failed encrypt audio payload: "
|
||||
Reference in New Issue
Block a user