diff --git a/.gitignore b/.gitignore index 3fe5e034..c9c0c465 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ packages/flutter-inspector/.dart_tool/ packages/flutter-inspector/pubspec.lock docs/.vitepress/dist/ docs/.vitepress/cache/ +ios/DerivedData/ cloud/ .cache/ .playwright-mcp/ diff --git a/README.md b/README.md index fa9e63e6..ec3f0512 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,23 @@

SimDeck

- SimDeck is a developer tool built for streamlining mobile app development for coding agents. - Drive iOS Simulators and Android emulators from the CLI using agents, browser, and automated tests on macOS. + SimDeck is a developer tool built for streamlining mobile app development using agents. + Drive iOS Simulators and Android emulators from browser & CLI.


+![Codex Screenshot](./assets/codex-screenshot.png) + ## Try it out ```sh npx simdeck ``` +Open the URL in your IDE of choice, for example in-app browser in Codex. + Install the CLI globally for agentic-use: ```sh @@ -35,11 +39,10 @@ view inside the editor. ## Features -- Local iOS Simulator video over browser-native WebRTC H.264 with VideoToolbox hardware encode and x264 software encode -- Android emulator frames are sourced from emulator gRPC; loopback browsers use raw RGBA over WebRTC, and non-loopback browsers use VideoToolbox-encoded H.264 +- Supports streaming both iOS simulators and Android emulators - Full simulator control & inspection using private iOS accessibility APIs and Android UIAutomator - available using `simdeck` CLI - Real-time screen `describe` command using accessibility view tree - available in token-efficient format for agents -- Simulator app performance gauges for CPU, memory, disk writes, network throughput, hang signals, and stack sampling +- Profiling built-in: CPU, memory, disk writes, network throughput, hang signals, and stack sampling - CoreSimulator chrome asset rendering for device bezels - NativeScript, React Native, Flutter, UIKit and SwiftUI runtime inspector plugins to debug app's view hierarchy live - `simdeck/test` for fast JS-based app tests that can query accessibility state and drive simulator controls @@ -57,7 +60,6 @@ documented in the [GitHub Actions guide](https://simdeck.nativescript.org/guide/ simdeck ``` -This starts a workspace-local foreground daemon, prints local and LAN HTTP URLs plus a pairing code for LAN browsers, and stops when you press `q` or Ctrl-C. To focus a specific simulator by name or UDID, pass it as the only argument: ```sh @@ -69,6 +71,18 @@ simdeck "iPhone 17 Pro Max" The served loopback browser UI receives the generated API access token automatically. LAN clients should pair with the printed code before receiving the API cookie. +For pairing with SimDeck iOS app: + +```sh +simdeck pair +``` + +This starts or refreshes the global LaunchAgent-backed SimDeck service, prints +local, LAN, and Tailscale URLs when available, and shows a QR code with a +`simdeck://pair` link. The QR contains the pairing code plus all detected +non-loopback addresses, so pairing once can save both the LAN and Tailscale +routes with the same service token. + CLI commands automatically use the same warm daemon: ```sh diff --git a/assets/codex-screenshot.png b/assets/codex-screenshot.png new file mode 100644 index 00000000..e1082393 Binary files /dev/null and b/assets/codex-screenshot.png differ diff --git a/cli/XCWChromeRenderer.m b/cli/XCWChromeRenderer.m index abd4feab..a9d99bdd 100644 --- a/cli/XCWChromeRenderer.m +++ b/cli/XCWChromeRenderer.m @@ -12,7 +12,12 @@ @interface XCWChromeRenderer () + (nullable NSDictionary *)inputNamed:(NSString *)buttonName chromeInfo:(NSDictionary *)chromeInfo error:(NSError * _Nullable __autoreleasing *)error; -+ (NSEdgeInsets)devicePaddingForChromeInfo:(NSDictionary *)chromeInfo; ++ (NSEdgeInsets)devicePaddingForChromeInfo:(NSDictionary *)chromeInfo + chromeSize:(CGSize)chromeSize; ++ (CGFloat)inputScaleForChromeInfo:(NSDictionary *)chromeInfo + chromeSize:(CGSize)chromeSize; ++ (CGSize)scaledInputAssetSize:(CGSize)assetSize scale:(CGFloat)scale; ++ (BOOL)input:(NSDictionary *)input shouldDrawOnTopForChromeInfo:(NSDictionary *)chromeInfo; @end @implementation XCWChromeRenderer @@ -394,8 +399,6 @@ + (nullable NSData *)screenshotPNGDataForDeviceName:(NSString *)deviceName BOOL hasModernPhoneSensor = [self shouldRenderPhoneChromeFromSlices:plist sensorName:sensorName]; BOOL hasComposite = !hasModernPhoneSensor && [self compositeAssetPathForChromeInfo:chromeInfo].length > 0; CGFloat screenScale = MAX([self numberValue:plist[@"mainScreenScale"]], 1.0); - CGFloat profileScreenWidth = [self numberValue:plist[@"mainScreenWidth"]]; - CGFloat profileScreenHeight = [self numberValue:plist[@"mainScreenHeight"]]; CGSize profileScreenSize = [self screenSizeForChromeInfo:chromeInfo chromeSize:compositeSize screenScale:screenScale]; @@ -406,17 +409,22 @@ + (nullable NSData *)screenshotPNGDataForDeviceName:(NSString *)deviceName CGFloat screenHeight; CGFloat screenX; CGFloat screenY; - if (hasComposite && pointScreenWidth > 0.0 && pointScreenHeight > 0.0) { + if (watchProfile) { + CGFloat usableHeight = MAX(compositeSize.height - standHeight, 1.0); + screenWidth = pointScreenWidth > 0.0 + ? MIN(pointScreenWidth, compositeSize.width) + : MAX(compositeSize.width - sizingLeft - sizingRight, 1.0); + screenHeight = pointScreenHeight > 0.0 + ? MIN(pointScreenHeight, usableHeight) + : MAX(usableHeight - sizingTop - sizingBottom, 1.0); + screenX = MAX((compositeSize.width - screenWidth) / 2.0, 0.0); + screenY = MAX((usableHeight - screenHeight) / 2.0, 0.0); + } else if (hasComposite && pointScreenWidth > 0.0 && pointScreenHeight > 0.0) { screenWidth = pointScreenWidth; screenHeight = pointScreenHeight; screenX = MAX((compositeSize.width - screenWidth) / 2.0, 0.0); CGFloat usableHeight = compositeSize.height - standHeight; screenY = MAX((usableHeight - screenHeight) / 2.0, bezelTop); - } else if (watchProfile) { - screenWidth = profileScreenWidth; - screenHeight = profileScreenHeight; - screenX = MAX((compositeSize.width - screenWidth) / 2.0, 0.0); - screenY = MAX((compositeSize.height - screenHeight) / 2.0, 0.0); } else { screenX = bezelLeft; screenY = bezelTop; @@ -425,7 +433,7 @@ + (nullable NSData *)screenshotPNGDataForDeviceName:(NSString *)deviceName } CGFloat innerRadius = MAX(rawCornerRadius - MAX(screenX, screenY), 0.0); - CGFloat radiusScale = pointScreenWidth > 0.0 ? screenWidth / pointScreenWidth : 1.0; + CGFloat radiusScale = !watchProfile && pointScreenWidth > 0.0 ? screenWidth / pointScreenWidth : 1.0; CGFloat chromeCornerRadius = innerRadius * radiusScale; CGFloat cornerRadius = chromeCornerRadius; @@ -747,7 +755,7 @@ + (BOOL)drawInputImagesForChromeInfo:(NSDictionary *)chromeInfo continue; } NSDictionary *input = inputValue; - BOOL onTop = [input[@"onTop"] respondsToSelector:@selector(boolValue)] && [input[@"onTop"] boolValue]; + BOOL onTop = [self input:input shouldDrawOnTopForChromeInfo:chromeInfo]; if (onTop != onlyOnTop) { continue; } @@ -756,12 +764,17 @@ + (BOOL)drawInputImagesForChromeInfo:(NSDictionary *)chromeInfo continue; } NSString *assetPath = [self resolvedChromeAssetPathForName:assetName chromePath:chromePath]; - CGSize assetSize = [self PDFPageSizeAtPath:assetPath]; + CGFloat inputScale = [self inputScaleForChromeInfo:chromeInfo chromeSize:size]; + CGSize assetSize = [self scaledInputAssetSize:[self PDFPageSizeAtPath:assetPath] + scale:inputScale]; if (assetSize.width <= 0.0 || assetSize.height <= 0.0) { continue; } - CGRect rect = [self inputFrameForInput:input assetSize:assetSize inSize:size]; + CGRect rect = [self inputFrameForInput:input + assetSize:assetSize + inSize:size + scale:inputScale]; if (![self drawPDFAtPath:assetPath inRect:rect context:context error:error]) { return NO; } @@ -771,7 +784,7 @@ + (BOOL)drawInputImagesForChromeInfo:(NSDictionary *)chromeInfo + (CGRect)fullFrameForChromeInfo:(NSDictionary *)chromeInfo chromeSize:(CGSize)chromeSize { - NSEdgeInsets padding = [self devicePaddingForChromeInfo:chromeInfo]; + NSEdgeInsets padding = [self devicePaddingForChromeInfo:chromeInfo chromeSize:chromeSize]; if (padding.top != 0.0 || padding.left != 0.0 || padding.bottom != 0.0 || padding.right != 0.0) { return CGRectMake(-padding.left, -padding.top, @@ -790,7 +803,7 @@ + (CGRect)fullFrameForChromeInfo:(NSDictionary *)chromeInfo continue; } NSDictionary *input = inputValue; - BOOL onTop = [input[@"onTop"] respondsToSelector:@selector(boolValue)] && [input[@"onTop"] boolValue]; + BOOL onTop = [self input:input shouldDrawOnTopForChromeInfo:chromeInfo]; if (hasComposite && watchProfile && !onTop) { continue; } @@ -799,17 +812,21 @@ + (CGRect)fullFrameForChromeInfo:(NSDictionary *)chromeInfo continue; } NSString *assetPath = [self resolvedChromeAssetPathForName:assetName chromePath:chromePath]; - CGSize assetSize = [self PDFPageSizeAtPath:assetPath]; + CGFloat inputScale = [self inputScaleForChromeInfo:chromeInfo chromeSize:chromeSize]; + CGSize assetSize = [self scaledInputAssetSize:[self PDFPageSizeAtPath:assetPath] + scale:inputScale]; if (assetSize.width <= 0.0 || assetSize.height <= 0.0) { continue; } bounds = CGRectUnion(bounds, [self inputFrameForInput:input assetSize:assetSize inSize:chromeSize + scale:inputScale offsetName:@"normal"]); bounds = CGRectUnion(bounds, [self inputFrameForInput:input assetSize:assetSize inSize:chromeSize + scale:inputScale offsetName:@"rollover"]); } return CGRectIntegral(bounds); @@ -818,23 +835,43 @@ + (CGRect)fullFrameForChromeInfo:(NSDictionary *)chromeInfo + (CGRect)inputFrameForInput:(NSDictionary *)input assetSize:(CGSize)assetSize inSize:(CGSize)size { - return [self inputFrameForInput:input assetSize:assetSize inSize:size offsetName:@"normal"]; + return [self inputFrameForInput:input assetSize:assetSize inSize:size scale:1.0 offsetName:@"normal"]; +} + ++ (CGRect)inputFrameForInput:(NSDictionary *)input + assetSize:(CGSize)assetSize + inSize:(CGSize)size + scale:(CGFloat)scale { + return [self inputFrameForInput:input assetSize:assetSize inSize:size scale:scale offsetName:@"normal"]; } + (CGRect)inputFrameForInput:(NSDictionary *)input assetSize:(CGSize)assetSize inSize:(CGSize)size offsetName:(NSString *)offsetName { + return [self inputFrameForInput:input + assetSize:assetSize + inSize:size + scale:1.0 + offsetName:offsetName]; +} + ++ (CGRect)inputFrameForInput:(NSDictionary *)input + assetSize:(CGSize)assetSize + inSize:(CGSize)size + scale:(CGFloat)scale + offsetName:(NSString *)offsetName { NSDictionary *offsets = [input[@"offsets"] isKindOfClass:[NSDictionary class]] ? input[@"offsets"] : @{}; NSDictionary *normalOffset = [offsets[@"normal"] isKindOfClass:[NSDictionary class]] ? offsets[@"normal"] : nil; NSDictionary *rolloverOffset = [offsets[@"rollover"] isKindOfClass:[NSDictionary class]] ? offsets[@"rollover"] : nil; NSDictionary *requestedOffset = [offsets[offsetName] isKindOfClass:[NSDictionary class]] ? offsets[offsetName] : nil; NSDictionary *primaryOffset = normalOffset ?: rolloverOffset ?: requestedOffset ?: @{}; NSDictionary *secondaryOffset = rolloverOffset ?: normalOffset ?: requestedOffset ?: @{}; - CGFloat normalX = [self numberValue:primaryOffset[@"x"]]; - CGFloat normalY = [self numberValue:primaryOffset[@"y"]]; - CGFloat rolloverX = [self numberValue:secondaryOffset[@"x"]]; - CGFloat rolloverY = [self numberValue:secondaryOffset[@"y"]]; + CGFloat coordinateScale = MAX(scale, 1.0); + CGFloat normalX = [self numberValue:primaryOffset[@"x"]] * coordinateScale; + CGFloat normalY = [self numberValue:primaryOffset[@"y"]] * coordinateScale; + CGFloat rolloverX = [self numberValue:secondaryOffset[@"x"]] * coordinateScale; + CGFloat rolloverY = [self numberValue:secondaryOffset[@"y"]] * coordinateScale; CGFloat restX = normalX; CGFloat restY = normalY; NSString *anchor = [input[@"anchor"] isKindOfClass:[NSString class]] ? input[@"anchor"] : @""; @@ -895,14 +932,16 @@ + (CGRect)inputFrameForInput:(NSDictionary *)input return CGRectMake(x, y, assetSize.width, assetSize.height); } -+ (NSEdgeInsets)devicePaddingForChromeInfo:(NSDictionary *)chromeInfo { ++ (NSEdgeInsets)devicePaddingForChromeInfo:(NSDictionary *)chromeInfo + chromeSize:(CGSize)chromeSize { NSDictionary *json = chromeInfo[@"json"]; NSDictionary *images = [json[@"images"] isKindOfClass:[NSDictionary class]] ? json[@"images"] : @{}; NSDictionary *padding = [images[@"devicePadding"] isKindOfClass:[NSDictionary class]] ? images[@"devicePadding"] : @{}; - return NSEdgeInsetsMake([self numberValue:padding[@"top"]], - [self numberValue:padding[@"left"]], - [self numberValue:padding[@"bottom"]], - [self numberValue:padding[@"right"]]); + CGFloat inputScale = [self inputScaleForChromeInfo:chromeInfo chromeSize:chromeSize]; + return NSEdgeInsetsMake([self numberValue:padding[@"top"]] * inputScale, + [self numberValue:padding[@"left"]] * inputScale, + [self numberValue:padding[@"bottom"]] * inputScale, + [self numberValue:padding[@"right"]] * inputScale); } + (NSArray *> *)buttonProfilesForChromeInfo:(NSDictionary *)chromeInfo @@ -925,12 +964,17 @@ + (NSEdgeInsets)devicePaddingForChromeInfo:(NSDictionary *)chromeInfo { } NSString *assetPath = [self resolvedChromeAssetPathForName:assetName chromePath:chromePath]; - CGSize assetSize = [self PDFPageSizeAtPath:assetPath]; + CGFloat inputScale = [self inputScaleForChromeInfo:chromeInfo chromeSize:chromeSize]; + CGSize assetSize = [self scaledInputAssetSize:[self PDFPageSizeAtPath:assetPath] + scale:inputScale]; if (assetSize.width <= 0.0 || assetSize.height <= 0.0) { continue; } - CGRect rect = [self inputFrameForInput:input assetSize:assetSize inSize:chromeSize]; + CGRect rect = [self inputFrameForInput:input + assetSize:assetSize + inSize:chromeSize + scale:inputScale]; rect = CGRectOffset(rect, chromeOffset.x, chromeOffset.y); if (CGRectGetWidth(rect) <= 0.0 || CGRectGetHeight(rect) <= 0.0) { continue; @@ -945,7 +989,7 @@ + (NSEdgeInsets)devicePaddingForChromeInfo:(NSDictionary *)chromeInfo { NSString *align = [input[@"align"] isKindOfClass:[NSString class]] ? input[@"align"] : @""; NSString *imageDownName = [input[@"imageDown"] isKindOfClass:[NSString class]] ? input[@"imageDown"] : @""; NSString *imageDownDrawMode = [input[@"imageDownDrawMode"] isKindOfClass:[NSString class]] ? input[@"imageDownDrawMode"] : @""; - BOOL onTop = [input[@"onTop"] respondsToSelector:@selector(boolValue)] && [input[@"onTop"] boolValue]; + BOOL onTop = [self input:input shouldDrawOnTopForChromeInfo:chromeInfo]; NSMutableDictionary *button = [@{ @"name": name, @@ -960,12 +1004,12 @@ + (NSEdgeInsets)devicePaddingForChromeInfo:(NSDictionary *)chromeInfo { @"align": align, @"onTop": @(onTop), @"normalOffset": @{ - @"x": @([self numberValue:normalOffset[@"x"]]), - @"y": @([self numberValue:normalOffset[@"y"]]), + @"x": @([self numberValue:normalOffset[@"x"]] * inputScale), + @"y": @([self numberValue:normalOffset[@"y"]] * inputScale), }, @"rolloverOffset": @{ - @"x": @([self numberValue:rolloverOffset[@"x"]]), - @"y": @([self numberValue:rolloverOffset[@"y"]]), + @"x": @([self numberValue:rolloverOffset[@"x"]] * inputScale), + @"y": @([self numberValue:rolloverOffset[@"y"]] * inputScale), }, } mutableCopy]; @@ -1027,13 +1071,54 @@ + (CGSize)screenSizeForChromeInfo:(NSDictionary *)chromeInfo return CGSizeMake(rawWidth / scale, rawHeight / scale); } - if (chromeSize.width > 0.0 && - chromeSize.height > 0.0 && - rawWidth <= chromeSize.width && - rawHeight <= chromeSize.height) { - return CGSizeMake(rawWidth, rawHeight); + return CGSizeMake(rawWidth, rawHeight); +} + ++ (CGFloat)inputScaleForChromeInfo:(NSDictionary *)chromeInfo + chromeSize:(CGSize)chromeSize { + NSDictionary *plist = chromeInfo[@"plist"]; + if (![self isWatchProfile:plist]) { + return 1.0; + } + // watchOS profiles expose screen geometry in raw pixels, while input PDFs + // and chrome offsets are authored against DeviceKit's nominal screen slot. + CGFloat screenScale = MAX([self numberValue:plist[@"mainScreenScale"]], 1.0); + NSDictionary *json = chromeInfo[@"json"]; + NSDictionary *images = [json[@"images"] isKindOfClass:[NSDictionary class]] ? json[@"images"] : @{}; + NSDictionary *sizing = [images[@"sizing"] isKindOfClass:[NSDictionary class]] ? images[@"sizing"] : @{}; + NSDictionary *stand = [images[@"stand"] isKindOfClass:[NSDictionary class]] ? images[@"stand"] : @{}; + CGFloat nominalWidth = chromeSize.width - + [self numberValue:sizing[@"leftWidth"]] - + [self numberValue:sizing[@"rightWidth"]]; + CGFloat nominalHeight = chromeSize.height - + [self numberValue:stand[@"height"]] - + [self numberValue:sizing[@"topHeight"]] - + [self numberValue:sizing[@"bottomHeight"]]; + CGFloat screenWidth = [self numberValue:plist[@"mainScreenWidth"]]; + CGFloat screenHeight = [self numberValue:plist[@"mainScreenHeight"]]; + if (nominalWidth <= 0.0 || nominalHeight <= 0.0 || screenWidth <= 0.0 || screenHeight <= 0.0) { + return screenScale; + } + CGFloat fitScale = MIN(screenWidth / nominalWidth, screenHeight / nominalHeight); + if (!isfinite(fitScale) || fitScale <= 0.0) { + return screenScale; + } + return screenScale * MIN(fitScale, 1.0); +} + ++ (CGSize)scaledInputAssetSize:(CGSize)assetSize scale:(CGFloat)scale { + CGFloat inputScale = MAX(scale, 1.0); + return CGSizeMake(assetSize.width * inputScale, assetSize.height * inputScale); +} + ++ (BOOL)input:(NSDictionary *)input shouldDrawOnTopForChromeInfo:(NSDictionary *)chromeInfo { + if ([input[@"onTop"] respondsToSelector:@selector(boolValue)] && [input[@"onTop"] boolValue]) { + return YES; } - return CGSizeMake(rawWidth / scale, rawHeight / scale); + NSString *type = [input[@"type"] isKindOfClass:[NSString class]] ? input[@"type"] : @""; + NSString *name = [input[@"name"] isKindOfClass:[NSString class]] ? input[@"name"] : @""; + return [self isWatchProfile:chromeInfo[@"plist"]] && + ([type isEqualToString:@"crown"] || [name isEqualToString:@"digital-crown"]); } + (BOOL)drawRasterizedPDFAtPath:(NSString *)path diff --git a/client/package-lock.json b/client/package-lock.json index 6ca02901..022b6cb8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -53,7 +53,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1239,7 +1238,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1433,7 +1431,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -1800,7 +1797,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1842,7 +1838,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2093,7 +2088,6 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/client/src/app/AppShell.tsx b/client/src/app/AppShell.tsx index 46032837..df12b3de 100644 --- a/client/src/app/AppShell.tsx +++ b/client/src/app/AppShell.tsx @@ -209,6 +209,59 @@ function buildAuthenticatedAssetUrl( return url.toString(); } +function chromeStampNumber(value: number | undefined): string { + return Number.isFinite(value) ? String(Math.round((value ?? 0) * 1000)) : "0"; +} + +function chromeStampText(value: string | undefined | null): string { + return (value ?? "").replace(/[^a-zA-Z0-9_.-]+/g, "_"); +} + +function buildChromeProfileAssetStamp(profile: ChromeProfile | null): string { + if (!profile) { + return ""; + } + + const geometryStamp = [ + profile.totalWidth, + profile.totalHeight, + profile.screenX, + profile.screenY, + profile.screenWidth, + profile.screenHeight, + profile.cornerRadius, + ] + .map(chromeStampNumber) + .join("x"); + const maskStamp = profile.hasScreenMask ? "mask" : "nomask"; + const buttonStamp = [...(profile.buttons ?? [])] + .sort((left, right) => left.name.localeCompare(right.name)) + .map((button) => + [ + chromeStampText(button.name), + chromeStampText(button.type), + chromeStampText(button.imageName), + chromeStampText(button.imageDownName), + chromeStampText(button.anchor), + chromeStampText(button.align), + button.onTop ? "top" : "under", + chromeStampNumber(button.x), + chromeStampNumber(button.y), + chromeStampNumber(button.width), + chromeStampNumber(button.height), + chromeStampNumber(button.normalOffset?.x), + chromeStampNumber(button.normalOffset?.y), + chromeStampNumber(button.rolloverOffset?.x), + chromeStampNumber(button.rolloverOffset?.y), + String(button.usagePage ?? ""), + String(button.usage ?? ""), + ].join(","), + ) + .join(";"); + + return [geometryStamp, maskStamp, buttonStamp].filter(Boolean).join(":"); +} + function shouldUseRemoteStreamDefault(apiRoot: string): boolean { if (apiRoot) { return true; @@ -910,23 +963,24 @@ export function AppShell({ button.name.toLowerCase() === "digital-crown", ), ); + const chromeGeometryStamp = buildChromeProfileAssetStamp( + viewportChromeProfile, + ); const chromeAssetStamp = [ selectedSimulator?.deviceTypeIdentifier, selectedSimulator?.deviceTypeName, selectedSimulator?.runtimeIdentifier, selectedSimulator?.runtimeName, selectedSimulator?.udid, - chromeHasInteractiveButtons ? "buttons" : "no-buttons", + chromeGeometryStamp, + chromeHasInteractiveButtons ? "baked-buttons" : "no-buttons", chromeHasCrown ? "crown" : "no-crown", ] .filter(Boolean) .join(":"); + const chromeButtonsRenderedInChrome = chromeHasInteractiveButtons; const chromeUrl = selectedSimulator - ? buildChromeUrl( - selectedSimulator.udid, - chromeAssetStamp, - !chromeHasInteractiveButtons || chromeHasCrown, - ) + ? buildChromeUrl(selectedSimulator.udid, chromeAssetStamp, true) : ""; const chromeButtonUrl = useCallback( (button: string, pressed = false) => @@ -955,10 +1009,12 @@ export function AppShell({ if (viewportChromeProfile.hasScreenMask) { urls.add(buildScreenMaskUrl(selectedSimulator.udid, chromeAssetStamp)); } - for (const button of viewportChromeProfile.buttons ?? []) { - urls.add(chromeButtonUrl(button.name, false)); - if (button.imageDownName) { - urls.add(chromeButtonUrl(button.name, true)); + if (!chromeButtonsRenderedInChrome) { + for (const button of viewportChromeProfile.buttons ?? []) { + urls.add(chromeButtonUrl(button.name, false)); + if (button.imageDownName) { + urls.add(chromeButtonUrl(button.name, true)); + } } } return [...urls].filter(Boolean); @@ -967,6 +1023,7 @@ export function AppShell({ chromeRequired, chromeUrl, chromeAssetStamp, + chromeButtonsRenderedInChrome, selectedSimulator?.udid, viewportChromeProfile, ]); @@ -2716,6 +2773,7 @@ export function AppShell({ chromeLoaded={chromeLoaded} chromeProfile={viewportChromeProfile} chromeRequired={chromeRequired} + chromeButtonsRenderedInChrome={chromeButtonsRenderedInChrome} chromeScreenStyle={viewportScreenStyle} chromeUrl={chromeUrl} chromeButtonUrl={chromeButtonUrl} diff --git a/client/src/features/viewport/DeviceChrome.tsx b/client/src/features/viewport/DeviceChrome.tsx index e548617b..6a220c37 100644 --- a/client/src/features/viewport/DeviceChrome.tsx +++ b/client/src/features/viewport/DeviceChrome.tsx @@ -16,6 +16,7 @@ interface DeviceChromeProps { accessibilityRoots: AccessibilityNode[]; accessibilitySelectedId: string; chromeProfile: ChromeProfile | null; + chromeButtonsRenderedInChrome: boolean; chromeScreenStyle: CSSProperties | null; chromeUrl: string; chromeButtonUrl: (button: string, pressed?: boolean) => string; @@ -61,6 +62,7 @@ export function DeviceChrome({ accessibilityRoots, accessibilitySelectedId, chromeProfile, + chromeButtonsRenderedInChrome, chromeScreenStyle, chromeUrl, chromeButtonUrl, @@ -109,6 +111,7 @@ export function DeviceChrome({ chromeProfile={chromeProfile} layer="under" onEvent={onChromeButtonEvent} + renderImages={!chromeButtonsRenderedInChrome} /> string; chromeProfile: ChromeProfile | null; @@ -228,6 +233,7 @@ function ChromeButtonOverlay({ usagePage?: number, usage?: number, ) => void; + renderImages: boolean; }) { const buttons = chromeProfile?.buttons ?? []; if (!chromeProfile || buttons.length === 0) { @@ -254,6 +260,7 @@ function ChromeButtonOverlay({ chromeButtonUrl={chromeButtonUrl} key={`${button.name}-${button.x}-${button.y}`} onEvent={onEvent} + renderImages={renderImages} totalHeight={chromeProfile.totalHeight} totalWidth={chromeProfile.totalWidth} wireName={wireName} @@ -268,6 +275,7 @@ function ChromeButtonHitTarget({ button, chromeButtonUrl, onEvent, + renderImages, totalHeight, totalWidth, wireName, @@ -280,6 +288,7 @@ function ChromeButtonHitTarget({ usagePage?: number, usage?: number, ) => void; + renderImages: boolean; totalHeight: number; totalWidth: number; wireName: string; @@ -372,7 +381,7 @@ function ChromeButtonHitTarget({ title={label} type="button" > - {downCompositeUnder ? ( + {renderImages && downCompositeUnder ? ( ) : null} - - {!pressed && pressedImageUrl ? ( + {renderImages ? ( + + ) : null} + {renderImages && !pressed && pressedImageUrl ? ( { expect(clamped).toEqual({ x: 0, y: -60 }); }); - it("fits device aspect inside chrome screen rect", () => { + it("uses the exact chrome screen rect even when stream aspect differs", () => { const rect = computeChromeScreenRect( { cornerRadius: 40, @@ -58,8 +58,12 @@ describe("viewportMath", () => { ); expect(rect).not.toBeNull(); - expect(rect?.x).toBeGreaterThanOrEqual(50); - expect(rect?.y).toBeGreaterThanOrEqual(25); + expect(rect).toEqual({ + height: 600, + width: 300, + x: 50, + y: 25, + }); }); it("uses the full chrome screen when stream and profile aspect nearly match", () => { diff --git a/client/src/features/viewport/viewportMath.ts b/client/src/features/viewport/viewportMath.ts index bee52234..97394b1b 100644 --- a/client/src/features/viewport/viewportMath.ts +++ b/client/src/features/viewport/viewportMath.ts @@ -88,44 +88,29 @@ export function mapDisplayedPointToNaturalOrientation( export function computeChromeScreenRect( chromeProfile: ChromeProfile | null, - deviceNaturalSize: Size | null, + _deviceNaturalSize: Size | null, ): ScreenRect | null { if (!chromeProfile) { return null; } - const profileAspect = chromeProfile.screenWidth / chromeProfile.screenHeight; - const deviceAspect = deviceNaturalSize - ? deviceNaturalSize.width / deviceNaturalSize.height - : profileAspect; - if (!deviceAspect || !Number.isFinite(deviceAspect)) { + if ( + !Number.isFinite(chromeProfile.screenX) || + !Number.isFinite(chromeProfile.screenY) || + !Number.isFinite(chromeProfile.screenWidth) || + !Number.isFinite(chromeProfile.screenHeight) || + chromeProfile.screenWidth <= 0 || + chromeProfile.screenHeight <= 0 + ) { return null; } - const aspectDelta = Math.abs(deviceAspect - profileAspect) / profileAspect; - if (aspectDelta <= 0.01) { - return { - height: chromeProfile.screenHeight, - width: chromeProfile.screenWidth, - x: chromeProfile.screenX, - y: chromeProfile.screenY, - }; - } - - let width = chromeProfile.screenWidth; - let height = width / deviceAspect; - let x = chromeProfile.screenX; - let y = chromeProfile.screenY; - - if (height > chromeProfile.screenHeight) { - height = chromeProfile.screenHeight; - width = height * deviceAspect; - x += (chromeProfile.screenWidth - width) / 2; - } else { - y += (chromeProfile.screenHeight - height) / 2; - } - - return { x, y, width, height }; + return { + height: chromeProfile.screenHeight, + width: chromeProfile.screenWidth, + x: chromeProfile.screenX, + y: chromeProfile.screenY, + }; } export function computeChromeScreenBorderRadius( diff --git a/docs/api/rest.md b/docs/api/rest.md index 6d92e03f..13411d15 100644 --- a/docs/api/rest.md +++ b/docs/api/rest.md @@ -19,6 +19,13 @@ LAN browsers can pair with the printed six-digit code through: POST /api/pair ``` +Successful pairing sets the browser auth cookie and also returns the access token +for native clients: + +```json +{ "ok": true, "accessToken": "" } +``` + ## Quick Examples ```sh diff --git a/docs/cli/commands.md b/docs/cli/commands.md index 65755ef0..e110c5ce 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -12,6 +12,7 @@ Replace `simdeck` with `./build/simdeck` when running from a source checkout. | `simdeck -k` | Stop the detached project daemon | | `simdeck -r` | Restart the detached project daemon | | `simdeck ui --open` | Open the browser UI from a daemon | +| `simdeck pair` | Show native iOS pairing code and QR | | `simdeck daemon status` | Show daemon URL, PID, token, and log path | | `simdeck daemon stop` | Stop the current project daemon | | `simdeck daemon killall` | Stop all project daemons | @@ -22,9 +23,15 @@ Examples: ```sh simdeck ui --port 4320 --open simdeck ui --open +simdeck pair simdeck daemon restart --video-codec software --stream-quality low ``` +`simdeck pair` uses the global LaunchAgent-backed service instead of a +project-local daemon. It binds the service for LAN access, preserves an existing +service token and pairing code when present, detects LAN and Tailscale IPv4 +addresses, and prints a `simdeck://pair` QR for the native iOS app. + ## Device Lifecycle ```sh diff --git a/docs/guide/lan-access.md b/docs/guide/lan-access.md index d3167781..6d74a356 100644 --- a/docs/guide/lan-access.md +++ b/docs/guide/lan-access.md @@ -26,9 +26,13 @@ Use an IP address or hostname that the remote device can resolve: ```sh simdeck ui --bind 0.0.0.0 --advertise-host my-mac.local --open simdeck ui --bind 0.0.0.0 --advertise-host 192.168.1.50 --open +simdeck ui --bind 0.0.0.0 --advertise-host 100.101.102.103 --open ``` If you bind to `0.0.0.0` but advertise `localhost`, remote browsers will try to connect to themselves. +Tailscale addresses work like direct HTTP hosts; discovery does not use LAN +broadcast across the tailnet, so use the Tailscale IP or MagicDNS name when +pairing a native client. ## Direct API Access diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md index 06633d58..3a32fc6f 100644 --- a/docs/guide/troubleshooting.md +++ b/docs/guide/troubleshooting.md @@ -189,12 +189,18 @@ Start SimDeck with a LAN bind and reachable advertised host: simdeck ui --bind 0.0.0.0 --advertise-host 192.168.1.50 --open ``` +For native iOS pairing, prefer: + +```sh +simdeck pair +``` + Then check: - The remote browser opens `http://192.168.1.50:4310`. - macOS Firewall allows the port. -- The pairing code matches the current daemon. -- API scripts send the daemon token. +- The pairing code matches the current daemon or global service. +- API scripts send the daemon or service token. See [LAN Access](/guide/lan-access). diff --git a/ios/README.md b/ios/README.md new file mode 100644 index 00000000..a1c5b936 --- /dev/null +++ b/ios/README.md @@ -0,0 +1,10 @@ +# SimDeck Studio iOS + +Native SwiftUI client for SimDeck live sessions. + +- Opens LAN, Tailscale, and SimDeck Studio URLs. +- Uses the daemon's `/api/simulators/{udid}/webrtc/offer` endpoint and renders the H.264 WebRTC track with Metal. +- Sends touch and hardware controls over the `simdeck-control` WebRTC data channel. +- Supports `https://simdeck.djdev.me/simulator/{id}` links through Associated Domains and the `simdeck://` custom URL scheme. + +Open `SimDeckStudio.xcodeproj`, select the `SimDeckStudio` scheme, and run on an iPhone or iPad target. The app display name is `SimDeck`. diff --git a/ios/SimDeckStudio.xcodeproj/project.pbxproj b/ios/SimDeckStudio.xcodeproj/project.pbxproj new file mode 100644 index 00000000..dc6f9109 --- /dev/null +++ b/ios/SimDeckStudio.xcodeproj/project.pbxproj @@ -0,0 +1,443 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 32F600000000000000000101 /* SimDeckStudioApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000201 /* SimDeckStudioApp.swift */; }; + 32F600000000000000000102 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000202 /* Models.swift */; }; + 32F600000000000000000103 /* AppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000203 /* AppModel.swift */; }; + 32F600000000000000000104 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000204 /* ContentView.swift */; }; + 32F600000000000000000105 /* SimulatorStreamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000205 /* SimulatorStreamView.swift */; }; + 32F600000000000000000106 /* SimDeckAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000206 /* SimDeckAPI.swift */; }; + 32F600000000000000000107 /* StudioLinkResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000207 /* StudioLinkResolver.swift */; }; + 32F600000000000000000108 /* SimDeckDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000208 /* SimDeckDiscovery.swift */; }; + 32F600000000000000000109 /* WebRTCClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000209 /* WebRTCClient.swift */; }; + 32F600000000000000000110 /* WebRTCVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000210 /* WebRTCVideoView.swift */; }; + 32F600000000000000000111 /* WebRTC.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000213 /* WebRTC.xcframework */; }; + 32F600000000000000000112 /* WebRTC.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000213 /* WebRTC.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 32F600000000000000000113 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32F600000000000000000214 /* Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 32F600000000000000000201 /* SimDeckStudioApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimDeckStudioApp.swift; sourceTree = ""; }; + 32F600000000000000000202 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; + 32F600000000000000000203 /* AppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModel.swift; sourceTree = ""; }; + 32F600000000000000000204 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 32F600000000000000000205 /* SimulatorStreamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorStreamView.swift; sourceTree = ""; }; + 32F600000000000000000206 /* SimDeckAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimDeckAPI.swift; sourceTree = ""; }; + 32F600000000000000000207 /* StudioLinkResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudioLinkResolver.swift; sourceTree = ""; }; + 32F600000000000000000208 /* SimDeckDiscovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimDeckDiscovery.swift; sourceTree = ""; }; + 32F600000000000000000209 /* WebRTCClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCClient.swift; sourceTree = ""; }; + 32F600000000000000000210 /* WebRTCVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCVideoView.swift; sourceTree = ""; }; + 32F600000000000000000211 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 32F600000000000000000212 /* SimDeckStudio.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SimDeckStudio.entitlements; sourceTree = ""; }; + 32F600000000000000000213 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = WebRTC.xcframework; sourceTree = ""; }; + 32F600000000000000000214 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 32F600000000000000000301 /* SimDeckStudio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimDeckStudio.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 32F600000000000000000401 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 32F600000000000000000111 /* WebRTC.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 32F600000000000000000001 = { + isa = PBXGroup; + children = ( + 32F600000000000000000002 /* SimDeckStudio */, + 32F600000000000000000004 /* Vendor */, + 32F600000000000000000003 /* Products */, + ); + sourceTree = ""; + }; + 32F600000000000000000002 /* SimDeckStudio */ = { + isa = PBXGroup; + children = ( + 32F600000000000000000011 /* App */, + 32F600000000000000000012 /* Discovery */, + 32F600000000000000000013 /* Networking */, + 32F600000000000000000014 /* Streaming */, + 32F600000000000000000015 /* Views */, + 32F600000000000000000214 /* Assets.xcassets */, + 32F600000000000000000211 /* Info.plist */, + 32F600000000000000000212 /* SimDeckStudio.entitlements */, + ); + path = SimDeckStudio; + sourceTree = ""; + }; + 32F600000000000000000003 /* Products */ = { + isa = PBXGroup; + children = ( + 32F600000000000000000301 /* SimDeckStudio.app */, + ); + name = Products; + sourceTree = ""; + }; + 32F600000000000000000004 /* Vendor */ = { + isa = PBXGroup; + children = ( + 32F600000000000000000213 /* WebRTC.xcframework */, + ); + path = Vendor; + sourceTree = ""; + }; + 32F600000000000000000011 /* App */ = { + isa = PBXGroup; + children = ( + 32F600000000000000000201 /* SimDeckStudioApp.swift */, + 32F600000000000000000202 /* Models.swift */, + 32F600000000000000000203 /* AppModel.swift */, + ); + path = App; + sourceTree = ""; + }; + 32F600000000000000000012 /* Discovery */ = { + isa = PBXGroup; + children = ( + 32F600000000000000000208 /* SimDeckDiscovery.swift */, + ); + path = Discovery; + sourceTree = ""; + }; + 32F600000000000000000013 /* Networking */ = { + isa = PBXGroup; + children = ( + 32F600000000000000000206 /* SimDeckAPI.swift */, + 32F600000000000000000207 /* StudioLinkResolver.swift */, + ); + path = Networking; + sourceTree = ""; + }; + 32F600000000000000000014 /* Streaming */ = { + isa = PBXGroup; + children = ( + 32F600000000000000000209 /* WebRTCClient.swift */, + 32F600000000000000000210 /* WebRTCVideoView.swift */, + ); + path = Streaming; + sourceTree = ""; + }; + 32F600000000000000000015 /* Views */ = { + isa = PBXGroup; + children = ( + 32F600000000000000000204 /* ContentView.swift */, + 32F600000000000000000205 /* SimulatorStreamView.swift */, + ); + path = Views; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 32F600000000000000000501 /* SimDeckStudio */ = { + isa = PBXNativeTarget; + buildConfigurationList = 32F600000000000000000702 /* Build configuration list for PBXNativeTarget "SimDeckStudio" */; + buildPhases = ( + 32F600000000000000000601 /* Sources */, + 32F600000000000000000401 /* Frameworks */, + 32F600000000000000000603 /* Embed Frameworks */, + 32F600000000000000000602 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SimDeckStudio; + packageProductDependencies = ( + ); + productName = SimDeckStudio; + productReference = 32F600000000000000000301 /* SimDeckStudio.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 32F600000000000000000701 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2650; + LastUpgradeCheck = 2650; + TargetAttributes = { + 32F600000000000000000501 = { + CreatedOnToolsVersion = 26.5; + }; + }; + }; + buildConfigurationList = 32F600000000000000000703 /* Build configuration list for PBXProject "SimDeckStudio" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 32F600000000000000000001; + packageReferences = ( + ); + productRefGroup = 32F600000000000000000003 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 32F600000000000000000501 /* SimDeckStudio */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 32F600000000000000000603 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 32F600000000000000000112 /* WebRTC.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXResourcesBuildPhase section */ + 32F600000000000000000602 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 32F600000000000000000113 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 32F600000000000000000601 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 32F600000000000000000101 /* SimDeckStudioApp.swift in Sources */, + 32F600000000000000000102 /* Models.swift in Sources */, + 32F600000000000000000103 /* AppModel.swift in Sources */, + 32F600000000000000000104 /* ContentView.swift in Sources */, + 32F600000000000000000105 /* SimulatorStreamView.swift in Sources */, + 32F600000000000000000106 /* SimDeckAPI.swift in Sources */, + 32F600000000000000000107 /* StudioLinkResolver.swift in Sources */, + 32F600000000000000000108 /* SimDeckDiscovery.swift in Sources */, + 32F600000000000000000109 /* WebRTCClient.swift in Sources */, + 32F600000000000000000110 /* WebRTCVideoView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 32F600000000000000000801 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 32F600000000000000000802 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 32F600000000000000000803 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = SimDeckStudio/SimDeckStudio.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 202605180103; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = CS838V553Y; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = SimDeckStudio/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.nativescript.simdeck; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = targeted; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 32F600000000000000000804 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = SimDeckStudio/SimDeckStudio.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 202605180103; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = CS838V553Y; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = SimDeckStudio/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.nativescript.simdeck; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = targeted; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 32F600000000000000000702 /* Build configuration list for PBXNativeTarget "SimDeckStudio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 32F600000000000000000803 /* Debug */, + 32F600000000000000000804 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 32F600000000000000000703 /* Build configuration list for PBXProject "SimDeckStudio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 32F600000000000000000801 /* Debug */, + 32F600000000000000000802 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + + }; + rootObject = 32F600000000000000000701 /* Project object */; +} diff --git a/ios/SimDeckStudio.xcodeproj/xcshareddata/xcschemes/SimDeckStudio.xcscheme b/ios/SimDeckStudio.xcodeproj/xcshareddata/xcschemes/SimDeckStudio.xcscheme new file mode 100644 index 00000000..c818df0a --- /dev/null +++ b/ios/SimDeckStudio.xcodeproj/xcshareddata/xcschemes/SimDeckStudio.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/SimDeckStudio/App/AppModel.swift b/ios/SimDeckStudio/App/AppModel.swift new file mode 100644 index 00000000..cd9bce08 --- /dev/null +++ b/ios/SimDeckStudio/App/AppModel.swift @@ -0,0 +1,1473 @@ +import Foundation +import CryptoKit +import Observation +import SwiftUI +import UIKit +@preconcurrency import WebRTC + +enum StreamState: String { + case idle = "Idle" + case connecting = "Connecting" + case connected = "Connected" + case disconnected = "Disconnected" + case failed = "Failed" +} + +enum HardwareButtonPhase: String { + case down + case up +} + +private struct HardwareButtonControlPayload: Encodable { + let button: String + let durationMs: Int? + let phase: String? + let usagePage: Int? + let usage: Int? +} + +private struct KeyControlPayload: Encodable { + let keyCode: Int + let modifiers: Int +} + +private struct EmptyControlPayload: Encodable {} + +private struct ChromeAssets { + var profile: ChromeProfile? + var image: UIImage? + var screenMask: UIImage? + + var isEmpty: Bool { + profile == nil && image == nil && screenMask == nil + } +} + +@MainActor +@Observable +final class AppModel { + let discovery = SimDeckDiscovery() + private static let savedEndpointsKey = "savedEndpoints" + private static let legacyRecentEndpointsKey = "recentEndpoints" + private static let selectedEndpointKey = "selectedEndpoint" + private static let streamConfigKey = "streamConfig" + private static let hapticsEnabledKey = "hapticsEnabled" + private static let touchOverlayVisibleKey = "touchOverlayVisible" + private static let lastFrameCacheDirectoryName = "LastStreamFrames" + + var endpoint: SimDeckEndpoint? + var savedEndpoints: [SimDeckEndpoint] = [] + var simulators: [SimulatorMetadata] = [] + var selectedSimulatorID: String? + var manualAddress = "" + var manualToken = "" + var pairingCode = "" + var authEndpoint: SimDeckEndpoint? + var status = "Ready" + var isBusy = false + var streamState: StreamState = .idle + var videoSize: CGSize = .zero + var chromeProfile: ChromeProfile? + var chromeImage: UIImage? + var chromeScreenMask: UIImage? + var streamDiagnostics = StreamDiagnostics() + var streamReconnects = 0 + var streamReconnectReason = "" + var bootingSimulatorID: String? + var streamDisplayToken = 0 + var hasCurrentStreamFrame = false + var lastStreamFrame: UIImage? + var streamConfig = AppModel.loadStreamConfig() + var hapticsEnabled = AppModel.loadHapticsEnabled() { + didSet { + UserDefaults.standard.set(hapticsEnabled, forKey: Self.hapticsEnabledKey) + } + } + var touchOverlayVisible = AppModel.loadTouchOverlayVisible() { + didSet { + UserDefaults.standard.set(touchOverlayVisible, forKey: Self.touchOverlayVisibleKey) + } + } + + @ObservationIgnored private var streamClient: WebRTCClient? + @ObservationIgnored private var hasAutoConnected = false + @ObservationIgnored private var isAutoConnecting = false + @ObservationIgnored private var streamRequestGeneration = 0 + @ObservationIgnored private var reconnectTask: Task? + @ObservationIgnored private var lastReconnectStartedAt = Date.distantPast + @ObservationIgnored private var chromeCache: [String: ChromeAssets] = [:] + @ObservationIgnored private var chromeCacheOrder: [String] = [] + @ObservationIgnored private var lastStreamFrameKey: String? + private static let chromeCacheLimit = 24 + + init() { + discovery.onEndpoint = { [weak self] endpoint in + Task { @MainActor in + await self?.autoConnectIfNeeded(endpoint) + } + } + } + + var selectedSimulator: SimulatorMetadata? { + simulators.first { $0.udid == selectedSimulatorID } + } + + var currentStreamClient: WebRTCClient? { streamClient } + var canStopStream: Bool { + streamState != .idle || streamClient != nil + } + + var isSelectedSimulatorBooting: Bool { + bootingSimulatorID == selectedSimulatorID + } + + var availableEndpoints: [SimDeckEndpoint] { + savedEndpoints + automaticEndpoints + } + + var automaticEndpoints: [SimDeckEndpoint] { + var endpoints = discovery.endpoints.filter { discovered in + !savedEndpoints.contains { endpointsRepresentSameServer($0, discovered) } + } + if let endpoint, + !savedEndpoints.contains(where: { endpointsRepresentSameServer($0, endpoint) }), + !endpoints.contains(where: { endpointsRepresentSameServer($0, endpoint) }) { + endpoints.insert(endpoint, at: 0) + } + return endpoints + } + + var selectedEndpointTitle: String { + endpoint?.name ?? "Select Server" + } + + var selectedEndpointSubtitle: String { + endpoint?.baseURL.host(percentEncoded: false) ?? "No SimDeck connected" + } + + var streamNavigationSubtitle: String { + endpoint?.name ?? "No SimDeck connected" + } + + func start() { + loadSavedEndpoints() + if let lastSelectedEndpoint = loadSelectedEndpoint() { + isAutoConnecting = true + discovery.upsert(lastSelectedEndpoint) + Task { + let connected = await connect( + lastSelectedEndpoint, + autoStart: false, + saveEndpoint: false, + presentPairingOnAuth: false + ) + isAutoConnecting = false + if connected { + hasAutoConnected = true + } else { + await autoConnectToAvailableEndpointIfNeeded() + } + } + } + discovery.start() + } + + @discardableResult + func connectManual() async -> Bool { + guard let endpoint = StudioLinkResolver.endpointFromAddress(manualAddress, token: manualToken) else { + status = "Enter a SimDeck URL or host." + return false + } + return await connect(endpoint, autoStart: false, saveEndpoint: true) + } + + func handle(url: URL) { + guard let route = StudioLinkResolver.route(for: url) else { + status = "Unsupported link." + return + } + switch route { + case let .endpoint(endpoint, autoStart): + Task { await connect(endpoint, autoStart: autoStart, saveEndpoint: true) } + case let .pairing(link, autoStart): + Task { await pair(link, autoStart: autoStart) } + } + } + + @discardableResult + func connect( + _ endpoint: SimDeckEndpoint, + autoStart: Bool, + saveEndpoint: Bool = false, + presentPairingOnAuth: Bool = true + ) async -> Bool { + let connectionEndpoint = endpointWithReusableToken(endpoint) + isBusy = true + status = "Connecting to \(connectionEndpoint.name)" + defer { isBusy = false } + + var pendingAuthEndpoint: SimDeckEndpoint? + var lastError: Error? + for candidate in connectionCandidates(for: connectionEndpoint) { + do { + let api = SimDeckAPI(endpoint: candidate) + let health = try await api.health() + var resolvedCandidate = candidate + resolvedCandidate.serverID = health.serverId ?? resolvedCandidate.serverID + resolvedCandidate.alternateBaseURLs = uniquedURLs( + resolvedCandidate.alternateBaseURLs + alternateURLs(from: health, fallbackPort: normalizedPort(for: resolvedCandidate.baseURL)) + ).filter { $0 != resolvedCandidate.baseURL } + let simulators = try await SimDeckAPI(endpoint: resolvedCandidate).simulators() + stopStream() + self.endpoint = resolvedCandidate + self.authEndpoint = nil + self.simulators = simulators + selectedSimulatorID = autoStart + ? resolvedCandidate.preferredSimulatorID + ?? simulators.first(where: \.isBooted)?.udid + ?? simulators.first?.udid + : resolvedCandidate.preferredSimulatorID + if saveEndpoint { + saveUserEndpoint(resolvedCandidate) + } + saveSelectedEndpoint(resolvedCandidate) + status = simulators.isEmpty ? "Connected. No simulators found." : "Connected." + hapticSuccess() + if autoStart, selectedSimulatorID != nil { + await prepareSelectedSimulator() + } + return true + } catch SimDeckAPIError.authRequired { + var pendingEndpoint = candidate + pendingEndpoint.requiresPairing = true + pendingAuthEndpoint = pendingEndpoint + discovery.upsert(pendingEndpoint) + lastError = SimDeckAPIError.authRequired + } catch { + lastError = error + } + } + + if let pendingAuthEndpoint { + status = "Pairing required." + hapticWarning() + guard presentPairingOnAuth else { + return false + } + self.endpoint = pendingAuthEndpoint + self.authEndpoint = pendingAuthEndpoint + self.simulators = [] + self.selectedSimulatorID = nil + manualAddress = pendingAuthEndpoint.baseURL.absoluteString + manualToken = pendingAuthEndpoint.token ?? "" + saveSelectedEndpoint(pendingAuthEndpoint) + return false + } + + if let lastError { + status = lastError.localizedDescription + hapticWarning() + return false + } + + status = "Unable to connect." + hapticWarning() + return false + + } + + @discardableResult + func pair() async -> Bool { + guard let authEndpoint else { return false } + return await pair(endpoint: authEndpoint, code: pairingCode, alternateEndpoints: [], autoStart: false) + } + + @discardableResult + func pair(_ link: SimDeckPairingLink, autoStart: Bool) async -> Bool { + let candidates = uniquedByBaseURL([link.endpoint] + link.alternateEndpoints) + if let token = link.endpoint.token?.nilIfBlank { + savePairedEndpoints(primary: link.endpoint, alternates: link.alternateEndpoints, token: token) + for candidate in candidates { + var pairedEndpoint = candidate + pairedEndpoint.token = token + if await connect(pairedEndpoint, autoStart: autoStart, saveEndpoint: true) { + return true + } + } + return false + } + guard let code = link.pairingCode?.nilIfBlank else { + authEndpoint = link.endpoint + pairingCode = "" + status = "Pairing code missing." + hapticWarning() + return false + } + for candidate in candidates { + let alternates = candidates.filter { $0.baseURL != candidate.baseURL } + if await pair(endpoint: candidate, code: code, alternateEndpoints: alternates, autoStart: autoStart) { + return true + } + } + return false + } + + @discardableResult + private func pair( + endpoint authEndpoint: SimDeckEndpoint, + code: String, + alternateEndpoints: [SimDeckEndpoint], + autoStart: Bool + ) async -> Bool { + isBusy = true + defer { isBusy = false } + do { + let token = try await SimDeckAPI(endpoint: authEndpoint).pair(code: code) + var pairedEndpoint = authEndpoint + if let token { + pairedEndpoint.token = token + manualToken = token + savePairedEndpoints(primary: pairedEndpoint, alternates: alternateEndpoints, token: token) + } + pairingCode = "" + let connected = await connect(pairedEndpoint, autoStart: autoStart, saveEndpoint: true) + if connected { + hapticSuccess() + } + return connected + } catch { + status = error.localizedDescription + hapticWarning() + return false + } + } + + @discardableResult + func useToken() async -> Bool { + guard var authEndpoint else { return false } + authEndpoint.token = manualToken.nilIfBlank + let connected = await connect(authEndpoint, autoStart: false, saveEndpoint: true) + if connected { + hapticSuccess() + } + return connected + } + + func handleScannedPairingPayload(_ value: String) { + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) + if let url = URL(string: trimmed), let route = StudioLinkResolver.route(for: url) { + switch route { + case let .pairing(link, autoStart): + Task { await pair(link, autoStart: autoStart) } + case let .endpoint(endpoint, autoStart): + Task { await connect(endpoint, autoStart: autoStart, saveEndpoint: true) } + } + return + } + let digits = trimmed.filter(\.isNumber) + if !digits.isEmpty { + pairingCode = String(digits.prefix(6)) + hapticSelection() + } else { + status = "That QR code is not a SimDeck pairing link." + hapticWarning() + } + } + + func refreshSimulators() async { + guard let endpoint else { return } + do { + simulators = try await SimDeckAPI(endpoint: endpoint).simulators() + if selectedSimulatorID == nil { + selectedSimulatorID = simulators.first(where: \.isBooted)?.udid ?? simulators.first?.udid + } + status = "Updated." + hapticSelection() + } catch { + status = error.localizedDescription + hapticWarning() + } + } + + func selectSimulator(_ udid: String?) { + guard selectedSimulatorID != udid else { return } + hapticSelection() + selectedSimulatorID = udid + resetStreamPresentation() + guard endpoint != nil, udid != nil else { + stopStream() + return + } + Task { await prepareSelectedSimulator() } + } + + func prepareSelectedSimulator() async { + guard let selectedSimulator else { return } + if selectedSimulator.isBooted { + await startStream() + } else { + await loadSelectedSimulatorChrome() + } + } + + @discardableResult + func startStream(automaticReconnect: Bool = false) async -> Bool { + guard let endpoint, let selectedSimulatorID else { return false } + guard selectedSimulator?.isBooted == true else { + await loadSelectedSimulatorChrome() + return false + } + streamRequestGeneration += 1 + let generation = streamRequestGeneration + stopCurrentStream(resetState: false) + resetStreamPresentation() + streamState = .connecting + status = automaticReconnect ? "Reconnecting WebRTC." : "Starting WebRTC." + do { + let api = SimDeckAPI(endpoint: endpoint) + async let health = try api.health(timeout: 8) + let client = WebRTCClient() + client.onConnectionState = { [weak self] state in + Task { @MainActor in + guard self?.isCurrentStreamRequest(generation, simulatorID: selectedSimulatorID) == true else { return } + self?.streamState = StreamState(peerState: state) + } + } + client.onVideoSize = { [weak self] size in + Task { @MainActor in + guard self?.isCurrentStreamRequest(generation, simulatorID: selectedSimulatorID) == true else { return } + if self?.videoSize != size { + self?.videoSize = size + } + } + } + client.onDiagnostics = { [weak self] diagnostics in + Task { @MainActor in + guard self?.isCurrentStreamRequest(generation, simulatorID: selectedSimulatorID) == true else { return } + self?.streamDiagnostics = diagnostics + } + } + let clientToken = ObjectIdentifier(client) + client.onReconnectNeeded = { [weak self] reason in + Task { @MainActor in + guard let self, + let activeClient = self.streamClient, + self.isCurrentStreamRequest(generation, simulatorID: selectedSimulatorID), + ObjectIdentifier(activeClient) == clientToken else { return } + self.scheduleStreamReconnect(reason: reason) + } + } + let loadedChromeAssets = await chromeAssets(api: api, endpoint: endpoint, simulatorID: selectedSimulatorID, forceRefresh: true) + guard isCurrentStreamRequest(generation, simulatorID: selectedSimulatorID) else { + client.disconnect() + return false + } + applyChromeAssets(loadedChromeAssets) + let loadedHealth = try await health + guard isCurrentStreamRequest(generation, simulatorID: selectedSimulatorID) else { + client.disconnect() + return false + } + let answer = try await client.connect( + api: api, + simulatorID: selectedSimulatorID, + health: loadedHealth, + streamConfig: streamConfig + ) + guard isCurrentStreamRequest(generation, simulatorID: selectedSimulatorID) else { + client.disconnect() + return false + } + streamClient = client + if let video = answer.video, video.width > 0, video.height > 0 { + videoSize = CGSize(width: video.width, height: video.height) + } + status = "WebRTC connected." + if !automaticReconnect { + hapticSuccess() + } + return true + } catch { + guard streamRequestGeneration == generation else { return false } + streamState = .failed + status = error.localizedDescription + if !automaticReconnect { + hapticWarning() + scheduleStreamReconnect(reason: "connect-failed") + } + stopCurrentStream(resetState: false) + return false + } + } + + func loadSelectedSimulatorChrome() async { + guard let endpoint, let selectedSimulatorID else { return } + streamRequestGeneration += 1 + let generation = streamRequestGeneration + stopCurrentStream(resetState: false) + resetStreamPresentation() + streamState = .idle + status = "Loading device chrome." + + let api = SimDeckAPI(endpoint: endpoint) + let loadedChromeAssets = await chromeAssets(api: api, endpoint: endpoint, simulatorID: selectedSimulatorID, forceRefresh: true) + guard isCurrentStreamRequest(generation, simulatorID: selectedSimulatorID) else { return } + applyChromeAssets(loadedChromeAssets) + status = selectedSimulator?.isBooted == true ? "Ready." : "Ready to boot." + } + + func bootSelectedSimulator() async { + guard let endpoint, let selectedSimulatorID, let selectedSimulator else { return } + guard !selectedSimulator.isBooted else { + await startStream() + return + } + bootingSimulatorID = selectedSimulatorID + streamState = .connecting + status = "Booting \(selectedSimulator.name)." + hapticSelection() + do { + let api = SimDeckAPI(endpoint: endpoint) + try await api.bootSimulator(udid: selectedSimulatorID) + simulators = try await api.simulators() + status = "Booted." + bootingSimulatorID = nil + hapticSuccess() + await startStream() + } catch { + streamState = .failed + status = error.localizedDescription + bootingSimulatorID = nil + hapticWarning() + } + } + + func stopStream() { + streamRequestGeneration += 1 + reconnectTask?.cancel() + reconnectTask = nil + bootingSimulatorID = nil + stopCurrentStream(resetState: true) + hapticSelection() + } + + @discardableResult + func createSimulator(_ request: CreateSimulatorRequest) async -> Bool { + guard let endpoint else { + status = "Select a SimDeck server first." + hapticWarning() + return false + } + isBusy = true + status = "Creating simulator." + defer { isBusy = false } + do { + let api = SimDeckAPI(endpoint: endpoint) + let response = try await api.createSimulator(request) + let refreshed = (try? await api.simulators()) ?? [] + if refreshed.isEmpty { + upsertSimulator(response.simulator) + if let pairedWatchSimulator = response.pairedWatchSimulator { + upsertSimulator(pairedWatchSimulator) + } + } else { + simulators = refreshed + } + selectedSimulatorID = response.simulator.udid + resetStreamPresentation() + status = "Created \(response.simulator.name)." + hapticSuccess() + await prepareSelectedSimulator() + return true + } catch { + status = error.localizedDescription + hapticWarning() + return false + } + } + + private func stopCurrentStream(resetState: Bool) { + streamClient?.disconnect() + streamClient = nil + if resetState { + streamState = .idle + resetStreamPresentation() + } + } + + func sendTouch(location: CGPoint, in screenFrame: CGRect, phase: String) { + guard let point = normalizedTouchPoint(location: location, in: screenFrame) else { return } + streamClient?.sendTouch(x: Double(point.x), y: Double(point.y), phase: phase) + } + + func sendEdgeTouch(location: CGPoint, in screenFrame: CGRect, phase: String, edge: String) { + guard let point = normalizedTouchPoint(location: location, in: screenFrame) else { return } + streamClient?.sendEdgeTouch(x: Double(point.x), y: Double(point.y), phase: phase, edge: edge) + } + + func normalizedTouchPoint(location: CGPoint, in screenFrame: CGRect) -> CGPoint? { + guard screenFrame.width > 0, screenFrame.height > 0 else { return nil } + let x = ((location.x - screenFrame.minX) / screenFrame.width).clamped(to: 0...1) + let y = ((location.y - screenFrame.minY) / screenFrame.height).clamped(to: 0...1) + return CGPoint(x: x, y: y) + } + + func markStreamFrameRendered(displayToken: Int) { + guard displayToken == streamDisplayToken else { return } + hasCurrentStreamFrame = true + } + + func updateLastStreamFrame(_ image: UIImage, displayToken: Int) { + guard displayToken == streamDisplayToken, + let endpoint, + let selectedSimulatorID else { + return + } + lastStreamFrameKey = lastFrameCacheKey(endpoint: endpoint, simulatorID: selectedSimulatorID) + lastStreamFrame = image + if videoSize == .zero { + videoSize = image.size + } + persistLastStreamFrame(image, endpoint: endpoint, simulatorID: selectedSimulatorID) + } + + func sendTouch(x: Double, y: Double, phase: String) { + streamClient?.sendTouch(x: x, y: y, phase: phase) + } + + func sendKeyboardText(_ text: String) { + for character in text { + guard let key = Self.keyControl(for: character) else { + status = "Unsupported keyboard input." + hapticWarning() + continue + } + sendKey(keyCode: key.keyCode, modifiers: key.modifiers) + } + } + + func sendKeyboardBackspace() { + sendKey(keyCode: 42, modifiers: 0) + } + + func dismissSimulatorKeyboard() { + let sent = streamClient?.dismissSimulatorKeyboard() ?? false + guard !sent else { return } + Task { + await postDismissKeyboard() + } + } + + @discardableResult + func sendKey(keyCode: Int, modifiers: Int = 0) -> Bool { + guard selectedSimulatorID != nil, (0...65_535).contains(keyCode) else { return false } + let sent = streamClient?.sendKey(keyCode: keyCode, modifiers: modifiers) ?? false + guard !sent else { return true } + Task { + await postKey(keyCode: keyCode, modifiers: modifiers) + } + return false + } + + func sendHome() { + tapHardwareButton(named: "home") + } + + func sendAppSwitcher() { + hapticImpact() + streamClient?.sendAppSwitcher() + } + + func sendLock() { + tapHardwareButton(named: "power") + } + + func sendHardwareButton(named button: String, phase: HardwareButtonPhase, usagePage: Int? = nil, usage: Int? = nil) { + guard selectedSimulatorID != nil else { return } + switch phase { + case .down: + hapticImpact() + case .up: + hapticSelection() + } + let sent = streamClient?.sendHardwareButton( + button: button, + phase: phase.rawValue, + usagePage: usagePage, + usage: usage + ) ?? false + guard !sent else { return } + Task { + await postHardwareButton( + named: button, + durationMs: nil, + phase: phase, + usagePage: usagePage, + usage: usage + ) + } + } + + func tapHardwareButton(named button: String, usagePage: Int? = nil, usage: Int? = nil, durationMs: Int = 80) { + guard selectedSimulatorID != nil else { return } + hapticImpact() + let sent = streamClient?.pressHardwareButton( + button: button, + durationMs: durationMs, + usagePage: usagePage, + usage: usage + ) ?? false + guard !sent else { return } + Task { + await postHardwareButton( + named: button, + durationMs: durationMs, + phase: nil, + usagePage: usagePage, + usage: usage + ) + } + } + + func rotateLeft() { + hapticSelection() + streamClient?.sendRotateLeft() + } + + func rotateRight() { + hapticSelection() + streamClient?.sendRotateRight() + } + + func toggleAppearance() { + guard selectedSimulatorID != nil else { return } + hapticSelection() + let sent = streamClient?.sendToggleAppearance() ?? false + guard !sent else { return } + Task { + await postToggleAppearance() + } + } + + func requestKeyframe() { + hapticImpact() + streamClient?.requestKeyframe() + } + + func retryStream() { + reconnectTask?.cancel() + reconnectTask = nil + hapticSelection() + Task { + await startStream() + } + } + + func setStreamEncoder(_ encoder: StreamEncoder) { + updateStreamConfig { $0.encoder = encoder } + } + + func setStreamFPS(_ fps: Int) { + updateStreamConfig { $0.fps = fps } + } + + func setStreamQuality(_ quality: StreamQualityPreset) { + updateStreamConfig { $0.quality = quality } + } + + func setTouchOverlayVisible(_ isVisible: Bool) { + guard touchOverlayVisible != isVisible else { return } + touchOverlayVisible = isVisible + hapticSelection() + } + + private func autoConnectIfNeeded(_ endpoint: SimDeckEndpoint) async { + guard !hasAutoConnected, !isAutoConnecting, self.endpoint == nil, authEndpoint == nil else { return } + await autoConnectToAvailableEndpointIfNeeded(preferredEndpoint: endpoint) + } + + private func autoConnectToAvailableEndpointIfNeeded(preferredEndpoint: SimDeckEndpoint? = nil) async { + guard !hasAutoConnected, !isAutoConnecting, self.endpoint == nil, authEndpoint == nil else { return } + let candidates = autoConnectCandidates(preferredEndpoint: preferredEndpoint) + guard !candidates.isEmpty else { return } + + isAutoConnecting = true + var connected = false + for candidate in candidates { + connected = await connect( + candidate, + autoStart: false, + saveEndpoint: false, + presentPairingOnAuth: false + ) + if connected { + break + } + } + isAutoConnecting = false + if connected { + hasAutoConnected = true + } + } + + private func autoConnectCandidates(preferredEndpoint: SimDeckEndpoint?) -> [SimDeckEndpoint] { + let orderedEndpoints = [preferredEndpoint].compactMap(\.self) + + discovery.endpoints + + savedEndpoints + return uniqued(orderedEndpoints) + .map(endpointWithReusableToken) + .filter { endpoint in + !endpoint.requiresPairing || endpoint.token?.nilIfBlank != nil + } + } + + private func isCurrentStreamRequest(_ generation: Int, simulatorID: String) -> Bool { + streamRequestGeneration == generation && selectedSimulatorID == simulatorID + } + + func handleScenePhase(_ phase: ScenePhase) { + switch phase { + case .active: + streamClient?.appDidBecomeActive() + if streamClient == nil, streamState == .disconnected || streamState == .failed { + scheduleStreamReconnect(reason: "foreground") + } + case .background: + streamClient?.appDidEnterBackground() + case .inactive: + break + @unknown default: + break + } + } + + func scheduleStreamReconnect(reason: String) { + guard endpoint != nil, selectedSimulatorID != nil, selectedSimulator?.isBooted == true else { return } + guard streamState != .connecting else { return } + reconnectTask?.cancel() + reconnectTask = Task { @MainActor [weak self] in + guard let self else { return } + let elapsed = Date().timeIntervalSince(self.lastReconnectStartedAt) + if elapsed < 1.5 { + try? await Task.sleep(for: .milliseconds(Int((1.5 - elapsed) * 1000))) + } + var attempt = 0 + while !Task.isCancelled, + self.endpoint != nil, + self.selectedSimulatorID != nil, + self.selectedSimulator?.isBooted == true { + attempt += 1 + self.streamReconnects += 1 + self.streamReconnectReason = reason + self.lastReconnectStartedAt = Date() + self.status = attempt == 1 + ? (reason == "foreground" ? "Resuming stream." : "Recovering stream.") + : "Retrying stream." + let connected = await self.startStream(automaticReconnect: true) + guard !connected else { return } + let delay = min(10.0, pow(1.8, Double(attempt))) + self.status = "Retrying stream in \(Int(delay.rounded(.up)))s." + try? await Task.sleep(for: .milliseconds(Int(delay * 1000))) + } + } + } + + private func postHardwareButton( + named button: String, + durationMs: Int?, + phase: HardwareButtonPhase?, + usagePage: Int?, + usage: Int? + ) async { + guard let endpoint, let selectedSimulatorID else { return } + do { + let payload = HardwareButtonControlPayload( + button: button, + durationMs: durationMs, + phase: phase?.rawValue, + usagePage: usagePage, + usage: usage + ) + let encodedID = selectedSimulatorID.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? selectedSimulatorID + try await SimDeckAPI(endpoint: endpoint).postControl(payload, path: "/api/simulators/\(encodedID)/button") + } catch { + status = error.localizedDescription + hapticWarning() + } + } + + private func postKey(keyCode: Int, modifiers: Int) async { + guard let endpoint, let selectedSimulatorID else { return } + do { + let payload = KeyControlPayload(keyCode: keyCode, modifiers: modifiers) + let encodedID = selectedSimulatorID.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? selectedSimulatorID + try await SimDeckAPI(endpoint: endpoint).postControl(payload, path: "/api/simulators/\(encodedID)/key") + } catch { + status = error.localizedDescription + hapticWarning() + } + } + + private func postDismissKeyboard() async { + guard let endpoint, let selectedSimulatorID else { return } + do { + let encodedID = selectedSimulatorID.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? selectedSimulatorID + try await SimDeckAPI(endpoint: endpoint).postControl( + EmptyControlPayload(), + path: "/api/simulators/\(encodedID)/dismiss-keyboard" + ) + } catch { + status = error.localizedDescription + hapticWarning() + } + } + + private func postToggleAppearance() async { + guard let endpoint, let selectedSimulatorID else { return } + do { + let encodedID = selectedSimulatorID.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? selectedSimulatorID + try await SimDeckAPI(endpoint: endpoint).postControl( + EmptyControlPayload(), + path: "/api/simulators/\(encodedID)/toggle-appearance" + ) + } catch { + status = error.localizedDescription + hapticWarning() + } + } + + private func resetStreamPresentation() { + streamDisplayToken &+= 1 + if !applyCachedChromeAssetsForSelection() { + chromeProfile = nil + chromeImage = nil + chromeScreenMask = nil + } + if !applyCachedLastStreamFrameForSelection() { + lastStreamFrameKey = nil + lastStreamFrame = nil + videoSize = .zero + } else if let lastStreamFrame { + videoSize = lastStreamFrame.size + } + hasCurrentStreamFrame = false + streamDiagnostics = StreamDiagnostics() + } + + private func chromeAssets( + api: SimDeckAPI, + endpoint: SimDeckEndpoint, + simulatorID: String, + forceRefresh: Bool = false + ) async -> ChromeAssets { + if !forceRefresh, let cached = cachedChromeAssets(endpoint: endpoint, simulatorID: simulatorID) { + return cached + } + + let loadedProfile = try? await api.chromeProfile(udid: simulatorID) + let assetStamp = loadedProfile?.assetStamp + let loadedImage = try? await api.chromeImage(udid: simulatorID, stamp: assetStamp) + let loadedScreenMask: UIImage? + if loadedProfile?.hasScreenMask == true { + loadedScreenMask = try? await api.screenMaskImage(udid: simulatorID, stamp: assetStamp) + } else { + loadedScreenMask = nil + } + let loadedAssets = ChromeAssets(profile: loadedProfile, image: loadedImage, screenMask: loadedScreenMask) + cacheChromeAssets(loadedAssets, endpoint: endpoint, simulatorID: simulatorID) + return loadedAssets + } + + @discardableResult + private func applyCachedChromeAssetsForSelection() -> Bool { + guard let endpoint, let selectedSimulatorID, + let cached = cachedChromeAssets(endpoint: endpoint, simulatorID: selectedSimulatorID) else { + return false + } + applyChromeAssets(cached) + return true + } + + private func applyChromeAssets(_ assets: ChromeAssets) { + chromeProfile = assets.profile + chromeImage = assets.image + chromeScreenMask = assets.screenMask + } + + private func cachedChromeAssets(endpoint: SimDeckEndpoint, simulatorID: String) -> ChromeAssets? { + let key = chromeCacheKey(endpoint: endpoint, simulatorID: simulatorID) + guard let cached = chromeCache[key] else { return nil } + markChromeCacheKeyUsed(key) + return cached + } + + private func cacheChromeAssets(_ assets: ChromeAssets, endpoint: SimDeckEndpoint, simulatorID: String) { + guard !assets.isEmpty else { return } + let key = chromeCacheKey(endpoint: endpoint, simulatorID: simulatorID) + chromeCache[key] = assets + markChromeCacheKeyUsed(key) + while chromeCacheOrder.count > Self.chromeCacheLimit, let evictedKey = chromeCacheOrder.first { + chromeCacheOrder.removeFirst() + chromeCache[evictedKey] = nil + } + } + + private func markChromeCacheKeyUsed(_ key: String) { + chromeCacheOrder.removeAll { $0 == key } + chromeCacheOrder.append(key) + } + + private func chromeCacheKey(endpoint: SimDeckEndpoint, simulatorID: String) -> String { + "\(endpoint.baseURL.absoluteString)|\(simulatorID)" + } + + @discardableResult + private func applyCachedLastStreamFrameForSelection() -> Bool { + guard let endpoint, let selectedSimulatorID else { + return false + } + let cacheKey = lastFrameCacheKey(endpoint: endpoint, simulatorID: selectedSimulatorID) + if lastStreamFrameKey == cacheKey, lastStreamFrame != nil { + return true + } + guard let image = loadLastStreamFrame(endpoint: endpoint, simulatorID: selectedSimulatorID) else { + return false + } + lastStreamFrameKey = cacheKey + lastStreamFrame = image + return true + } + + private func loadLastStreamFrame(endpoint: SimDeckEndpoint, simulatorID: String) -> UIImage? { + guard let url = lastFrameCacheURL(endpoint: endpoint, simulatorID: simulatorID), + let data = try? Data(contentsOf: url) else { + return nil + } + return UIImage(data: data) + } + + private func persistLastStreamFrame(_ image: UIImage, endpoint: SimDeckEndpoint, simulatorID: String) { + guard let url = lastFrameCacheURL(endpoint: endpoint, simulatorID: simulatorID), + let data = image.jpegData(compressionQuality: 0.78) else { + return + } + Task.detached(priority: .utility) { + do { + try FileManager.default.createDirectory( + at: url.deletingLastPathComponent(), + withIntermediateDirectories: true + ) + try data.write(to: url, options: [.atomic]) + } catch { + #if DEBUG + print("Unable to persist SimDeck frame cache: \(error.localizedDescription)") + #endif + } + } + } + + private func lastFrameCacheURL(endpoint: SimDeckEndpoint, simulatorID: String) -> URL? { + guard let baseURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { + return nil + } + return baseURL + .appendingPathComponent(Self.lastFrameCacheDirectoryName, isDirectory: true) + .appendingPathComponent("\(lastFrameCacheKey(endpoint: endpoint, simulatorID: simulatorID)).jpg") + } + + private func lastFrameCacheKey(endpoint: SimDeckEndpoint, simulatorID: String) -> String { + let source = "\(endpoint.baseURL.absoluteString)|\(simulatorID)" + let digest = SHA256.hash(data: Data(source.utf8)) + return digest.map { String(format: "%02x", $0) }.joined() + } + + private func updateStreamConfig(_ update: (inout StreamConfig) -> Void) { + var next = streamConfig + update(&next) + guard next != streamConfig else { return } + streamConfig = next + saveStreamConfig(next) + streamClient?.applyStreamQuality(next) + if streamClient != nil { + status = "Stream set to \(next.summary)." + } + hapticSelection() + } + + private func upsertSimulator(_ simulator: SimulatorMetadata) { + if let index = simulators.firstIndex(where: { $0.udid == simulator.udid }) { + simulators[index] = simulator + } else { + simulators.insert(simulator, at: 0) + } + } + + private static func keyControl(for character: Character) -> (keyCode: Int, modifiers: Int)? { + let shift = 1 << 0 + let value = String(character) + if let keyCode = unshiftedHIDUsage[value] { + return (keyCode, 0) + } + if let keyCode = shiftedHIDUsage[value] { + return (keyCode, shift) + } + return nil + } + + private static let unshiftedHIDUsage: [String: Int] = [ + "a": 4, "b": 5, "c": 6, "d": 7, "e": 8, "f": 9, "g": 10, "h": 11, "i": 12, + "j": 13, "k": 14, "l": 15, "m": 16, "n": 17, "o": 18, "p": 19, "q": 20, + "r": 21, "s": 22, "t": 23, "u": 24, "v": 25, "w": 26, "x": 27, "y": 28, "z": 29, + "1": 30, "2": 31, "3": 32, "4": 33, "5": 34, "6": 35, "7": 36, "8": 37, "9": 38, "0": 39, + "\n": 40, "\r": 40, "\u{1B}": 41, "\t": 43, " ": 44, + "-": 45, "=": 46, "[": 47, "]": 48, "\\": 49, ";": 51, "'": 52, + "`": 53, ",": 54, ".": 55, "/": 56, + "\u{2019}": 52, "\u{2018}": 52, "\u{2013}": 45, "\u{2014}": 45 + ] + + private static let shiftedHIDUsage: [String: Int] = [ + "A": 4, "B": 5, "C": 6, "D": 7, "E": 8, "F": 9, "G": 10, "H": 11, "I": 12, + "J": 13, "K": 14, "L": 15, "M": 16, "N": 17, "O": 18, "P": 19, "Q": 20, + "R": 21, "S": 22, "T": 23, "U": 24, "V": 25, "W": 26, "X": 27, "Y": 28, "Z": 29, + "!": 30, "@": 31, "#": 32, "$": 33, "%": 34, "^": 35, "&": 36, "*": 37, "(": 38, ")": 39, + "_": 45, "+": 46, "{": 47, "}": 48, "|": 49, ":": 51, "\"": 52, + "~": 53, "<": 54, ">": 55, "?": 56, + "\u{201C}": 52, "\u{201D}": 52 + ] + + private func loadSavedEndpoints() { + let data = UserDefaults.standard.data(forKey: Self.savedEndpointsKey) + ?? UserDefaults.standard.data(forKey: Self.legacyRecentEndpointsKey) + guard let data, + let endpoints = try? JSONDecoder().decode([SimDeckEndpoint].self, from: data) else { + return + } + savedEndpoints = uniqued(endpoints).map { endpoint in + var saved = endpoint + if saved.source == .recent { + saved.source = .manual + } + return saved + } + persistSavedEndpoints() + } + + func saveUserEndpoint(_ endpoint: SimDeckEndpoint) { + var saved = endpoint + if saved.source == .recent { + saved.source = .manual + } + saved.requiresPairing = false + if let existing = savedEndpoints.first(where: { endpointsRepresentSameServer($0, saved) }) { + saved = mergedEndpoint(existing, saved) + saved.source = .manual + saved.requiresPairing = false + } + savedEndpoints.removeAll { endpointsRepresentSameServer($0, saved) } + savedEndpoints.insert(saved, at: 0) + savedEndpoints = Array(uniqued(savedEndpoints).prefix(12)) + persistSavedEndpoints() + } + + func renameSavedEndpoint(_ endpoint: SimDeckEndpoint, to name: String) { + let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty, + let index = savedEndpoints.firstIndex(where: { endpointsRepresentSameServer($0, endpoint) }) else { + return + } + savedEndpoints[index].name = trimmed + if var current = self.endpoint, endpointsRepresentSameServer(current, endpoint) { + current.name = trimmed + self.endpoint = current + saveSelectedEndpoint(current) + } + if var pending = authEndpoint, endpointsRepresentSameServer(pending, endpoint) { + pending.name = trimmed + authEndpoint = pending + } + persistSavedEndpoints() + } + + func deleteSavedEndpoint(_ endpoint: SimDeckEndpoint) { + savedEndpoints.removeAll { endpointsRepresentSameServer($0, endpoint) } + if let current = self.endpoint, endpointsRepresentSameServer(current, endpoint) { + UserDefaults.standard.removeObject(forKey: Self.selectedEndpointKey) + } + persistSavedEndpoints() + hapticSelection() + } + + private func savePairedEndpoints(primary: SimDeckEndpoint, alternates: [SimDeckEndpoint], token: String) { + for endpoint in Array(alternates.reversed()) + [primary] { + var saved = endpoint + saved.token = token + saved.requiresPairing = false + saveUserEndpoint(saved) + } + } + + private func endpointWithReusableToken(_ endpoint: SimDeckEndpoint) -> SimDeckEndpoint { + guard endpoint.token?.nilIfBlank == nil, + let token = reusableToken(for: endpoint) else { + return endpoint + } + var endpoint = endpoint + endpoint.token = token + endpoint.requiresPairing = false + return endpoint + } + + private func reusableToken(for endpoint: SimDeckEndpoint) -> String? { + let storedEndpoints = savedEndpoints + [self.endpoint, loadSelectedEndpoint()].compactMap(\.self) + if let serverID = endpoint.serverID?.nilIfBlank, + let token = storedEndpoints + .first(where: { $0.serverID == serverID })? + .token? + .nilIfBlank { + return token + } + if let exactToken = storedEndpoints + .first(where: { endpointsRepresentSameServer($0, endpoint) })? + .token? + .nilIfBlank { + return exactToken + } + + guard hostCanShareSimDeckToken(endpoint.baseURL.host(percentEncoded: false)) else { + return nil + } + let port = normalizedPort(for: endpoint.baseURL) + return storedEndpoints + .first { stored in + stored.token?.nilIfBlank != nil + && normalizedPort(for: stored.baseURL) == port + && hostCanShareSimDeckToken(stored.baseURL.host(percentEncoded: false)) + }? + .token? + .nilIfBlank + } + + private func connectionCandidates(for endpoint: SimDeckEndpoint) -> [SimDeckEndpoint] { + let primary = endpointWithReusableToken(endpoint) + let alternateEndpoints = preferredAlternateURLs(for: primary).map { url in + var alternate = primary + alternate.baseURL = url.normalizedSimDeckBaseURL() + alternate.source = endpointSource(for: alternate.baseURL) + alternate.alternateBaseURLs = ([primary.baseURL] + primary.alternateBaseURLs) + .map { $0.normalizedSimDeckBaseURL() } + .filter { $0 != alternate.baseURL } + return endpointWithReusableToken(alternate) + } + return uniquedByBaseURL([primary] + alternateEndpoints) + } + + private func preferredAlternateURLs(for endpoint: SimDeckEndpoint) -> [URL] { + let urls = endpoint.alternateBaseURLs.filter { $0 != endpoint.baseURL } + let preferred = urls.sorted { + endpointSourceRank(endpointSource(for: $0)) < endpointSourceRank(endpointSource(for: $1)) + } + return preferred + } + + private func endpointSource(for url: URL) -> EndpointSource { + guard let host = url.host(percentEncoded: false)?.lowercased() else { + return .manual + } + let parts = host.split(separator: ".").compactMap { UInt8($0) } + if parts.count == 4 && parts[0] == 100 && (parts[1] & 0b1100_0000) == 0b0100_0000 { + return .tailscale + } + if host.hasSuffix(".local") { + return .bonjour + } + if hostCanShareSimDeckToken(host) { + return .lan + } + return .manual + } + + private func endpointSourceRank(_ source: EndpointSource) -> Int { + switch source { + case .bonjour: 0 + case .lan: 1 + case .tailscale: 2 + case .studioLink: 3 + case .manual: 4 + case .recent: 5 + } + } + + private func endpointsRepresentSameServer(_ lhs: SimDeckEndpoint, _ rhs: SimDeckEndpoint) -> Bool { + if let lhsID = lhs.serverID?.nilIfBlank, + let rhsID = rhs.serverID?.nilIfBlank { + return lhsID == rhsID + } + return lhs.baseURL == rhs.baseURL + || lhs.alternateBaseURLs.contains(rhs.baseURL) + || rhs.alternateBaseURLs.contains(lhs.baseURL) + } + + private func mergedEndpoint(_ lhs: SimDeckEndpoint, _ rhs: SimDeckEndpoint) -> SimDeckEndpoint { + let preferred = endpointSourceRank(lhs.source) <= endpointSourceRank(rhs.source) ? lhs : rhs + let other = preferred.baseURL == lhs.baseURL ? rhs : lhs + var merged = preferred + merged.serverID = preferred.serverID ?? other.serverID + merged.token = preferred.token ?? other.token + merged.preferredSimulatorID = preferred.preferredSimulatorID ?? other.preferredSimulatorID + merged.requiresPairing = preferred.requiresPairing && other.requiresPairing + merged.alternateBaseURLs = uniquedURLs( + [lhs.baseURL, rhs.baseURL] + lhs.alternateBaseURLs + rhs.alternateBaseURLs + ) + .filter { $0 != merged.baseURL } + return merged + } + + private func uniquedURLs(_ urls: [URL]) -> [URL] { + var seen = Set() + var result: [URL] = [] + for url in urls.map({ $0.normalizedSimDeckBaseURL() }) where seen.insert(url).inserted { + result.append(url) + } + return result + } + + private func uniquedByBaseURL(_ endpoints: [SimDeckEndpoint]) -> [SimDeckEndpoint] { + var seen = Set() + var result: [SimDeckEndpoint] = [] + for endpoint in endpoints where seen.insert(endpoint.baseURL).inserted { + result.append(endpoint) + } + return result + } + + private func alternateURLs(from health: HealthResponse, fallbackPort: Int) -> [URL] { + guard let advertiseHost = health.advertiseHost?.nilIfBlank else { return [] } + var components = URLComponents() + components.scheme = "http" + components.host = advertiseHost + components.port = health.httpPort ?? fallbackPort + return components.url.map { [$0] } ?? [] + } + + private func normalizedPort(for url: URL) -> Int { + if let port = url.port { + return port + } + return url.scheme?.lowercased() == "https" ? 443 : 80 + } + + private func hostCanShareSimDeckToken(_ host: String?) -> Bool { + guard let host = host?.lowercased(), !host.isEmpty else { + return false + } + if host == "localhost" || host.hasSuffix(".local") { + return true + } + let parts = host.split(separator: ".").compactMap { UInt8($0) } + guard parts.count == 4 else { + return false + } + return parts[0] == 10 + || parts[0] == 127 + || (parts[0] == 169 && parts[1] == 254) + || (parts[0] == 172 && (16...31).contains(parts[1])) + || (parts[0] == 192 && parts[1] == 168) + || (parts[0] == 100 && (parts[1] & 0b1100_0000) == 0b0100_0000) + } + + private func persistSavedEndpoints() { + if let data = try? JSONEncoder().encode(savedEndpoints) { + UserDefaults.standard.set(data, forKey: Self.savedEndpointsKey) + } + } + + private func uniqued(_ endpoints: [SimDeckEndpoint]) -> [SimDeckEndpoint] { + var result: [SimDeckEndpoint] = [] + for endpoint in endpoints { + if let index = result.firstIndex(where: { endpointsRepresentSameServer($0, endpoint) }) { + result[index] = mergedEndpoint(result[index], endpoint) + } else { + result.append(endpoint) + } + } + return result + } + + private func loadSelectedEndpoint() -> SimDeckEndpoint? { + guard let data = UserDefaults.standard.data(forKey: Self.selectedEndpointKey) else { + return nil + } + return try? JSONDecoder().decode(SimDeckEndpoint.self, from: data) + } + + private func saveSelectedEndpoint(_ endpoint: SimDeckEndpoint) { + if let data = try? JSONEncoder().encode(endpoint) { + UserDefaults.standard.set(data, forKey: Self.selectedEndpointKey) + } + } + + private static func loadStreamConfig() -> StreamConfig { + guard let data = UserDefaults.standard.data(forKey: streamConfigKey), + let config = try? JSONDecoder().decode(StreamConfig.self, from: data) else { + return StreamConfig() + } + return config + } + + private func saveStreamConfig(_ config: StreamConfig) { + if let data = try? JSONEncoder().encode(config) { + UserDefaults.standard.set(data, forKey: Self.streamConfigKey) + } + } + + private static func loadHapticsEnabled() -> Bool { + UserDefaults.standard.object(forKey: hapticsEnabledKey) as? Bool ?? true + } + + private static func loadTouchOverlayVisible() -> Bool { + UserDefaults.standard.object(forKey: touchOverlayVisibleKey) as? Bool ?? true + } + + func hapticSelection() { + guard hapticsEnabled else { return } + UISelectionFeedbackGenerator().selectionChanged() + } + + func hapticImpact() { + guard hapticsEnabled else { return } + UIImpactFeedbackGenerator(style: .light).impactOccurred() + } + + func hapticSuccess() { + guard hapticsEnabled else { return } + UINotificationFeedbackGenerator().notificationOccurred(.success) + } + + func hapticWarning() { + guard hapticsEnabled else { return } + UINotificationFeedbackGenerator().notificationOccurred(.warning) + } +} + +private extension CGFloat { + func clamped(to range: ClosedRange) -> CGFloat { + Swift.min(Swift.max(self, range.lowerBound), range.upperBound) + } +} + +private extension StreamState { + init(peerState: RTCPeerConnectionState) { + switch peerState { + case .connected: + self = .connected + case .connecting, .new: + self = .connecting + case .disconnected, .closed: + self = .disconnected + case .failed: + self = .failed + @unknown default: + self = .disconnected + } + } +} diff --git a/ios/SimDeckStudio/App/Models.swift b/ios/SimDeckStudio/App/Models.swift new file mode 100644 index 00000000..01cc1e79 --- /dev/null +++ b/ios/SimDeckStudio/App/Models.swift @@ -0,0 +1,587 @@ +import Foundation + +enum EndpointSource: String, Codable, CaseIterable, Sendable { + case bonjour + case lan + case tailscale + case manual + case studioLink + case recent + + var label: String { + switch self { + case .bonjour: "Bonjour" + case .lan: "LAN" + case .tailscale: "Tailscale" + case .manual: "Manual" + case .studioLink: "Studio" + case .recent: "Recent" + } + } + + var systemImage: String { + switch self { + case .bonjour: "dot.radiowaves.left.and.right" + case .lan: "network" + case .tailscale: "point.3.connected.trianglepath.dotted" + case .manual: "link" + case .studioLink: "cloud" + case .recent: "clock" + } + } +} + +struct SimDeckEndpoint: Identifiable, Hashable, Codable, Sendable { + var id: String { baseURL.absoluteString } + + var name: String + var baseURL: URL + var source: EndpointSource + var token: String? + var requiresPairing: Bool + var preferredSimulatorID: String? + var serverID: String? + var alternateBaseURLs: [URL] + + init( + name: String, + baseURL: URL, + source: EndpointSource, + token: String? = nil, + requiresPairing: Bool = false, + preferredSimulatorID: String? = nil, + serverID: String? = nil, + alternateBaseURLs: [URL] = [] + ) { + let normalizedBaseURL = baseURL.normalizedSimDeckBaseURL() + self.name = name + self.baseURL = normalizedBaseURL + self.source = source + self.token = token?.nilIfBlank + self.requiresPairing = requiresPairing + self.preferredSimulatorID = preferredSimulatorID?.nilIfBlank + self.serverID = serverID?.nilIfBlank + self.alternateBaseURLs = alternateBaseURLs + .map { $0.normalizedSimDeckBaseURL() } + .filter { $0 != normalizedBaseURL } + } + + private enum CodingKeys: String, CodingKey { + case name + case baseURL + case source + case token + case requiresPairing + case preferredSimulatorID + case serverID + case alternateBaseURLs + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.init( + name: try container.decode(String.self, forKey: .name), + baseURL: try container.decode(URL.self, forKey: .baseURL), + source: try container.decode(EndpointSource.self, forKey: .source), + token: try container.decodeIfPresent(String.self, forKey: .token), + requiresPairing: try container.decodeIfPresent(Bool.self, forKey: .requiresPairing) ?? false, + preferredSimulatorID: try container.decodeIfPresent(String.self, forKey: .preferredSimulatorID), + serverID: try container.decodeIfPresent(String.self, forKey: .serverID), + alternateBaseURLs: try container.decodeIfPresent([URL].self, forKey: .alternateBaseURLs) ?? [] + ) + } +} + +struct SimulatorMetadata: Identifiable, Hashable, Decodable, Sendable { + var id: String { udid } + + let udid: String + let name: String + let platform: String? + let runtimeIdentifier: String? + let runtimeName: String? + let deviceTypeIdentifier: String? + let deviceTypeName: String? + let isBooted: Bool + let android: AndroidSimulatorInfo? + let privateDisplay: PrivateDisplayInfo? + + var subtitle: String { + [runtimeName, deviceTypeName] + .compactMap(\.self) + .filter { !$0.isEmpty } + .joined(separator: " - ") + } + + var systemImage: String { + let metadata = [ + platform, + runtimeIdentifier, + runtimeName, + deviceTypeIdentifier, + deviceTypeName, + name + ] + .compactMap { $0?.lowercased() } + .joined(separator: " ") + + if metadata.contains("apple-tv") || metadata.contains("apple tv") || metadata.contains("tvos") { + return "appletv" + } + if metadata.contains("apple-watch") || metadata.contains("apple watch") || metadata.contains("watchos") { + return "applewatch" + } + if metadata.contains("ipad") { + return "ipad" + } + if metadata.contains("vision") || metadata.contains("xros") { + return "visionpro" + } + if metadata.contains("mac") { + return "macbook" + } + if metadata.contains("android") || metadata.contains("pixel") { + return "rectangle.portrait" + } + return "iphone.gen3" + } +} + +struct AndroidSimulatorInfo: Hashable, Decodable, Sendable { + let avdName: String? + let grpcPort: Int? + let serial: String? +} + +struct PrivateDisplayInfo: Hashable, Decodable, Sendable { + let displayReady: Bool + let displayStatus: String + let displayWidth: Int + let displayHeight: Int +} + +struct StreamDiagnostics: Hashable, Sendable { + var codec: String = "" + var width: UInt64 = 0 + var height: UInt64 = 0 + var receivedPackets: UInt64 = 0 + var decodedFrames: UInt64 = 0 + var renderedFrames: UInt64 = 0 + var decoderDroppedFrames: UInt64 = 0 + var presentationDroppedFrames: UInt64 = 0 + var droppedFrames: UInt64 = 0 + var packetsLost: UInt64 = 0 + var latestPacketGapMs: Double = 0 + var latestFrameGapMs: Double = 0 + var packetFps: Double = 0 + var decodedFps: Double = 0 + var renderedFps: Double = 0 + var peerConnectionState: String = "" + var iceConnectionState: String = "" + var iceGatheringState: String = "" + var signalingState: String = "" + var selectedCandidatePair: String = "" + var timestamp = Date() + + init() {} + + init(stats: [String: Any]) { + codec = stats["codec"] as? String ?? "" + width = StreamDiagnostics.uintValue(stats["width"]) + height = StreamDiagnostics.uintValue(stats["height"]) + receivedPackets = StreamDiagnostics.uintValue(stats["receivedPackets"]) + decodedFrames = StreamDiagnostics.uintValue(stats["decodedFrames"]) + renderedFrames = StreamDiagnostics.uintValue(stats["renderedFrames"]) + decoderDroppedFrames = StreamDiagnostics.uintValue(stats["decoderDroppedFrames"]) + presentationDroppedFrames = StreamDiagnostics.uintValue(stats["presentationDroppedFrames"]) + droppedFrames = StreamDiagnostics.uintValue(stats["droppedFrames"]) + if decoderDroppedFrames == 0 { + decoderDroppedFrames = droppedFrames + } + packetsLost = StreamDiagnostics.uintValue(stats["packetsLost"]) + latestPacketGapMs = StreamDiagnostics.doubleValue(stats["latestPacketGapMs"]) + latestFrameGapMs = StreamDiagnostics.doubleValue(stats["latestFrameGapMs"]) + packetFps = StreamDiagnostics.doubleValue(stats["packetFps"]) + decodedFps = StreamDiagnostics.doubleValue(stats["decodedFps"]) + renderedFps = StreamDiagnostics.doubleValue(stats["appFps"]) + peerConnectionState = stats["peerConnectionState"] as? String ?? stats["status"] as? String ?? "" + iceConnectionState = stats["iceConnectionState"] as? String ?? "" + iceGatheringState = stats["iceGatheringState"] as? String ?? "" + signalingState = stats["signalingState"] as? String ?? "" + selectedCandidatePair = stats["selectedCandidatePair"] as? String ?? "" + timestamp = Date() + } + + private static func uintValue(_ value: Any?) -> UInt64 { + if let value = value as? UInt64 { + return value + } + if let value = value as? UInt { + return UInt64(value) + } + if let value = value as? Int { + return UInt64(max(value, 0)) + } + if let value = value as? NSNumber { + return value.uint64Value + } + return 0 + } + + private static func doubleValue(_ value: Any?) -> Double { + if let value = value as? Double { + return value + } + if let value = value as? NSNumber { + return value.doubleValue + } + return 0 + } +} + +struct ChromeProfile: Hashable, Decodable, Sendable { + let totalWidth: Double + let totalHeight: Double + let screenX: Double + let screenY: Double + let screenWidth: Double + let screenHeight: Double + let cornerRadius: Double + let chromeStyle: String? + let hasScreenMask: Bool? + let buttons: [ChromeButtonProfile]? + + var assetStamp: String { + var parts = [ + totalWidth, + totalHeight, + screenX, + screenY, + screenWidth, + screenHeight, + cornerRadius + ] + .map { value in + Self.stampValue(value) + } + parts.append(hasScreenMask == true ? "mask" : "nomask") + parts.append(contentsOf: (buttons ?? []) + .sorted { $0.name < $1.name } + .map(\.assetStamp)) + return parts.joined(separator: "x") + } + + private static func stampValue(_ value: Double) -> String { + value.isFinite ? String(Int((value * 1000).rounded())) : "0" + } +} + +struct ChromeButtonProfile: Hashable, Decodable, Sendable { + let name: String + let label: String? + let type: String? + let imageName: String? + let imageDownName: String? + let x: Double + let y: Double + let width: Double + let height: Double + let anchor: String? + let align: String? + let usagePage: Int? + let usage: Int? + let onTop: Bool? + + var assetStamp: String { + [ + sanitized(name), + sanitized(type), + sanitized(imageName), + sanitized(imageDownName), + sanitized(anchor), + sanitized(align), + onTop == true ? "top" : "under", + stampValue(x), + stampValue(y), + stampValue(width), + stampValue(height), + usagePage.map(String.init) ?? "", + usage.map(String.init) ?? "" + ].joined(separator: ".") + } + + private func stampValue(_ value: Double) -> String { + value.isFinite ? String(Int((value * 1000).rounded())) : "0" + } + + private func sanitized(_ value: String?) -> String { + (value ?? "").map { character in + character.isLetter || character.isNumber || character == "_" || character == "-" || character == "." + ? character + : "_" + } + .reduce(into: "") { $0.append($1) } + } +} + +struct SimulatorsResponse: Decodable, Sendable { + let simulators: [SimulatorMetadata] +} + +struct SimulatorDeviceTypeOption: Identifiable, Hashable, Decodable, Sendable { + var id: String { identifier } + + let identifier: String + let name: String + let productFamily: String? + let supportedRuntimeIdentifiers: [String]? +} + +struct SimulatorRuntimeOption: Identifiable, Hashable, Decodable, Sendable { + var id: String { identifier } + + let identifier: String + let name: String + let platform: String? + let isAvailable: Bool? + let supportedDeviceTypeIdentifiers: [String]? +} + +struct AndroidEmulatorDeviceTypeOption: Identifiable, Hashable, Decodable, Sendable { + var id: String { identifier } + + let identifier: String + let name: String + let oem: String? + let tag: String? +} + +struct AndroidEmulatorSystemImageOption: Identifiable, Hashable, Decodable, Sendable { + var id: String { identifier } + + let identifier: String + let name: String + let description: String? + let apiLevel: Int? + let tag: String? + let abi: String? +} + +struct AndroidEmulatorCreateOptions: Hashable, Decodable, Sendable { + let deviceTypes: [AndroidEmulatorDeviceTypeOption] + let systemImages: [AndroidEmulatorSystemImageOption] + let unavailableReason: String? +} + +struct SimulatorCreateOptionsResponse: Hashable, Decodable, Sendable { + let deviceTypes: [SimulatorDeviceTypeOption] + let runtimes: [SimulatorRuntimeOption] + let android: AndroidEmulatorCreateOptions? +} + +struct CreatePairedWatchRequest: Encodable, Hashable, Sendable { + let name: String + let deviceTypeIdentifier: String + let runtimeIdentifier: String? +} + +struct CreateSimulatorRequest: Encodable, Hashable, Sendable { + let platform: String? + let name: String + let deviceTypeIdentifier: String + let runtimeIdentifier: String? + let pairedWatch: CreatePairedWatchRequest? +} + +struct CreateSimulatorResponse: Decodable, Sendable { + let ok: Bool + let created: CreatedSimulatorInfo + let simulator: SimulatorMetadata + let pairedWatchSimulator: SimulatorMetadata? +} + +struct CreatedSimulatorInfo: Decodable, Sendable { + let udid: String + let pairedWatchUDID: String? +} + +struct HealthResponse: Decodable, Sendable { + let ok: Bool + let serverId: String? + let advertiseHost: String? + let httpPort: Int? + let videoCodec: String? + let realtimeStream: Bool? + let webRtc: WebRTCConfigurationResponse? +} + +struct WebRTCConfigurationResponse: Decodable, Sendable { + let iceServers: [IceServer]? + let iceTransportPolicy: String? +} + +struct IceServer: Hashable, Decodable, Sendable { + let urls: [String] + let username: String? + let credential: String? + + enum CodingKeys: String, CodingKey { + case urls + case username + case credential + } + + init(urls: [String], username: String? = nil, credential: String? = nil) { + self.urls = urls + self.username = username + self.credential = credential + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + if let urls = try? container.decode([String].self, forKey: .urls) { + self.urls = urls + } else { + self.urls = [try container.decode(String.self, forKey: .urls)] + } + username = try container.decodeIfPresent(String.self, forKey: .username) + credential = try container.decodeIfPresent(String.self, forKey: .credential) + } +} + +struct WebRTCVideoMetadata: Decodable, Sendable { + let width: Int + let height: Int +} + +struct WebRTCAnswerPayload: Decodable, Sendable { + let sdp: String + let type: String + let video: WebRTCVideoMetadata? +} + +enum StreamEncoder: String, CaseIterable, Codable, Hashable, Sendable { + case auto + case hardware + case software + + var label: String { + switch self { + case .auto: "Auto" + case .hardware: "Hardware" + case .software: "Software" + } + } +} + +enum StreamQualityPreset: String, CaseIterable, Codable, Hashable, Sendable { + case auto + case full + case balanced + case economy + case low + case tiny + + var label: String { + switch self { + case .auto: "Auto" + case .full: "Full" + case .balanced: "1280" + case .economy: "1080" + case .low: "720" + case .tiny: "540" + } + } + + var summaryLabel: String { + switch self { + case .auto: "Auto" + case .full: "Full res" + case .balanced: "1280px" + case .economy: "1080px" + case .low: "720px" + case .tiny: "540px" + } + } + + var payloadProfile: String { + self == .auto ? StreamQualityPreset.economy.rawValue : rawValue + } +} + +struct StreamConfig: Codable, Hashable, Sendable { + var encoder: StreamEncoder = .auto + var fps: Int = 60 + var quality: StreamQualityPreset = .full + + var summary: String { + "WebRTC / \(quality.summaryLabel) / \(fps) fps" + } +} + +struct StreamQualityPayload: Encodable, Sendable { + var profile: String + var fps: Int + var videoCodec: String + + init(config: StreamConfig = StreamConfig()) { + profile = config.quality.payloadProfile + fps = config.fps + videoCodec = config.encoder.rawValue + } + + var jsonObject: [String: Any] { + [ + "profile": profile, + "fps": fps, + "videoCodec": videoCodec + ] + } +} + +struct WebRTCOfferPayload: Encodable, Sendable { + let clientId: String + let sdp: String + let streamConfig: StreamQualityPayload + let type: String +} + +enum AppRoute: Hashable, Sendable { + case endpoint(SimDeckEndpoint, autoStart: Bool) + case pairing(SimDeckPairingLink, autoStart: Bool) +} + +struct SimDeckPairingLink: Hashable, Sendable { + let endpoint: SimDeckEndpoint + let pairingCode: String? + let alternateEndpoints: [SimDeckEndpoint] +} + +extension URL { + func normalizedSimDeckBaseURL() -> URL { + guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { + return self + } + components.query = nil + components.fragment = nil + if components.path != "/" { + components.path = components.path.trimmingTrailingSlashes() + } + return components.url ?? self + } +} + +extension String { + var nilIfBlank: String? { + let trimmed = trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.isEmpty ? nil : trimmed + } + + func trimmingTrailingSlashes() -> String { + var value = self + while value.count > 1 && value.hasSuffix("/") { + value.removeLast() + } + return value + } +} diff --git a/ios/SimDeckStudio/App/SimDeckStudioApp.swift b/ios/SimDeckStudio/App/SimDeckStudioApp.swift new file mode 100644 index 00000000..569a734b --- /dev/null +++ b/ios/SimDeckStudio/App/SimDeckStudioApp.swift @@ -0,0 +1,19 @@ +import SwiftUI + +@main +struct SimDeckStudioApp: App { + @State private var model = AppModel() + @Environment(\.scenePhase) private var scenePhase + + var body: some Scene { + WindowGroup { + ContentView(model: model) + .onOpenURL { url in + model.handle(url: url) + } + .onChange(of: scenePhase) { _, phase in + model.handleScenePhase(phase) + } + } + } +} diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png new file mode 100644 index 00000000..077e0f05 Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png new file mode 100644 index 00000000..f0caa8f6 Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png new file mode 100644 index 00000000..b0c5d0b4 Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png new file mode 100644 index 00000000..88a069eb Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png new file mode 100644 index 00000000..66bec3a9 Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png new file mode 100644 index 00000000..db3b10ef Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png new file mode 100644 index 00000000..f0caa8f6 Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png new file mode 100644 index 00000000..1d8f5d4f Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png new file mode 100644 index 00000000..6fe5774a Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png new file mode 100644 index 00000000..6fe5774a Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png new file mode 100644 index 00000000..c5ad72dd Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png new file mode 100644 index 00000000..cadc9b5f Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png new file mode 100644 index 00000000..ba24d38f Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000..9ac46b53 Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon.png new file mode 100644 index 00000000..05ce75b7 Binary files /dev/null and b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/AppIcon.png differ diff --git a/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..ffaaa8a5 --- /dev/null +++ b/ios/SimDeckStudio/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images": [ + { + "filename": "AppIcon-20x20@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "20x20" + }, + { + "filename": "AppIcon-20x20@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "20x20" + }, + { + "filename": "AppIcon-29x29@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-29x29@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "29x29" + }, + { + "filename": "AppIcon-40x40@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "40x40" + }, + { + "filename": "AppIcon-40x40@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "40x40" + }, + { + "filename": "AppIcon-60x60@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "60x60" + }, + { + "filename": "AppIcon-60x60@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "60x60" + }, + { + "filename": "AppIcon-20x20@1x.png", + "idiom": "ipad", + "scale": "1x", + "size": "20x20" + }, + { + "filename": "AppIcon-20x20@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "20x20" + }, + { + "filename": "AppIcon-29x29@1x.png", + "idiom": "ipad", + "scale": "1x", + "size": "29x29" + }, + { + "filename": "AppIcon-29x29@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-40x40@1x.png", + "idiom": "ipad", + "scale": "1x", + "size": "40x40" + }, + { + "filename": "AppIcon-40x40@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "40x40" + }, + { + "filename": "AppIcon-76x76@1x.png", + "idiom": "ipad", + "scale": "1x", + "size": "76x76" + }, + { + "filename": "AppIcon-76x76@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "76x76" + }, + { + "filename": "AppIcon-83.5x83.5@2x.png", + "idiom": "ipad", + "scale": "2x", + "size": "83.5x83.5" + }, + { + "filename": "AppIcon.png", + "idiom": "ios-marketing", + "scale": "1x", + "size": "1024x1024" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/ios/SimDeckStudio/Assets.xcassets/Contents.json b/ios/SimDeckStudio/Assets.xcassets/Contents.json new file mode 100644 index 00000000..74d6a722 --- /dev/null +++ b/ios/SimDeckStudio/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/ios/SimDeckStudio/Discovery/SimDeckDiscovery.swift b/ios/SimDeckStudio/Discovery/SimDeckDiscovery.swift new file mode 100644 index 00000000..fe99b173 --- /dev/null +++ b/ios/SimDeckStudio/Discovery/SimDeckDiscovery.swift @@ -0,0 +1,350 @@ +import Darwin +import Foundation +import Observation + +@MainActor +@Observable +final class SimDeckDiscovery { + var endpoints: [SimDeckEndpoint] = [] + var isScanning = false + + @ObservationIgnored private let bonjour = BonjourDiscovery() + @ObservationIgnored private var scanTask: Task? + @ObservationIgnored var onEndpoint: ((SimDeckEndpoint) -> Void)? + + init() { + bonjour.onEndpoint = { [weak self] endpoint in + Task { @MainActor in + self?.upsert(endpoint) + } + } + } + + func start() { + bonjour.start() + refresh() + } + + func stop() { + bonjour.stop() + scanTask?.cancel() + scanTask = nil + } + + func refresh() { + scanTask?.cancel() + isScanning = true + scanTask = Task { + let local = await Self.scanPriorityHosts() + guard !Task.isCancelled else { return } + await MainActor.run { + for endpoint in local { + upsert(endpoint) + } + } + let found = await Self.scanLikelyHosts() + guard !Task.isCancelled else { return } + await MainActor.run { + for endpoint in found { + upsert(endpoint) + } + isScanning = false + } + } + } + + func upsert(_ endpoint: SimDeckEndpoint) { + let isNewEndpoint: Bool + let shouldNotify: Bool + if let index = endpoints.firstIndex(where: { Self.sameServer($0, endpoint) }) { + let previous = endpoints[index] + endpoints[index] = Self.mergedEndpoint(previous, endpoint) + isNewEndpoint = false + shouldNotify = previous.baseURL != endpoints[index].baseURL + || (previous.requiresPairing && !endpoints[index].requiresPairing) + } else { + endpoints.append(endpoint) + isNewEndpoint = true + shouldNotify = true + } + endpoints.sort { + if $0.source == $1.source { + return $0.name.localizedStandardCompare($1.name) == .orderedAscending + } + return sourceRank($0.source) < sourceRank($1.source) + } + if isNewEndpoint || shouldNotify { + onEndpoint?(endpoint) + } + } + + private static func sameServer(_ lhs: SimDeckEndpoint, _ rhs: SimDeckEndpoint) -> Bool { + if let lhsID = lhs.serverID?.nilIfBlank, + let rhsID = rhs.serverID?.nilIfBlank { + return lhsID == rhsID + } + return lhs.baseURL == rhs.baseURL + || lhs.alternateBaseURLs.contains(rhs.baseURL) + || rhs.alternateBaseURLs.contains(lhs.baseURL) + } + + private static func mergedEndpoint(_ lhs: SimDeckEndpoint, _ rhs: SimDeckEndpoint) -> SimDeckEndpoint { + let preferred = preferredEndpoint(lhs, rhs) + let other = preferred.baseURL == lhs.baseURL ? rhs : lhs + var merged = preferred + merged.serverID = preferred.serverID ?? other.serverID + merged.token = preferred.token ?? other.token + merged.requiresPairing = preferred.requiresPairing && other.requiresPairing + merged.preferredSimulatorID = preferred.preferredSimulatorID ?? other.preferredSimulatorID + merged.alternateBaseURLs = uniquedURLs( + [lhs.baseURL, rhs.baseURL] + lhs.alternateBaseURLs + rhs.alternateBaseURLs + ) + .filter { $0 != merged.baseURL } + return merged + } + + private static func preferredEndpoint(_ lhs: SimDeckEndpoint, _ rhs: SimDeckEndpoint) -> SimDeckEndpoint { + if lhs.requiresPairing != rhs.requiresPairing { + return lhs.requiresPairing ? rhs : lhs + } + if sourceRankValue(lhs.source) != sourceRankValue(rhs.source) { + return sourceRankValue(lhs.source) < sourceRankValue(rhs.source) ? lhs : rhs + } + return lhs + } + + private static func uniquedURLs(_ urls: [URL]) -> [URL] { + var seen = Set() + var result: [URL] = [] + for url in urls.map({ $0.normalizedSimDeckBaseURL() }) where seen.insert(url).inserted { + result.append(url) + } + return result + } + + private func sourceRank(_ source: EndpointSource) -> Int { + Self.sourceRankValue(source) + } + + private static func sourceRankValue(_ source: EndpointSource) -> Int { + switch source { + case .bonjour: 0 + case .lan: 1 + case .tailscale: 2 + case .studioLink: 3 + case .manual: 4 + case .recent: 5 + } + } + + private static func scanLikelyHosts() async -> [SimDeckEndpoint] { + let candidates = IPv4Interface.discoveryCandidates() + let ports = [4310, 4311, 4312, 4313, 4314, 4320] + return await scan(candidates: candidates, ports: ports) + } + + private static func scanPriorityHosts() async -> [SimDeckEndpoint] { + for port in [4313, 4310, 4311, 4312, 4314, 4320] { + if let endpoint = await probe(host: "127.0.0.1", port: port, source: .manual) { + return [endpoint] + } + } + if let endpoint = await probe(host: "localhost", port: 4313, source: .manual) { + return [endpoint] + } + if let endpoint = await probe(host: "simdeck.local", port: 4310, source: .bonjour) { + return [endpoint] + } + return [] + } + + private static func scan(candidates: [DiscoveryCandidate], ports: [Int]) async -> [SimDeckEndpoint] { + var results: [SimDeckEndpoint] = [] + + for batch in candidates.chunked(into: 16) { + await withTaskGroup(of: SimDeckEndpoint?.self) { group in + for candidate in batch { + for port in ports { + group.addTask { + await probe(host: candidate.host, port: port, source: candidate.source) + } + } + } + for await endpoint in group { + if let endpoint, !results.contains(where: { $0.baseURL == endpoint.baseURL }) { + results.append(endpoint) + } + } + } + } + return results + } + + private static func probe(host: String, port: Int, source: EndpointSource) async -> SimDeckEndpoint? { + var components = URLComponents() + components.scheme = "http" + components.host = host + components.port = port + guard let baseURL = components.url, + let healthURL = URL(string: "/api/health", relativeTo: baseURL) else { + return nil + } + var request = URLRequest(url: healthURL) + request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData + request.timeoutInterval = 1.25 + request.setValue(baseURL.absoluteString.trimmingTrailingSlashes(), forHTTPHeaderField: "Origin") + do { + let (data, response) = try await URLSession.shared.data(for: request) + guard let http = response as? HTTPURLResponse else { return nil } + if http.statusCode == 401 { + let health = try? JSONDecoder().decode(HealthResponse.self, from: data) + return SimDeckEndpoint( + name: endpointName(for: host), + baseURL: baseURL, + source: source, + requiresPairing: true, + serverID: health?.serverId, + alternateBaseURLs: alternateURLs(from: health, fallbackPort: port) + ) + } + guard http.statusCode == 200, + let health = try? JSONDecoder().decode(HealthResponse.self, from: data), + health.ok else { + return nil + } + return SimDeckEndpoint( + name: endpointName(for: host), + baseURL: baseURL, + source: source, + serverID: health.serverId, + alternateBaseURLs: alternateURLs(from: health, fallbackPort: port) + ) + } catch { + return nil + } + } + + private static func alternateURLs(from health: HealthResponse?, fallbackPort: Int) -> [URL] { + guard let advertiseHost = health?.advertiseHost?.nilIfBlank else { return [] } + var components = URLComponents() + components.scheme = "http" + components.host = advertiseHost + components.port = health?.httpPort ?? fallbackPort + return components.url.map { [$0] } ?? [] + } + + private static func endpointName(for host: String) -> String { + if host == "127.0.0.1" || host == "localhost" { + return "Local SimDeck" + } + return "SimDeck \(host)" + } +} + +private final class BonjourDiscovery: NSObject, NetServiceBrowserDelegate, NetServiceDelegate { + var onEndpoint: (@Sendable (SimDeckEndpoint) -> Void)? + private let browser = NetServiceBrowser() + private var services: [NetService] = [] + + override init() { + super.init() + browser.delegate = self + } + + func start() { + browser.searchForServices(ofType: "_simdeck._tcp.", inDomain: "local.") + } + + func stop() { + browser.stop() + services.removeAll() + } + + func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { + services.append(service) + service.delegate = self + service.resolve(withTimeout: 2) + } + + func netServiceDidResolveAddress(_ sender: NetService) { + let host = sender.hostName?.trimmingTrailingSlashes() ?? "\(sender.name).local" + let txt = NetService.dictionary(fromTXTRecord: sender.txtRecordData() ?? Data()) + let serverID = txt["sid"].flatMap { String(data: $0, encoding: .utf8) }?.nilIfBlank + let advertisedHost = txt["host"].flatMap { String(data: $0, encoding: .utf8) }?.nilIfBlank + var components = URLComponents() + components.scheme = "http" + components.host = host + components.port = sender.port + guard let url = components.url else { return } + var alternateURLs: [URL] = [] + if let advertisedHost { + var advertised = URLComponents() + advertised.scheme = "http" + advertised.host = advertisedHost + advertised.port = sender.port + if let advertisedURL = advertised.url { + alternateURLs.append(advertisedURL) + } + } + onEndpoint?( + SimDeckEndpoint( + name: sender.name.isEmpty ? "SimDeck \(host)" : sender.name, + baseURL: url, + source: .bonjour, + serverID: serverID, + alternateBaseURLs: alternateURLs + ) + ) + } +} + +private struct DiscoveryCandidate: Hashable { + let host: String + let source: EndpointSource +} + +private struct IPv4Interface { + let address: [UInt8] + let isTailscale: Bool + + static func discoveryCandidates() -> [DiscoveryCandidate] { + var candidates: [DiscoveryCandidate] = [] + var seen = Set() + var interfaces: UnsafeMutablePointer? + guard getifaddrs(&interfaces) == 0, let first = interfaces else { return candidates } + defer { freeifaddrs(interfaces) } + + var cursor: UnsafeMutablePointer? = first + while let current = cursor { + defer { cursor = current.pointee.ifa_next } + let flags = Int32(current.pointee.ifa_flags) + guard flags & IFF_UP != 0, flags & IFF_LOOPBACK == 0 else { continue } + guard current.pointee.ifa_addr.pointee.sa_family == UInt8(AF_INET) else { continue } + let socketAddress = current.pointee.ifa_addr.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { $0.pointee } + let host = IPv4Interface.bytes(from: socketAddress.sin_addr) + guard host.count == 4 else { continue } + let source: EndpointSource = host[0] == 100 && (host[1] & 0b1100_0000) == 0b0100_0000 ? .tailscale : .lan + for last in UInt8(1)...UInt8(254) where last != host[3] { + let candidate = DiscoveryCandidate(host: "\(host[0]).\(host[1]).\(host[2]).\(last)", source: source) + if seen.insert(candidate).inserted { + candidates.append(candidate) + } + } + } + return candidates + } + + private static func bytes(from address: in_addr) -> [UInt8] { + var address = address + return withUnsafeBytes(of: &address.s_addr) { Array($0) } + } +} + +private extension Array { + func chunked(into size: Int) -> [[Element]] { + guard size > 0 else { return [self] } + return stride(from: 0, to: count, by: size).map { + Array(self[$0.. + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + SimDeck + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleURLName + org.nativescript.simdeck + CFBundleURLSchemes + + simdeck + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + http + https + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + ts.net + + NSIncludesSubdomains + + NSExceptionAllowsInsecureHTTPLoads + + + + + NSBonjourServices + + _simdeck._tcp + + NSCameraUsageDescription + SimDeck uses the camera to scan pairing QR codes. + NSLocalNetworkUsageDescription + SimDeck discovers and connects to simulator streams on your local network. + NSMicrophoneUsageDescription + SimDeck uses WebRTC to connect to simulator streams. Microphone access is only used for WebRTC features that send audio from this device. + UILaunchScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios/SimDeckStudio/Networking/SimDeckAPI.swift b/ios/SimDeckStudio/Networking/SimDeckAPI.swift new file mode 100644 index 00000000..02c6cbd3 --- /dev/null +++ b/ios/SimDeckStudio/Networking/SimDeckAPI.swift @@ -0,0 +1,239 @@ +import Foundation +import UIKit + +enum SimDeckAPIError: LocalizedError { + case authRequired + case invalidResponse + case requestFailed(Int, String) + + var errorDescription: String? { + switch self { + case .authRequired: + "Pairing or an API token is required." + case .invalidResponse: + "SimDeck returned an invalid response." + case let .requestFailed(status, message): + "Request failed with status \(status): \(message)" + } + } +} + +struct SimDeckAPI: Sendable { + let endpoint: SimDeckEndpoint + var baseURL: URL { endpoint.baseURL } + + func health(timeout: TimeInterval = 5) async throws -> HealthResponse { + try await decode(path: "/api/health", timeout: timeout) + } + + func simulators() async throws -> [SimulatorMetadata] { + let response: SimulatorsResponse = try await decode(path: "/api/simulators") + return response.simulators + } + + func simulatorCreateOptions() async throws -> SimulatorCreateOptionsResponse { + try await decode(path: "/api/simulators/create-options") + } + + func createSimulator(_ payload: CreateSimulatorRequest) async throws -> CreateSimulatorResponse { + try await decode(path: "/api/simulators", method: "POST", body: payload, timeout: 300) + } + + func pair(code: String) async throws -> String? { + let payload = ["code": code] + let (data, response) = try await requestWithHTTPResponse( + path: "/api/pair", + method: "POST", + body: payload, + timeout: 10 + ) + let pairResponse = try? JSONDecoder().decode(PairResponse.self, from: data) + return pairResponse?.accessToken?.nilIfBlank ?? accessToken(from: response) + } + + func bootSimulator(udid: String) async throws { + let _: EmptyResponse = try await decode( + path: "/api/simulators/\(udid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? udid)/boot", + method: "POST", + body: Optional.none, + timeout: 300 + ) + } + + func postWebRTCOffer(_ offer: WebRTCOfferPayload, udid: String) async throws -> WebRTCAnswerPayload { + try await decode( + path: "/api/simulators/\(udid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? udid)/webrtc/offer", + method: "POST", + body: offer, + timeout: 20 + ) + } + + func chromeProfile(udid: String) async throws -> ChromeProfile { + try await decode( + path: "/api/simulators/\(udid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? udid)/chrome-profile", + cachePolicy: .reloadIgnoringLocalAndRemoteCacheData + ) + } + + func chromeImage(udid: String, stamp: String? = nil) async throws -> UIImage { + let data = try await request( + path: "/api/simulators/\(udid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? udid)/chrome.png", + method: "GET", + body: Optional.none, + timeout: 10, + queryItems: Self.assetQueryItems(stamp: stamp), + cachePolicy: .reloadIgnoringLocalAndRemoteCacheData + ) + guard let image = UIImage(data: data) else { + throw SimDeckAPIError.invalidResponse + } + return image + } + + func screenMaskImage(udid: String, stamp: String? = nil) async throws -> UIImage { + let data = try await request( + path: "/api/simulators/\(udid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? udid)/screen-mask.png", + method: "GET", + body: Optional.none, + timeout: 10, + queryItems: Self.assetQueryItems(stamp: stamp), + cachePolicy: .reloadIgnoringLocalAndRemoteCacheData + ) + guard let image = UIImage(data: data) else { + throw SimDeckAPIError.invalidResponse + } + return image + } + + func postControl(_ payload: some Encodable, path: String) async throws { + let _: EmptyResponse = try await decode(path: path, method: "POST", body: payload) + } + + private func decode( + path: String, + method: String = "GET", + body: (some Encodable)? = Optional.none, + timeout: TimeInterval = 10, + queryItems: [URLQueryItem] = [], + cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy + ) async throws -> T { + let data = try await request(path: path, method: method, body: body, timeout: timeout, queryItems: queryItems, cachePolicy: cachePolicy) + if data.isEmpty, T.self == EmptyResponse.self { + return EmptyResponse() as! T + } + return try JSONDecoder().decode(T.self, from: data) + } + + private func request( + path: String, + method: String, + body: (some Encodable)?, + timeout: TimeInterval, + queryItems: [URLQueryItem] = [], + cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy + ) async throws -> Data { + let (data, _) = try await requestWithHTTPResponse( + path: path, + method: method, + body: body, + timeout: timeout, + queryItems: queryItems, + cachePolicy: cachePolicy + ) + return data + } + + private func requestWithHTTPResponse( + path: String, + method: String, + body: (some Encodable)?, + timeout: TimeInterval, + queryItems: [URLQueryItem] = [], + cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy + ) async throws -> (Data, HTTPURLResponse) { + var request = URLRequest(url: url(for: path, queryItems: queryItems), cachePolicy: cachePolicy, timeoutInterval: timeout) + request.httpMethod = method + request.setValue("application/json", forHTTPHeaderField: "Accept") + request.setValue(originHeaderValue, forHTTPHeaderField: "Origin") + if let token = endpoint.token?.nilIfBlank { + request.setValue(token, forHTTPHeaderField: "X-SimDeck-Token") + } + if let body { + request.httpBody = try JSONEncoder().encode(AnyEncodable(body)) + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + + let (data, response) = try await URLSession.shared.data(for: request) + guard let httpResponse = response as? HTTPURLResponse else { + throw SimDeckAPIError.invalidResponse + } + if httpResponse.statusCode == 401 { + throw SimDeckAPIError.authRequired + } + guard (200..<300).contains(httpResponse.statusCode) else { + let message = String(data: data, encoding: .utf8) ?? HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode) + throw SimDeckAPIError.requestFailed(httpResponse.statusCode, message) + } + return (data, httpResponse) + } + + private func accessToken(from response: HTTPURLResponse) -> String? { + let headerFields = response.allHeaderFields.reduce(into: [String: String]()) { result, item in + guard let key = item.key as? String else { return } + result[key] = String(describing: item.value) + } + return HTTPCookie + .cookies(withResponseHeaderFields: headerFields, for: baseURL) + .first { $0.name == "simdeck_token" }? + .value + .nilIfBlank + } + + private var originHeaderValue: String { + guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { + return baseURL.absoluteString + } + components.path = "" + components.query = nil + components.fragment = nil + return components.url?.absoluteString.trimmingTrailingSlashes() ?? baseURL.absoluteString + } + + private func url(for path: String, queryItems: [URLQueryItem] = []) -> URL { + guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { + return baseURL.appendingPathComponent(path) + } + let prefix = components.path.trimmingTrailingSlashes() + let suffix = path.hasPrefix("/") ? path : "/\(path)" + components.path = "\(prefix)\(suffix)" + if !queryItems.isEmpty { + components.queryItems = (components.queryItems ?? []) + queryItems + } + return components.url ?? baseURL.appendingPathComponent(path) + } + + private static func assetQueryItems(stamp: String?) -> [URLQueryItem] { + guard let stamp = stamp?.nilIfBlank else { return [] } + return [URLQueryItem(name: "stamp", value: stamp)] + } +} + +private struct EmptyResponse: Codable {} + +private struct PairResponse: Decodable { + let ok: Bool + let accessToken: String? +} + +private struct AnyEncodable: Encodable { + private let encodeValue: (Encoder) throws -> Void + + init(_ value: some Encodable) { + encodeValue = value.encode + } + + func encode(to encoder: Encoder) throws { + try encodeValue(encoder) + } +} diff --git a/ios/SimDeckStudio/Networking/StudioLinkResolver.swift b/ios/SimDeckStudio/Networking/StudioLinkResolver.swift new file mode 100644 index 00000000..dc9f770f --- /dev/null +++ b/ios/SimDeckStudio/Networking/StudioLinkResolver.swift @@ -0,0 +1,172 @@ +import Foundation + +enum StudioLinkResolver { + static func route(for url: URL) -> AppRoute? { + if url.scheme?.lowercased() == "simdeck" { + if let pairingLink = pairingLinkFromCustomScheme(url) { + return .pairing(pairingLink, autoStart: true) + } + if let endpoint = endpointFromCustomScheme(url) { + return .endpoint(endpoint, autoStart: true) + } + } + guard ["http", "https"].contains(url.scheme?.lowercased() ?? "") else { + return nil + } + if let endpoint = endpointFromStudioURL(url) { + return .endpoint(endpoint, autoStart: true) + } + let serverID = queryValue("serverId", in: url) ?? queryValue("sid", in: url) ?? queryValue("s", in: url) + return .endpoint( + SimDeckEndpoint( + name: url.host ?? "SimDeck", + baseURL: url, + source: source(for: url.host), + preferredSimulatorID: queryValue("device", in: url) ?? queryValue("udid", in: url), + serverID: serverID + ), + autoStart: true + ) + } + + static func endpointFromAddress(_ value: String, token: String? = nil) -> SimDeckEndpoint? { + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + let withScheme = trimmed.contains("://") ? trimmed : "http://\(trimmed)" + guard let url = URL(string: withScheme), url.host != nil else { return nil } + if let endpoint = endpointFromStudioURL(url) { + var endpointWithToken = endpoint + endpointWithToken.token = token?.nilIfBlank + return endpointWithToken + } + return SimDeckEndpoint( + name: url.host ?? "SimDeck", + baseURL: url, + source: source(for: url.host), + token: token + ) + } + + private static func endpointFromCustomScheme(_ url: URL) -> SimDeckEndpoint? { + guard url.scheme?.lowercased() == "simdeck" else { return nil } + let serverID = queryValue("serverId", in: url) ?? queryValue("sid", in: url) ?? queryValue("s", in: url) + if let rawURL = queryValue("url", in: url) ?? queryValue("u", in: url), + var endpoint = endpointFromAddress(rawURL) { + if let token = queryValue("token", in: url) { + endpoint.token = token + } + endpoint.preferredSimulatorID = queryValue("device", in: url) ?? queryValue("udid", in: url) + endpoint.serverID = serverID + return endpoint + } + guard let host = queryValue("host", in: url) ?? url.host else { return nil } + let port = queryValue("port", in: url).flatMap(Int.init) + var components = URLComponents() + components.scheme = queryValue("scheme", in: url) ?? "http" + components.host = host + components.port = port + guard let baseURL = components.url else { return nil } + return SimDeckEndpoint( + name: host, + baseURL: baseURL, + source: source(for: host), + token: queryValue("token", in: url), + preferredSimulatorID: queryValue("device", in: url) ?? queryValue("udid", in: url), + serverID: serverID + ) + } + + private static func pairingLinkFromCustomScheme(_ url: URL) -> SimDeckPairingLink? { + guard url.scheme?.lowercased() == "simdeck", + ["pair", "pairing"].contains(url.host?.lowercased() ?? "") else { + return nil + } + guard var endpoint = endpointFromCustomScheme(url) else { return nil } + let pairingCode = queryValue("code", in: url) ?? queryValue("pairingCode", in: url) ?? queryValue("c", in: url) + let serverID = queryValue("serverId", in: url) ?? queryValue("sid", in: url) ?? queryValue("s", in: url) + endpoint.serverID = endpoint.serverID ?? serverID + if endpoint.token == nil { + endpoint.token = queryValue("token", in: url) + } + let alternateEndpoints = alternateEndpointValues(in: url).compactMap { rawValue -> SimDeckEndpoint? in + guard var alternate = endpointFromAddress(rawValue, token: endpoint.token) else { return nil } + alternate.preferredSimulatorID = endpoint.preferredSimulatorID + alternate.serverID = endpoint.serverID + return alternate + } + .filter { $0.baseURL != endpoint.baseURL } + return SimDeckPairingLink( + endpoint: endpoint, + pairingCode: pairingCode, + alternateEndpoints: uniquedEndpoints(alternateEndpoints) + ) + } + + private static func endpointFromStudioURL(_ url: URL) -> SimDeckEndpoint? { + let parts = url.pathComponents.filter { $0 != "/" } + guard let simulatorIndex = parts.firstIndex(of: "simulator"), + parts.indices.contains(simulatorIndex + 1) else { + return nil + } + let previewID = parts[simulatorIndex + 1] + guard !previewID.isEmpty, + var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + return nil + } + components.path = "/api/provider-sessions/\(previewID)/simdeck" + components.query = nil + components.fragment = nil + guard let baseURL = components.url else { return nil } + return SimDeckEndpoint( + name: "Studio \(previewID)", + baseURL: baseURL, + source: .studioLink, + token: queryValue("simdeckToken", in: url) ?? queryValue("token", in: url), + preferredSimulatorID: queryValue("device", in: url) ?? queryValue("udid", in: url), + serverID: queryValue("serverId", in: url) ?? queryValue("sid", in: url) ?? queryValue("s", in: url) + ) + } + + private static func queryValue(_ name: String, in url: URL) -> String? { + URLComponents(url: url, resolvingAgainstBaseURL: false)? + .queryItems? + .first { $0.name == name }? + .value? + .nilIfBlank + } + + private static func queryValues(_ name: String, in url: URL) -> [String] { + URLComponents(url: url, resolvingAgainstBaseURL: false)? + .queryItems? + .filter { $0.name == name } + .compactMap { $0.value?.nilIfBlank } ?? [] + } + + private static func alternateEndpointValues(in url: URL) -> [String] { + queryValues("alt", in: url) + + queryValues("a", in: url) + + Array(queryValues("url", in: url).dropFirst()) + + queryValues("lan", in: url) + + queryValues("tailscale", in: url) + } + + private static func uniquedEndpoints(_ endpoints: [SimDeckEndpoint]) -> [SimDeckEndpoint] { + var seen = Set() + var result: [SimDeckEndpoint] = [] + for endpoint in endpoints where seen.insert(endpoint.baseURL).inserted { + result.append(endpoint) + } + return result + } + + private static func source(for host: String?) -> EndpointSource { + guard let host, isTailscaleIPv4Host(host) else { return .manual } + return .tailscale + } + + private static func isTailscaleIPv4Host(_ host: String) -> Bool { + let parts = host.split(separator: ".").compactMap { UInt8($0) } + guard parts.count == 4 else { return false } + return parts[0] == 100 && (parts[1] & 0b1100_0000) == 0b0100_0000 + } +} diff --git a/ios/SimDeckStudio/SimDeckStudio.entitlements b/ios/SimDeckStudio/SimDeckStudio.entitlements new file mode 100644 index 00000000..28586c6a --- /dev/null +++ b/ios/SimDeckStudio/SimDeckStudio.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.associated-domains + + applinks:simdeck.djdev.me + + + diff --git a/ios/SimDeckStudio/Streaming/WebRTCClient.swift b/ios/SimDeckStudio/Streaming/WebRTCClient.swift new file mode 100644 index 00000000..72700b0a --- /dev/null +++ b/ios/SimDeckStudio/Streaming/WebRTCClient.swift @@ -0,0 +1,816 @@ +import Foundation +@preconcurrency import WebRTC + +final class WebRTCClient: NSObject { + let clientID = "simdeck-ios-\(UUID().uuidString)" + + var onConnectionState: (@Sendable (RTCPeerConnectionState) -> Void)? + var onVideoSize: (@Sendable (CGSize) -> Void)? + var onMessage: (@Sendable (String) -> Void)? + var onDiagnostics: (@Sendable (StreamDiagnostics) -> Void)? + var onReconnectNeeded: (@Sendable (String) -> Void)? + + private static let initializeSSL: Void = { + RTCInitializeSSL() + }() + + private let factory: RTCPeerConnectionFactory + private var peerConnection: RTCPeerConnection? + private var controlChannel: RTCDataChannel? + private var telemetryChannel: RTCDataChannel? + private var remoteTrack: RTCVideoTrack? + private var renderers: [any RTCVideoRenderer] = [] + private var pendingControlMessages: [Data] = [] + private var keepAliveTask: Task? + private var statsTask: Task? + private var renderWatchdogTask: Task? + private var activeSimulatorID: String? + private var peerConnectionState = "new" + private var iceConnectionState = "new" + private var iceGatheringState = "new" + private var signalingState = "stable" + private var lastDecodedFrames: UInt64 = 0 + private var lastDecodedFrameAt = Date() + private var lastPacketsReceived: UInt64 = 0 + private var lastPacketReceivedAt = Date() + private var lastStatsSampleAt: Date? + private var lastStatsDecodedFrames: UInt64 = 0 + private var lastStatsRenderedFrames: UInt64 = 0 + private var lastStatsPacketsReceived: UInt64 = 0 + private var lastStallRecoveryAt = Date.distantPast + private var lastReconnectRequestedAt = Date.distantPast + private var lastUserActivityAt = Date.distantPast + private var renderedFrameCount: UInt64 = 0 + private var lastRenderedFrameAt = Date() + private var isAppForeground = true + private var isDisconnecting = false + + override init() { + _ = Self.initializeSSL + factory = RTCPeerConnectionFactory( + encoderFactory: RTCDefaultVideoEncoderFactory(), + decoderFactory: RTCDefaultVideoDecoderFactory() + ) + super.init() + } + + func connect( + api: SimDeckAPI, + simulatorID: String, + health: HealthResponse, + streamConfig: StreamConfig + ) async throws -> WebRTCAnswerPayload { + disconnect() + isDisconnecting = false + + let configuration = RTCConfiguration() + configuration.sdpSemantics = .unifiedPlan + configuration.bundlePolicy = .maxBundle + configuration.rtcpMuxPolicy = .require + configuration.continualGatheringPolicy = .gatherContinually + configuration.tcpCandidatePolicy = .disabled + configuration.enableDscp = true + configuration.rtcpVideoReportIntervalMs = 250 + configuration.iceServers = iceServers(from: health) + if health.webRtc?.iceTransportPolicy?.lowercased() == "relay" { + configuration.iceTransportPolicy = .relay + } else { + configuration.iceTransportPolicy = .all + } + + let constraints = RTCMediaConstraints( + mandatoryConstraints: nil, + optionalConstraints: ["DtlsSrtpKeyAgreement": "true"] + ) + guard let peerConnection = factory.peerConnection(with: configuration, constraints: constraints, delegate: self) else { + throw SimDeckAPIError.invalidResponse + } + self.peerConnection = peerConnection + + let transceiverInit = RTCRtpTransceiverInit() + transceiverInit.direction = .recvOnly + _ = peerConnection.addTransceiver(of: .video, init: transceiverInit) + + let controlConfig = RTCDataChannelConfiguration() + controlConfig.isOrdered = true + controlChannel = peerConnection.dataChannel(forLabel: "simdeck-control", configuration: controlConfig) + controlChannel?.delegate = self + + let telemetryConfig = RTCDataChannelConfiguration() + telemetryConfig.isOrdered = false + telemetryConfig.maxRetransmits = 0 + telemetryChannel = peerConnection.dataChannel(forLabel: "simdeck-telemetry", configuration: telemetryConfig) + telemetryChannel?.delegate = self + + let offer = try await offer(for: peerConnection) + try await setLocalDescription(offer, on: peerConnection) + await waitForIceGathering(on: peerConnection, timeout: api.baseURL.isLoopbackOrLocal ? 0.35 : 3.0) + + guard let localDescription = peerConnection.localDescription else { + throw SimDeckAPIError.invalidResponse + } + let payload = WebRTCOfferPayload( + clientId: clientID, + sdp: localDescription.sdp, + streamConfig: StreamQualityPayload(config: streamConfig), + type: "offer" + ) + let answer = try await api.postWebRTCOffer(payload, udid: simulatorID) + try await setRemoteDescription( + RTCSessionDescription(type: .answer, sdp: answer.sdp), + on: peerConnection + ) + activeSimulatorID = simulatorID + lastDecodedFrames = 0 + lastPacketsReceived = 0 + lastDecodedFrameAt = Date() + lastPacketReceivedAt = Date() + lastStatsSampleAt = nil + lastStatsDecodedFrames = 0 + lastStatsRenderedFrames = 0 + lastStatsPacketsReceived = 0 + renderedFrameCount = 0 + lastRenderedFrameAt = Date() + lastStallRecoveryAt = .distantPast + lastReconnectRequestedAt = .distantPast + lastUserActivityAt = .distantPast + isAppForeground = true + sendPageVisibilityStats(visible: true, simulatorID: simulatorID) + sendStreamControl(foreground: true, forceKeyframe: true, snapshot: true) + startKeepAlive() + startStatsReporting(simulatorID: simulatorID) + startRenderWatchdog() + return answer + } + + func attachRenderer(_ renderer: any RTCVideoRenderer) { + let rendererObject = renderer as AnyObject + if renderers.contains(where: { ($0 as AnyObject) === rendererObject }) { + return + } + renderers.append(renderer) + remoteTrack?.add(renderer) + } + + func detachRenderer(_ renderer: any RTCVideoRenderer) { + remoteTrack?.remove(renderer) + let rendererObject = renderer as AnyObject + renderers.removeAll { ($0 as AnyObject) === rendererObject } + } + + func disconnect() { + isDisconnecting = true + keepAliveTask?.cancel() + keepAliveTask = nil + statsTask?.cancel() + statsTask = nil + renderWatchdogTask?.cancel() + renderWatchdogTask = nil + if let activeSimulatorID { + sendPageVisibilityStats(visible: false, simulatorID: activeSimulatorID) + } + sendStreamControl(foreground: false, forceKeyframe: false, snapshot: false, allowQueue: false) + activeSimulatorID = nil + for renderer in renderers { + remoteTrack?.remove(renderer) + } + renderers.removeAll() + remoteTrack = nil + controlChannel?.close() + telemetryChannel?.close() + controlChannel = nil + telemetryChannel = nil + pendingControlMessages.removeAll() + peerConnection?.close() + peerConnection = nil + } + + func sendTouch(x: Double, y: Double, phase: String) { + markUserActivity() + sendJSON(["type": "touch", "x": x, "y": y, "phase": phase]) + } + + func sendEdgeTouch(x: Double, y: Double, phase: String, edge: String) { + markUserActivity() + sendJSON(["type": "edgeTouch", "x": x, "y": y, "phase": phase, "edge": edge]) + } + + @discardableResult + func sendKey(keyCode: Int, modifiers: Int) -> Bool { + markUserActivity() + return sendJSON([ + "type": "key", + "keyCode": keyCode, + "modifiers": modifiers + ], allowQueue: false) + } + + @discardableResult + func dismissSimulatorKeyboard() -> Bool { + markUserActivity() + return sendJSON(["type": "dismissKeyboard"], allowQueue: false) + } + + func sendHome() { + markUserActivity() + sendJSON(["type": "home"]) + } + + func sendAppSwitcher() { + markUserActivity() + sendJSON(["type": "appSwitcher"]) + } + + func sendRotateLeft() { + markUserActivity() + sendJSON(["type": "rotateLeft"]) + } + + func sendRotateRight() { + markUserActivity() + sendJSON(["type": "rotateRight"]) + } + + @discardableResult + func sendToggleAppearance() -> Bool { + markUserActivity() + return sendJSON(["type": "toggleAppearance"], allowQueue: false) + } + + func sendLock() { + markUserActivity() + pressHardwareButton(button: "power", durationMs: 80) + } + + @discardableResult + func sendHardwareButton(button: String, phase: String, usagePage: Int?, usage: Int?) -> Bool { + markUserActivity() + var payload: [String: Any] = [ + "type": "button", + "button": button, + "phase": phase + ] + if let usagePage { + payload["usagePage"] = usagePage + } + if let usage { + payload["usage"] = usage + } + return sendJSON(payload, allowQueue: false) + } + + @discardableResult + func pressHardwareButton(button: String, durationMs: Int = 80, usagePage: Int? = nil, usage: Int? = nil) -> Bool { + markUserActivity() + var payload: [String: Any] = [ + "type": "button", + "button": button, + "durationMs": durationMs + ] + if let usagePage { + payload["usagePage"] = usagePage + } + if let usage { + payload["usage"] = usage + } + return sendJSON(payload, allowQueue: false) + } + + func requestKeyframe() { + markUserActivity() + sendStreamControl(foreground: true, forceKeyframe: true, snapshot: true) + } + + func applyStreamQuality(_ config: StreamConfig) { + sendJSON([ + "type": "streamQuality", + "config": StreamQualityPayload(config: config).jsonObject + ]) + sendStreamControl(foreground: true, forceKeyframe: true, snapshot: true) + } + + func recordRenderedFrame(_ frame: RTCVideoFrame?) { + guard frame != nil else { return } + renderedFrameCount += 1 + lastRenderedFrameAt = Date() + } + + func appDidBecomeActive() { + isAppForeground = true + startKeepAlive() + startRenderWatchdog() + if let activeSimulatorID { + sendPageVisibilityStats(visible: true, simulatorID: activeSimulatorID) + } + sendStreamControl(foreground: true, forceKeyframe: true, snapshot: true, allowQueue: false) + let now = Date() + let staleFrameGap = now.timeIntervalSince(max(lastRenderedFrameAt, lastDecodedFrameAt)) + if peerConnectionState != "connected" || staleFrameGap > 4 { + requestReconnect(reason: "foreground-resume") + } + } + + func appDidEnterBackground() { + isAppForeground = false + keepAliveTask?.cancel() + keepAliveTask = nil + if let activeSimulatorID { + sendPageVisibilityStats(visible: false, simulatorID: activeSimulatorID) + } + sendStreamControl(foreground: false, forceKeyframe: false, snapshot: false, allowQueue: false) + } + + private func sendStreamControl( + foreground: Bool, + forceKeyframe: Bool, + snapshot: Bool, + allowQueue: Bool = true + ) { + sendJSON([ + "type": "streamControl", + "clientId": clientID, + "foreground": foreground, + "forceKeyframe": forceKeyframe, + "snapshot": snapshot + ], allowQueue: allowQueue) + } + + private func markUserActivity() { + lastUserActivityAt = Date() + } + + @discardableResult + private func sendJSON(_ object: [String: Any], allowQueue: Bool = true) -> Bool { + guard let data = try? JSONSerialization.data(withJSONObject: object) else { return false } + let isMove = Self.isMoveControlMessage(object) + if isMove, let controlChannel, controlChannel.readyState == .open, controlChannel.bufferedAmount > 128_000 { + return false + } + if sendControlData(data) { + return true + } + guard allowQueue, !isMove else { return false } + pendingControlMessages.append(data) + if pendingControlMessages.count > 80 { + pendingControlMessages.removeFirst(pendingControlMessages.count - 80) + } + return false + } + + private func requestReconnect(reason: String) { + guard isAppForeground, !isDisconnecting else { return } + let now = Date() + guard now.timeIntervalSince(lastReconnectRequestedAt) > 5 else { return } + lastReconnectRequestedAt = now + onReconnectNeeded?(reason) + } + + private func sendTelemetryJSON(_ object: [String: Any]) { + guard telemetryChannel?.readyState == .open, + let data = try? JSONSerialization.data(withJSONObject: object) else { + return + } + let buffer = RTCDataBuffer(data: data, isBinary: false) + _ = telemetryChannel?.sendData(buffer) + } + + @discardableResult + private func sendControlData(_ data: Data) -> Bool { + guard controlChannel?.readyState == .open else { + return false + } + let buffer = RTCDataBuffer(data: data, isBinary: false) + return controlChannel?.sendData(buffer) ?? false + } + + private func flushPendingControlMessages() { + guard controlChannel?.readyState == .open else { return } + let queued = pendingControlMessages + pendingControlMessages.removeAll() + for data in queued { + _ = sendControlData(data) + } + } + + private func startKeepAlive() { + keepAliveTask?.cancel() + keepAliveTask = Task { [weak self] in + while !Task.isCancelled { + try? await Task.sleep(for: .seconds(2)) + guard !Task.isCancelled else { return } + self?.sendStreamControl( + foreground: true, + forceKeyframe: false, + snapshot: false + ) + } + } + } + + private func startStatsReporting(simulatorID: String) { + statsTask?.cancel() + statsTask = Task { [weak self] in + while !Task.isCancelled { + self?.collectStats(simulatorID: simulatorID) + try? await Task.sleep(for: .seconds(2)) + } + } + } + + private func startRenderWatchdog() { + renderWatchdogTask?.cancel() + renderWatchdogTask = Task { [weak self] in + while !Task.isCancelled { + try? await Task.sleep(for: .milliseconds(750)) + guard let self, !Task.isCancelled, self.peerConnection != nil, self.isAppForeground else { return } + let now = Date() + let packetGap = now.timeIntervalSince(self.lastPacketReceivedAt) + let renderedGap = now.timeIntervalSince(self.lastRenderedFrameAt) + let decodedGap = now.timeIntervalSince(self.lastDecodedFrameAt) + let noFirstFrame = self.renderedFrameCount == 0 && renderedGap > 1.5 + let hardStall = max(packetGap, max(renderedGap, decodedGap)) > 4 + if hardStall { + self.requestReconnect(reason: "stream-stalled") + continue + } + let recentUserActivity = now.timeIntervalSince(self.lastUserActivityAt) < 8 + let activeStall = recentUserActivity && (packetGap > 1.5 || max(renderedGap, decodedGap) > 2) + guard (noFirstFrame || activeStall), + now.timeIntervalSince(self.lastStallRecoveryAt) > 3 else { + continue + } + self.lastStallRecoveryAt = now + self.sendStreamControl(foreground: true, forceKeyframe: true, snapshot: true) + } + } + } + + private func collectStats(simulatorID: String) { + guard let peerConnection else { return } + peerConnection.statistics { [weak self] report in + guard let self else { return } + let now = Date() + var inboundVideo: RTCStatistics? + var selectedPair: RTCStatistics? + var codecsByID: [String: RTCStatistics] = [:] + + for (_, statistic) in report.statistics { + if statistic.type == "codec" { + codecsByID[statistic.id] = statistic + } + if statistic.type == "inbound-rtp", statistic.mediaKind == "video" { + inboundVideo = statistic + } + if statistic.type == "candidate-pair", + statistic.stringValue("state") == "succeeded", + statistic.boolValue("nominated") == true { + selectedPair = statistic + } + } + + var stats: [String: Any] = [ + "clientId": self.clientID, + "kind": "webrtc", + "timestampMs": now.timeIntervalSince1970 * 1000, + "udid": simulatorID, + "status": self.peerConnectionState, + "detail": "receiver-stats", + "peerConnectionState": self.peerConnectionState, + "iceConnectionState": self.iceConnectionState, + "iceGatheringState": self.iceGatheringState, + "signalingState": self.signalingState, + "clientBundle": Bundle.main.bundleIdentifier ?? "dev.dj.simdeck.studio", + "userAgent": "SimDeck Studio iOS" + ] + + if let inboundVideo { + let codecID = inboundVideo.stringValue("codecId") + let codec = codecID.flatMap { codecsByID[$0]?.stringValue("mimeType") } + let receivedPackets = inboundVideo.uintValue("packetsReceived") ?? 0 + let packetsLost = inboundVideo.uintValue("packetsLost") ?? 0 + let decodedFrames = inboundVideo.uintValue("framesDecoded") ?? inboundVideo.uintValue("framesReceived") ?? 0 + let droppedFrames = inboundVideo.uintValue("framesDropped") ?? 0 + if receivedPackets > self.lastPacketsReceived { + self.lastPacketsReceived = receivedPackets + self.lastPacketReceivedAt = now + } + if decodedFrames > self.lastDecodedFrames { + self.lastDecodedFrames = decodedFrames + self.lastDecodedFrameAt = now + } + let latestPacketGapMs = now.timeIntervalSince(self.lastPacketReceivedAt) * 1000 + let latestDecodedFrameGapMs = now.timeIntervalSince(self.lastDecodedFrameAt) * 1000 + let latestRenderedFrameGapMs = now.timeIntervalSince(self.lastRenderedFrameAt) * 1000 + let latestFrameGapMs = max(latestDecodedFrameGapMs, latestRenderedFrameGapMs) + stats["codec"] = codec ?? "video" + stats["receivedPackets"] = receivedPackets + stats["packetsLost"] = packetsLost + stats["decodedFrames"] = decodedFrames + stats["decoderDroppedFrames"] = droppedFrames + stats["droppedFrames"] = droppedFrames + stats["latestPacketGapMs"] = latestPacketGapMs + stats["latestFrameGapMs"] = latestFrameGapMs + let recentUserActivity = now.timeIntervalSince(self.lastUserActivityAt) < 8 + let activeStall = recentUserActivity && (latestPacketGapMs > 1_500 || latestFrameGapMs > 2_000) + let noFirstFrame = self.renderedFrameCount == 0 && latestFrameGapMs > 1_500 + if (noFirstFrame || activeStall), + now.timeIntervalSince(self.lastStallRecoveryAt) > 3 { + self.lastStallRecoveryAt = now + self.sendStreamControl(foreground: true, forceKeyframe: true, snapshot: true) + } + if let width = inboundVideo.uintValue("frameWidth") { + stats["width"] = width + } + if let height = inboundVideo.uintValue("frameHeight") { + stats["height"] = height + } + stats["renderedFrames"] = self.renderedFrameCount + if let decodedFps = inboundVideo.doubleValue("framesPerSecond") { + stats["decodedFps"] = decodedFps + } + if let previousSampleAt = self.lastStatsSampleAt { + let elapsed = now.timeIntervalSince(previousSampleAt) + if elapsed > 0 { + stats["packetFps"] = Double(receivedPackets.saturatingDelta(from: self.lastStatsPacketsReceived)) / elapsed + stats["decodedFps"] = Double(decodedFrames.saturatingDelta(from: self.lastStatsDecodedFrames)) / elapsed + stats["appFps"] = Double(self.renderedFrameCount.saturatingDelta(from: self.lastStatsRenderedFrames)) / elapsed + } + } + self.lastStatsSampleAt = now + self.lastStatsPacketsReceived = receivedPackets + self.lastStatsDecodedFrames = decodedFrames + self.lastStatsRenderedFrames = self.renderedFrameCount + } + if let selectedPair { + let local = selectedPair.stringValue("localCandidateId") ?? "local" + let remote = selectedPair.stringValue("remoteCandidateId") ?? "remote" + stats["selectedCandidatePair"] = "\(local) -> \(remote)" + } + + self.onDiagnostics?(StreamDiagnostics(stats: stats)) + self.sendTelemetryJSON(["type": "clientStats", "stats": stats]) + } + } + + private static func isMoveControlMessage(_ object: [String: Any]) -> Bool { + guard let type = object["type"] as? String, + let phase = object["phase"] as? String else { + return false + } + return phase == "moved" && (type == "touch" || type == "edgeTouch" || type == "multiTouch") + } + + private func sendPageVisibilityStats(visible: Bool, simulatorID: String) { + sendTelemetryJSON([ + "type": "clientStats", + "stats": [ + "clientId": clientID, + "kind": "page", + "timestampMs": Date().timeIntervalSince1970 * 1000, + "udid": simulatorID, + "visibilityState": visible ? "visible" : "hidden", + "focused": visible + ] + ]) + } + + private func iceServers(from health: HealthResponse) -> [RTCIceServer] { + let servers = health.webRtc?.iceServers ?? [IceServer(urls: ["stun:stun.l.google.com:19302"])] + return servers.map { server in + RTCIceServer( + urlStrings: server.urls, + username: server.username, + credential: server.credential + ) + } + } + + private func offer(for peerConnection: RTCPeerConnection) async throws -> RTCSessionDescription { + let constraints = RTCMediaConstraints( + mandatoryConstraints: [ + "OfferToReceiveVideo": "true", + "OfferToReceiveAudio": "false" + ], + optionalConstraints: nil + ) + return try await withCheckedThrowingContinuation { continuation in + peerConnection.offer(for: constraints) { description, error in + if let error { + continuation.resume(throwing: error) + } else if let description { + continuation.resume(returning: description) + } else { + continuation.resume(throwing: SimDeckAPIError.invalidResponse) + } + } + } + } + + private func setLocalDescription(_ description: RTCSessionDescription, on peerConnection: RTCPeerConnection) async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + peerConnection.setLocalDescription(description) { error in + if let error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + } + } + } + + private func setRemoteDescription(_ description: RTCSessionDescription, on peerConnection: RTCPeerConnection) async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + peerConnection.setRemoteDescription(description) { error in + if let error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + } + } + } + + private func waitForIceGathering(on peerConnection: RTCPeerConnection, timeout: TimeInterval) async { + let deadline = Date().addingTimeInterval(timeout) + while peerConnection.iceGatheringState != .complete && Date() < deadline { + try? await Task.sleep(for: .milliseconds(50)) + } + } + + private func attachRemoteTrack(_ track: RTCVideoTrack) { + for renderer in renderers { + remoteTrack?.remove(renderer) + track.add(renderer) + } + remoteTrack = track + } +} + +extension WebRTCClient: RTCPeerConnectionDelegate { + func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) { + signalingState = stateChanged.statsLabel + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) { + if let track = stream.videoTracks.first { + attachRemoteTrack(track) + } + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {} + + func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {} + + func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) { + iceConnectionState = newState.statsLabel + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) { + iceGatheringState = newState.statsLabel + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {} + + func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {} + + func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) { + dataChannel.delegate = self + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCPeerConnectionState) { + peerConnectionState = newState.statsLabel + onConnectionState?(newState) + switch newState { + case .failed, .closed: + requestReconnect(reason: "peer-\(newState.statsLabel)") + case .disconnected: + Task { [weak self] in + try? await Task.sleep(for: .seconds(2)) + guard let self, self.peerConnectionState == "disconnected" else { return } + self.requestReconnect(reason: "peer-disconnected") + } + default: + break + } + } + + func peerConnection(_ peerConnection: RTCPeerConnection, didAdd rtpReceiver: RTCRtpReceiver, streams mediaStreams: [RTCMediaStream]) { + if let track = rtpReceiver.track as? RTCVideoTrack { + attachRemoteTrack(track) + } + } +} + +private extension RTCStatistics { + var mediaKind: String? { + stringValue("kind") ?? stringValue("mediaType") + } + + func stringValue(_ key: String) -> String? { + values[key] as? String + } + + func boolValue(_ key: String) -> Bool? { + if let value = values[key] as? NSNumber { + return value.boolValue + } + return values[key] as? Bool + } + + func uintValue(_ key: String) -> UInt64? { + if let value = values[key] as? NSNumber { + return value.uint64Value + } + return nil + } + + func doubleValue(_ key: String) -> Double? { + if let value = values[key] as? NSNumber { + return value.doubleValue + } + return nil + } +} + +private extension UInt64 { + func saturatingDelta(from previous: UInt64) -> UInt64 { + self >= previous ? self - previous : 0 + } +} + +private extension RTCPeerConnectionState { + var statsLabel: String { + switch self { + case .new: "new" + case .connecting: "connecting" + case .connected: "connected" + case .disconnected: "disconnected" + case .failed: "failed" + case .closed: "closed" + @unknown default: "unknown" + } + } +} + +private extension RTCIceConnectionState { + var statsLabel: String { + switch self { + case .new: "new" + case .checking: "checking" + case .connected: "connected" + case .completed: "completed" + case .failed: "failed" + case .disconnected: "disconnected" + case .closed: "closed" + case .count: "count" + @unknown default: "unknown" + } + } +} + +private extension RTCIceGatheringState { + var statsLabel: String { + switch self { + case .new: "new" + case .gathering: "gathering" + case .complete: "complete" + @unknown default: "unknown" + } + } +} + +private extension RTCSignalingState { + var statsLabel: String { + switch self { + case .stable: "stable" + case .haveLocalOffer: "have-local-offer" + case .haveLocalPrAnswer: "have-local-pranswer" + case .haveRemoteOffer: "have-remote-offer" + case .haveRemotePrAnswer: "have-remote-pranswer" + case .closed: "closed" + @unknown default: "unknown" + } + } +} + +extension WebRTCClient: RTCDataChannelDelegate { + func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) { + if dataChannel.readyState == .open, dataChannel.label == "simdeck-control" { + flushPendingControlMessages() + sendStreamControl(foreground: true, forceKeyframe: true, snapshot: true) + startKeepAlive() + } + } + + func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) { + guard !buffer.isBinary, let text = String(data: buffer.data, encoding: .utf8) else { return } + onMessage?(text) + } +} + +extension URL { + var isLoopbackOrLocal: Bool { + guard let host = host(percentEncoded: false)?.lowercased() else { return false } + return host == "localhost" || host == "127.0.0.1" || host == "::1" || host.hasSuffix(".local") + } +} diff --git a/ios/SimDeckStudio/Streaming/WebRTCVideoView.swift b/ios/SimDeckStudio/Streaming/WebRTCVideoView.swift new file mode 100644 index 00000000..b9e832a1 --- /dev/null +++ b/ios/SimDeckStudio/Streaming/WebRTCVideoView.swift @@ -0,0 +1,154 @@ +import CoreImage +import SwiftUI +import UIKit +@preconcurrency import WebRTC + +struct WebRTCVideoView: UIViewRepresentable { + let client: WebRTCClient? + let onVideoSize: (CGSize) -> Void + let onFrameRendered: () -> Void + let onFrameSnapshot: (UIImage) -> Void + + func makeCoordinator() -> Coordinator { + Coordinator( + client: client, + onVideoSize: onVideoSize, + onFrameRendered: onFrameRendered, + onFrameSnapshot: onFrameSnapshot + ) + } + + func makeUIView(context: Context) -> RTCMTLVideoView { + let view = RTCMTLVideoView(frame: .zero) + view.videoContentMode = .scaleAspectFit + view.backgroundColor = .black + context.coordinator.attach(view) + return view + } + + func updateUIView(_ uiView: RTCMTLVideoView, context: Context) { + context.coordinator.onVideoSize = onVideoSize + context.coordinator.onFrameRendered = onFrameRendered + context.coordinator.onFrameSnapshot = onFrameSnapshot + if context.coordinator.client !== client { + context.coordinator.detach(uiView) + context.coordinator.client = client + context.coordinator.attach(uiView) + } + } + + static func dismantleUIView(_ uiView: RTCMTLVideoView, coordinator: Coordinator) { + coordinator.detach(uiView) + } + + final class Coordinator: NSObject, RTCVideoRenderer, RTCVideoViewDelegate { + var client: WebRTCClient? + var onVideoSize: (CGSize) -> Void + var onFrameRendered: () -> Void + var onFrameSnapshot: (UIImage) -> Void + private var lastReportedSize = CGSize.zero + private var hasReportedRenderedFrame = false + private var lastSnapshotAt = Date.distantPast + private static let snapshotInterval: TimeInterval = 0.75 + private static let snapshotContext = CIContext(options: [.priorityRequestLow: true]) + + init( + client: WebRTCClient?, + onVideoSize: @escaping (CGSize) -> Void, + onFrameRendered: @escaping () -> Void, + onFrameSnapshot: @escaping (UIImage) -> Void + ) { + self.client = client + self.onVideoSize = onVideoSize + self.onFrameRendered = onFrameRendered + self.onFrameSnapshot = onFrameSnapshot + } + + func attach(_ view: RTCMTLVideoView) { + view.delegate = self + client?.attachRenderer(view) + client?.attachRenderer(self) + } + + func detach(_ view: RTCMTLVideoView) { + view.delegate = nil + client?.detachRenderer(view) + client?.detachRenderer(self) + } + + func setSize(_ size: CGSize) { + reportVideoSize(size) + } + + func renderFrame(_ frame: RTCVideoFrame?) { + client?.recordRenderedFrame(frame) + guard let frame else { return } + if !hasReportedRenderedFrame { + hasReportedRenderedFrame = true + Task { @MainActor in + onFrameRendered() + } + } + captureSnapshotIfNeeded(from: frame) + } + + func videoView(_ videoView: any RTCVideoRenderer, didChangeVideoSize size: CGSize) { + reportVideoSize(size) + } + + private func reportVideoSize(_ size: CGSize) { + guard size != lastReportedSize else { return } + lastReportedSize = size + Task { @MainActor in + onVideoSize(size) + } + } + + private func captureSnapshotIfNeeded(from frame: RTCVideoFrame) { + let now = Date() + guard now.timeIntervalSince(lastSnapshotAt) >= Self.snapshotInterval else { return } + lastSnapshotAt = now + + guard let image = Self.image(from: frame) else { return } + Task { @MainActor in + onFrameSnapshot(image) + } + } + + private static func image(from frame: RTCVideoFrame) -> UIImage? { + guard let buffer = frame.buffer as? RTCCVPixelBuffer else { return nil } + var image = CIImage(cvPixelBuffer: buffer.pixelBuffer) + if buffer.requiresCropping() { + image = image.cropped(to: CGRect( + x: CGFloat(buffer.cropX), + y: CGFloat(buffer.cropY), + width: CGFloat(buffer.cropWidth), + height: CGFloat(buffer.cropHeight) + )) + } + guard let cgImage = snapshotContext.createCGImage(image, from: image.extent) else { return nil } + return UIImage(cgImage: cgImage, scale: 1, orientation: frame.uiImageOrientation) + } + } +} + +private extension RTCVideoRotation { + var uiImageOrientation: UIImage.Orientation { + switch rawValue { + case 90: + return .right + case 180: + return .down + case 270: + return .left + default: + return .up + } + } +} + +private extension RTCVideoFrame { + var uiImageOrientation: UIImage.Orientation { + rotation.uiImageOrientation + } +} diff --git a/ios/SimDeckStudio/Views/ContentView.swift b/ios/SimDeckStudio/Views/ContentView.swift new file mode 100644 index 00000000..56706c72 --- /dev/null +++ b/ios/SimDeckStudio/Views/ContentView.swift @@ -0,0 +1,1293 @@ +@preconcurrency import AVFoundation +import SwiftUI + +struct ContentView: View { + @Bindable var model: AppModel + @State private var searchText = "" + @State private var searchExpanded = false + + var body: some View { + navigationContent(usesSearchAccessory: true) + .task { + model.start() + } + } + + private func navigationContent(usesSearchAccessory: Bool) -> some View { + NavigationSplitView { + SidebarView( + model: model, + searchText: $searchText, + searchExpanded: $searchExpanded, + usesSearchAccessory: usesSearchAccessory + ) + } detail: { + SimulatorStreamView(model: model) + } + } +} + +private struct SidebarView: View { + @Bindable var model: AppModel + @Binding var searchText: String + @Binding var searchExpanded: Bool + let usesSearchAccessory: Bool + @State private var presentedSheet: SidebarSheet? + + private var filteredSimulators: [SimulatorMetadata] { + let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines) + guard !query.isEmpty else { return model.simulators } + return model.simulators.filter { simulator in + simulator.name.localizedCaseInsensitiveContains(query) + || simulator.subtitle.localizedCaseInsensitiveContains(query) + || simulator.udid.localizedCaseInsensitiveContains(query) + } + } + + var body: some View { + sidebarContent + .sheet(item: $presentedSheet) { sheet in + switch sheet { + case .servers: + ServerSelectionSheet(model: model) + case .connect: + ConnectServerSheet(model: model) + case .pair: + PairServerSheet(model: model) + case .settings: + SettingsSheet(model: model) + case .newSimulator: + NewSimulatorSheet(model: model) + } + } + .onChange(of: model.authEndpoint?.id) { _, endpointID in + if endpointID != nil { + presentedSheet = .pair + } + } + } + + @ViewBuilder + private var sidebarContent: some View { + if usesSearchAccessory { + sidebarList + .safeAreaInset(edge: .bottom, spacing: 0) { + SimulatorSearchDock( + model: model, + text: $searchText, + isExpanded: $searchExpanded + ) { + presentedSheet = .newSimulator + } + } + } else { + sidebarList + } + } + + private var sidebarList: some View { + List(selection: simulatorSelection) { + ForEach(filteredSimulators) { simulator in + SimulatorRow(simulator: simulator) + .tag(simulator.udid) + } + } + .navigationTitle("") + .navigationBarTitleDisplayMode(.inline) + .overlay { + if model.isBusy && model.simulators.isEmpty { + ProgressView() + } else if model.endpoint == nil { + ContentUnavailableView("Select a Server", systemImage: "server.rack") + } else if model.authEndpoint != nil { + VStack(spacing: 16) { + ContentUnavailableView("Pair Server", systemImage: "lock") + Button { + presentedSheet = .pair + } label: { + Label("Pair", systemImage: "checkmark.seal") + } + .buttonStyle(.borderedProminent) + } + } else if filteredSimulators.isEmpty { + ContentUnavailableView( + searchText.isEmpty ? "No Simulators" : "No Results", + systemImage: searchText.isEmpty ? "iphone.slash" : "magnifyingglass" + ) + } + } + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button { + model.hapticSelection() + presentedSheet = .settings + } label: { + Label("Settings", systemImage: "gearshape") + } + } + ToolbarItem(placement: .principal) { + ServerTitleButton(model: model) { + model.hapticSelection() + presentedSheet = .servers + } + } + ToolbarItem(placement: .primaryAction) { + Button { + model.hapticSelection() + presentedSheet = .connect + } label: { + Label("Connect", systemImage: "personalhotspot") + } + } + } + } + + private var simulatorSelection: Binding { + Binding { + model.selectedSimulatorID + } set: { udid in + model.selectSimulator(udid) + } + } +} + +private enum SidebarSheet: Identifiable { + case servers + case connect + case pair + case settings + case newSimulator + + var id: Self { self } +} + +private struct ServerTitleButton: View { + @Bindable var model: AppModel + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack(spacing: 10) { + Circle() + .fill(titleColor) + .frame(width: 7, height: 7) + Spacer(minLength: 4) + VStack(alignment: .center, spacing: 1) { + Text(model.selectedEndpointTitle) + .font(.headline) + .lineLimit(1) + .multilineTextAlignment(.center) + Text(model.selectedEndpointSubtitle) + .font(.caption2) + .foregroundStyle(.secondary) + .lineLimit(1) + .multilineTextAlignment(.center) + } + Spacer(minLength: 4) + Image(systemName: "chevron.down") + .font(.caption2.weight(.semibold)) + .foregroundStyle(.secondary) + } + .padding(.leading, 14) + .padding(.trailing, 12) + .padding(.vertical, 5) + .contentShape(Capsule()) + } + .buttonStyle(.plain) + .frame(minWidth: 170, maxWidth: 240) + .frame(height: 42) + .modifier(GlassCapsuleModifier(interactive: true)) + } + + private var titleColor: Color { + if model.authEndpoint != nil { + return .orange + } + return model.endpoint == nil ? .secondary : .green + } +} + +private struct ServerSelectionSheet: View { + @Bindable var model: AppModel + @Environment(\.dismiss) private var dismiss + @State private var renamingEndpoint: SimDeckEndpoint? + @State private var renameText = "" + + var body: some View { + NavigationStack { + List { + if model.savedEndpoints.isEmpty && model.automaticEndpoints.isEmpty { + ContentUnavailableView("No Servers", systemImage: "server.rack") + } else { + if !model.savedEndpoints.isEmpty { + Section("Saved") { + ForEach(model.savedEndpoints) { endpoint in + serverButton(endpoint, saveEndpoint: false) + .swipeActions(edge: .trailing) { + Button(role: .destructive) { + model.deleteSavedEndpoint(endpoint) + } label: { + Label("Delete", systemImage: "trash") + } + Button { + beginRenaming(endpoint) + } label: { + Label("Rename", systemImage: "pencil") + } + } + .contextMenu { + Button { + beginRenaming(endpoint) + } label: { + Label("Rename", systemImage: "pencil") + } + Button(role: .destructive) { + model.deleteSavedEndpoint(endpoint) + } label: { + Label("Delete", systemImage: "trash") + } + } + } + } + } + + if !model.automaticEndpoints.isEmpty { + Section("Auto-Detected") { + ForEach(model.automaticEndpoints) { endpoint in + serverButton(endpoint, saveEndpoint: false) + } + } + } + } + } + .alert("Rename Server", isPresented: renameAlertBinding) { + TextField("Name", text: $renameText) + Button("Cancel", role: .cancel) { + renamingEndpoint = nil + } + Button("Save") { + if let renamingEndpoint { + model.renameSavedEndpoint(renamingEndpoint, to: renameText) + } + renamingEndpoint = nil + } + .disabled(renameText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + .navigationTitle("SimDeck Servers") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Done") { + model.hapticSelection() + dismiss() + } + } + ToolbarItem(placement: .primaryAction) { + Button { + model.hapticSelection() + model.discovery.refresh() + } label: { + Label("Refresh", systemImage: "arrow.clockwise") + } + .disabled(model.discovery.isScanning) + } + } + } + .presentationDetents([.medium, .large]) + } + + private func serverButton(_ endpoint: SimDeckEndpoint, saveEndpoint: Bool) -> some View { + Button { + model.hapticSelection() + Task { + if await model.connect(endpoint, autoStart: false, saveEndpoint: saveEndpoint) { + dismiss() + } + } + } label: { + HStack(spacing: 12) { + EndpointRow(endpoint: endpoint) + Spacer() + if model.endpoint?.baseURL == endpoint.baseURL { + Image(systemName: "checkmark") + .font(.headline) + .foregroundStyle(.tint) + } + } + } + .buttonStyle(.plain) + } + + private var renameAlertBinding: Binding { + Binding { + renamingEndpoint != nil + } set: { isPresented in + if !isPresented { + renamingEndpoint = nil + } + } + } + + private func beginRenaming(_ endpoint: SimDeckEndpoint) { + model.hapticSelection() + renamingEndpoint = endpoint + renameText = endpoint.name + } +} + +private struct PairServerSheet: View { + @Bindable var model: AppModel + @Environment(\.dismiss) private var dismiss + @State private var isScanning = false + + var body: some View { + NavigationStack { + Form { + if let endpoint = model.authEndpoint { + Section("Server") { + EndpointRow(endpoint: endpoint) + } + } + + Section("Pair") { + TextField("Pairing Code", text: $model.pairingCode) + .keyboardType(.numberPad) + Button { + model.hapticSelection() + isScanning = true + } label: { + Label("Scan QR Code", systemImage: "qrcode.viewfinder") + } + Button { + model.hapticSelection() + Task { + if await model.pair() { + dismiss() + } + } + } label: { + Label("Pair", systemImage: "checkmark.seal") + } + .disabled(model.pairingCode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + + Section("Token") { + SecureField("Token", text: $model.manualToken) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + Button { + model.hapticSelection() + isScanning = true + } label: { + Label("Scan Pairing QR", systemImage: "qrcode.viewfinder") + } + Button { + model.hapticSelection() + Task { + if await model.useToken() { + dismiss() + } + } + } label: { + Label("Use Token", systemImage: "key") + } + .disabled(model.manualToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + + if !model.status.isEmpty { + Section { + Text(model.status) + .font(.footnote) + .foregroundStyle(.secondary) + } + } + } + .navigationTitle("Pair Server") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Done") { + model.hapticSelection() + dismiss() + } + } + } + } + .sheet(isPresented: $isScanning) { + QRCodeScannerSheet(model: model) + } + .presentationDetents([.medium, .large]) + } +} + +private struct SettingsSheet: View { + @Bindable var model: AppModel + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + Form { + Section { + Toggle("Haptics", isOn: $model.hapticsEnabled) + .onChange(of: model.hapticsEnabled) { _, enabled in + if enabled { + model.hapticSuccess() + } + } + } + } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Done") { + model.hapticSelection() + dismiss() + } + } + } + } + .presentationDetents([.medium]) + } +} + +private struct NewSimulatorSheet: View { + @Bindable var model: AppModel + @Environment(\.dismiss) private var dismiss + @State private var options: SimulatorCreateOptionsResponse? + @State private var platform: CreationPlatform = .ios + @State private var name = "" + @State private var nameDirty = false + @State private var deviceTypeIdentifier = "" + @State private var runtimeIdentifier = "" + @State private var pairedWatch = false + @State private var watchName = "" + @State private var watchNameDirty = false + @State private var watchDeviceTypeIdentifier = "" + @State private var watchRuntimeIdentifier = "" + @State private var androidName = "" + @State private var androidNameDirty = false + @State private var androidDeviceTypeIdentifier = "" + @State private var androidSystemImageIdentifier = "" + @State private var isLoading = false + @State private var isCreating = false + @State private var error = "" + + private var runtimeOptions: [SimulatorRuntimeOption] { + compatibleRuntimes(deviceTypeIdentifier, options: options) + } + + private var watchRuntimeOptions: [SimulatorRuntimeOption] { + compatibleRuntimes(watchDeviceTypeIdentifier, options: options) + } + + private var watchDeviceTypes: [SimulatorDeviceTypeOption] { + (options?.deviceTypes ?? []).filter { + isWatchDeviceType($0) && !compatibleRuntimes($0.identifier, options: options).isEmpty + } + } + + private var selectedDeviceType: SimulatorDeviceTypeOption? { + options?.deviceTypes.first { $0.identifier == deviceTypeIdentifier } + } + + private var selectedAndroidDeviceType: AndroidEmulatorDeviceTypeOption? { + options?.android?.deviceTypes.first { $0.identifier == androidDeviceTypeIdentifier } + } + + private var selectedAndroidSystemImage: AndroidEmulatorSystemImageOption? { + options?.android?.systemImages.first { $0.identifier == androidSystemImageIdentifier } + } + + private var pairedWatchAvailable: Bool { + guard let selectedDeviceType else { return false } + return isPhoneDeviceType(selectedDeviceType) && !watchDeviceTypes.isEmpty && !watchRuntimeOptions.isEmpty + } + + private var canCreate: Bool { + switch platform { + case .ios: + let baseReady = !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + && !deviceTypeIdentifier.isEmpty + && !runtimeIdentifier.isEmpty + let watchReady = !pairedWatch || ( + !watchName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + && !watchDeviceTypeIdentifier.isEmpty + && !watchRuntimeIdentifier.isEmpty + ) + return baseReady && watchReady + case .android: + return !(options?.android?.unavailableReason?.isEmpty == false) + && !androidName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + && !androidDeviceTypeIdentifier.isEmpty + && !androidSystemImageIdentifier.isEmpty + } + } + + var body: some View { + NavigationStack { + Form { + Picker("Platform", selection: $platform) { + ForEach(CreationPlatform.allCases) { platform in + Text(platform.label).tag(platform) + } + } + .pickerStyle(.segmented) + .onChange(of: platform) { _, _ in + model.hapticSelection() + error = "" + if platform == .android { + pairedWatch = false + } + } + + if isLoading { + Section { + HStack { + Spacer() + ProgressView() + Spacer() + } + } + } else if platform == .android { + androidFields + } else { + iosFields + if pairedWatchAvailable { + Section { + Toggle("Paired Apple Watch", isOn: $pairedWatch) + .onChange(of: pairedWatch) { _, _ in model.hapticSelection() } + } + } + if pairedWatch { + watchFields + } + } + + if !error.isEmpty { + Section { + Text(error) + .font(.footnote) + .foregroundStyle(.red) + } + } else if !model.status.isEmpty { + Section { + Text(model.status) + .font(.footnote) + .foregroundStyle(.secondary) + } + } + } + .navigationTitle("New Simulator") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + model.hapticSelection() + dismiss() + } + } + ToolbarItem(placement: .confirmationAction) { + Button(isCreating ? "Creating" : "Create") { + Task { await create() } + } + .disabled(isLoading || isCreating || !canCreate) + } + } + .task { + await loadOptions() + } + } + .presentationDetents([.large]) + } + + private var iosFields: some View { + Section("iOS Simulator") { + TextField("Simulator Name", text: $name) + .onChange(of: name) { _, _ in nameDirty = true } + Picker("Device Type", selection: $deviceTypeIdentifier) { + ForEach(options?.deviceTypes.filter { !isWatchDeviceType($0) } ?? []) { deviceType in + Text(deviceType.name).tag(deviceType.identifier) + } + } + .onChange(of: deviceTypeIdentifier) { _, identifier in + model.hapticSelection() + let deviceType = options?.deviceTypes.first { $0.identifier == identifier } + runtimeIdentifier = chooseCompatibleRuntime(identifier, options: options)?.identifier ?? "" + if !nameDirty { + name = deviceType?.name ?? "" + } + } + Picker("OS Version", selection: $runtimeIdentifier) { + ForEach(runtimeOptions) { runtime in + Text(runtime.name).tag(runtime.identifier) + } + } + .onChange(of: runtimeIdentifier) { _, _ in model.hapticSelection() } + } + } + + private var watchFields: some View { + Section("Apple Watch") { + TextField("Watch Name", text: $watchName) + .onChange(of: watchName) { _, _ in watchNameDirty = true } + Picker("Device Type", selection: $watchDeviceTypeIdentifier) { + ForEach(watchDeviceTypes) { deviceType in + Text(deviceType.name).tag(deviceType.identifier) + } + } + .onChange(of: watchDeviceTypeIdentifier) { _, identifier in + model.hapticSelection() + let deviceType = options?.deviceTypes.first { $0.identifier == identifier } + watchRuntimeIdentifier = chooseCompatibleRuntime(identifier, options: options)?.identifier ?? "" + if !watchNameDirty { + watchName = deviceType?.name ?? "" + } + } + Picker("OS Version", selection: $watchRuntimeIdentifier) { + ForEach(watchRuntimeOptions) { runtime in + Text(runtime.name).tag(runtime.identifier) + } + } + .onChange(of: watchRuntimeIdentifier) { _, _ in model.hapticSelection() } + } + } + + private var androidFields: some View { + Section("Android Emulator") { + if let unavailableReason = options?.android?.unavailableReason { + Text(unavailableReason) + .font(.footnote) + .foregroundStyle(.secondary) + } + TextField("Emulator Name", text: $androidName) + .onChange(of: androidName) { _, _ in androidNameDirty = true } + Picker("Device Profile", selection: $androidDeviceTypeIdentifier) { + ForEach(options?.android?.deviceTypes ?? []) { deviceType in + Text(deviceType.name).tag(deviceType.identifier) + } + } + .onChange(of: androidDeviceTypeIdentifier) { _, identifier in + model.hapticSelection() + let deviceType = options?.android?.deviceTypes.first { $0.identifier == identifier } + if !androidNameDirty, let deviceType { + androidName = defaultAndroidName(deviceType: deviceType, systemImage: selectedAndroidSystemImage) + } + } + Picker("System Image", selection: $androidSystemImageIdentifier) { + ForEach(options?.android?.systemImages ?? []) { systemImage in + Text(systemImage.name).tag(systemImage.identifier) + } + } + .onChange(of: androidSystemImageIdentifier) { _, identifier in + model.hapticSelection() + let systemImage = options?.android?.systemImages.first { $0.identifier == identifier } + if !androidNameDirty, let selectedAndroidDeviceType { + androidName = defaultAndroidName(deviceType: selectedAndroidDeviceType, systemImage: systemImage) + } + } + } + } + + private func loadOptions() async { + guard options == nil, let endpoint = model.endpoint else { return } + isLoading = true + error = "" + defer { isLoading = false } + do { + let loadedOptions = try await SimDeckAPI(endpoint: endpoint).simulatorCreateOptions() + options = loadedOptions + applyDefaults(from: loadedOptions) + } catch { + self.error = error.localizedDescription + model.hapticWarning() + } + } + + private func applyDefaults(from options: SimulatorCreateOptionsResponse) { + platform = model.selectedSimulator?.platform == "android-emulator" ? .android : .ios + let initialDeviceType = chooseInitialDeviceType( + options.deviceTypes, + selectedDeviceTypeIdentifier: model.selectedSimulator?.deviceTypeIdentifier + ) + deviceTypeIdentifier = initialDeviceType?.identifier ?? "" + runtimeIdentifier = chooseCompatibleRuntime( + initialDeviceType?.identifier ?? "", + options: options, + preferredIdentifier: model.selectedSimulator?.runtimeIdentifier + )?.identifier ?? "" + name = initialDeviceType?.name ?? "" + nameDirty = false + + let initialWatchDeviceType = chooseInitialWatchDeviceType(options) + watchDeviceTypeIdentifier = initialWatchDeviceType?.identifier ?? "" + watchRuntimeIdentifier = chooseCompatibleRuntime( + initialWatchDeviceType?.identifier ?? "", + options: options + )?.identifier ?? "" + watchName = initialWatchDeviceType?.name ?? "" + watchNameDirty = false + pairedWatch = false + + let initialAndroidDeviceType = chooseInitialAndroidDeviceType( + options, + preferredName: model.selectedSimulator?.android?.avdName + ) + let initialAndroidSystemImage = options.android?.systemImages.first + androidDeviceTypeIdentifier = initialAndroidDeviceType?.identifier ?? "" + androidSystemImageIdentifier = initialAndroidSystemImage?.identifier ?? "" + if let initialAndroidDeviceType { + androidName = defaultAndroidName(deviceType: initialAndroidDeviceType, systemImage: initialAndroidSystemImage) + } + androidNameDirty = false + } + + private func create() async { + guard canCreate else { return } + model.hapticSelection() + isCreating = true + error = "" + defer { isCreating = false } + let request = CreateSimulatorRequest( + platform: platform.rawValue, + name: platform == .android + ? androidName.trimmingCharacters(in: .whitespacesAndNewlines) + : name.trimmingCharacters(in: .whitespacesAndNewlines), + deviceTypeIdentifier: platform == .android ? androidDeviceTypeIdentifier : deviceTypeIdentifier, + runtimeIdentifier: platform == .android ? androidSystemImageIdentifier : runtimeIdentifier, + pairedWatch: platform == .ios && pairedWatch + ? CreatePairedWatchRequest( + name: watchName.trimmingCharacters(in: .whitespacesAndNewlines), + deviceTypeIdentifier: watchDeviceTypeIdentifier, + runtimeIdentifier: watchRuntimeIdentifier + ) + : nil + ) + if await model.createSimulator(request) { + dismiss() + } else { + error = model.status + } + } +} + +private enum CreationPlatform: String, CaseIterable, Identifiable { + case ios + case android + + var id: Self { self } + + var label: String { + switch self { + case .ios: "iOS" + case .android: "Android" + } + } +} + +private func chooseInitialDeviceType( + _ deviceTypes: [SimulatorDeviceTypeOption], + selectedDeviceTypeIdentifier: String? +) -> SimulatorDeviceTypeOption? { + deviceTypes.first { $0.identifier == selectedDeviceTypeIdentifier } + ?? deviceTypes.first(where: isPhoneDeviceType) + ?? deviceTypes.first { !isWatchDeviceType($0) } + ?? deviceTypes.first +} + +private func chooseInitialWatchDeviceType(_ options: SimulatorCreateOptionsResponse) -> SimulatorDeviceTypeOption? { + options.deviceTypes.first { + isWatchDeviceType($0) && !compatibleRuntimes($0.identifier, options: options).isEmpty + } +} + +private func chooseInitialAndroidDeviceType( + _ options: SimulatorCreateOptionsResponse, + preferredName: String? +) -> AndroidEmulatorDeviceTypeOption? { + let deviceTypes = options.android?.deviceTypes ?? [] + return deviceTypes.first { $0.identifier == preferredName } + ?? deviceTypes.first { $0.identifier == "pixel_8" } + ?? deviceTypes.first { $0.identifier.hasPrefix("pixel_") } + ?? deviceTypes.first +} + +private func chooseCompatibleRuntime( + _ deviceTypeIdentifier: String, + options: SimulatorCreateOptionsResponse?, + preferredIdentifier: String? = nil +) -> SimulatorRuntimeOption? { + let runtimes = compatibleRuntimes(deviceTypeIdentifier, options: options) + return runtimes.first { $0.identifier == preferredIdentifier } ?? runtimes.first +} + +private func compatibleRuntimes( + _ deviceTypeIdentifier: String, + options: SimulatorCreateOptionsResponse? +) -> [SimulatorRuntimeOption] { + guard !deviceTypeIdentifier.isEmpty, let options else { return [] } + let deviceType = options.deviceTypes.first { $0.identifier == deviceTypeIdentifier } + return options.runtimes.filter { runtime in + if runtime.isAvailable == false { + return false + } + return runtime.supportedDeviceTypeIdentifiers?.contains(deviceTypeIdentifier) == true + || deviceType?.supportedRuntimeIdentifiers?.contains(runtime.identifier) == true + } +} + +private func isPhoneDeviceType(_ deviceType: SimulatorDeviceTypeOption) -> Bool { + (deviceType.productFamily ?? "").lowercased() == "iphone" +} + +private func isWatchDeviceType(_ deviceType: SimulatorDeviceTypeOption) -> Bool { + (deviceType.productFamily ?? "").lowercased().contains("watch") +} + +private func defaultAndroidName( + deviceType: AndroidEmulatorDeviceTypeOption, + systemImage: AndroidEmulatorSystemImageOption? +) -> String { + let apiSuffix = systemImage?.apiLevel.map { "_API_\($0)" } ?? "" + let raw = "\(deviceType.name)\(apiSuffix)" + let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "_.-")) + let sanitized = raw.map { character in + character.unicodeScalars.allSatisfy { allowed.contains($0) } ? String(character) : "_" + }.joined() + return sanitized + .replacingOccurrences(of: "_+", with: "_", options: .regularExpression) + .trimmingCharacters(in: CharacterSet(charactersIn: "_")) +} + +private struct ConnectServerSheet: View { + @Bindable var model: AppModel + @Environment(\.dismiss) private var dismiss + @State private var isScanning = false + + var body: some View { + NavigationStack { + Form { + Section("Server") { + TextField("Host or Studio URL", text: $model.manualAddress) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .keyboardType(.URL) + SecureField("Token", text: $model.manualToken) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + Button { + model.hapticSelection() + isScanning = true + } label: { + Label("Scan Pairing QR", systemImage: "qrcode.viewfinder") + } + Button { + model.hapticSelection() + Task { + if await model.connectManual() { + dismiss() + } + } + } label: { + Label("Connect", systemImage: "link") + } + } + + if model.authEndpoint != nil { + Section("Pair") { + TextField("Pairing Code", text: $model.pairingCode) + .keyboardType(.numberPad) + Button { + model.hapticSelection() + isScanning = true + } label: { + Label("Scan QR Code", systemImage: "qrcode.viewfinder") + } + Button { + model.hapticSelection() + Task { + if await model.pair() { + dismiss() + } + } + } label: { + Label("Pair", systemImage: "checkmark.seal") + } + Button { + model.hapticSelection() + Task { + if await model.useToken() { + dismiss() + } + } + } label: { + Label("Use Token", systemImage: "key") + } + } + } + + if !model.status.isEmpty { + Section { + Text(model.status) + .font(.footnote) + .foregroundStyle(.secondary) + } + } + } + .navigationTitle("Connect") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Done") { + model.hapticSelection() + dismiss() + } + } + } + } + .sheet(isPresented: $isScanning) { + QRCodeScannerSheet(model: model) + } + .presentationDetents([.medium, .large]) + } +} + +private struct SimulatorSearchDock: View { + @Bindable var model: AppModel + @Binding var text: String + @Binding var isExpanded: Bool + let onCreateSimulator: () -> Void + @FocusState private var isFocused: Bool + + private var isSearchBarVisible: Bool { + isExpanded || !text.isEmpty + } + + var body: some View { + GeometryReader { proxy in + let width = isSearchBarVisible + ? max(48, proxy.size.width - 88) + : 48.0 + + HStack(spacing: 8) { + searchControl(width: width) + + Button { + model.hapticSelection() + onCreateSimulator() + } label: { + Label("New Simulator", systemImage: "plus") + .labelStyle(.iconOnly) + .foregroundStyle(.primary) + .frame(width: 48, height: 48) + .contentShape(Circle()) + } + .buttonStyle(.plain) + .modifier(GlassCircleModifier(interactive: true)) + .disabled(model.endpoint == nil || model.authEndpoint != nil) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing) + .padding(.horizontal, 16) + .padding(.vertical, 8) + .zIndex(1) + } + .frame(height: 64) + .animation(.snappy(duration: 0.24), value: isSearchBarVisible) + .onChange(of: isExpanded) { _, expanded in + guard expanded else { return } + Task { @MainActor in + try? await Task.sleep(nanoseconds: 80_000_000) + isFocused = true + } + } + } + + @ViewBuilder + private func searchControl(width: CGFloat) -> some View { + HStack(spacing: 8) { + if isSearchBarVisible { + Image(systemName: "magnifyingglass") + .foregroundStyle(.secondary) + + TextField("Search Simulators", text: $text) + .focused($isFocused) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .submitLabel(.search) + .transition(.opacity) + + Button { + model.hapticSelection() + if text.isEmpty { + isFocused = false + withAnimation(.snappy(duration: 0.24)) { + isExpanded = false + } + } else { + text = "" + } + } label: { + Label(text.isEmpty ? "Close Search" : "Clear", systemImage: "xmark.circle.fill") + } + .labelStyle(.iconOnly) + .foregroundStyle(.secondary) + .buttonStyle(.plain) + .transition(.opacity) + } else { + Button { + model.hapticSelection() + withAnimation(.snappy(duration: 0.24)) { + isExpanded = true + } + } label: { + Label("Search", systemImage: "magnifyingglass") + .labelStyle(.iconOnly) + .foregroundStyle(.primary) + .frame(width: 48, height: 48) + .contentShape(Circle()) + } + .buttonStyle(.plain) + } + } + .padding(.horizontal, isSearchBarVisible ? 14 : 0) + .frame(width: width, height: 48) + .contentShape(Capsule()) + .modifier(GlassCapsuleModifier(interactive: true)) + } +} + +private struct GlassCapsuleModifier: ViewModifier { + let interactive: Bool + + func body(content: Content) -> some View { + if #available(iOS 26.0, *) { + if interactive { + content.glassEffect(.regular.interactive(), in: .capsule) + } else { + content.glassEffect(.regular, in: .capsule) + } + } else { + content.background(.ultraThinMaterial, in: Capsule()) + } + } +} + +private struct GlassCircleModifier: ViewModifier { + let interactive: Bool + + func body(content: Content) -> some View { + if #available(iOS 26.0, *) { + if interactive { + content.glassEffect(.regular.interactive(), in: .circle) + } else { + content.glassEffect(.regular, in: .circle) + } + } else { + content.background(.ultraThinMaterial, in: Circle()) + } + } +} + +private struct QRCodeScannerSheet: View { + @Bindable var model: AppModel + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + QRCodeScannerView { value in + model.handleScannedPairingPayload(value) + dismiss() + } + .ignoresSafeArea(edges: .bottom) + .navigationTitle("Scan Pairing QR") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + model.hapticSelection() + dismiss() + } + } + } + } + } +} + +private struct QRCodeScannerView: UIViewControllerRepresentable { + let onScan: (String) -> Void + + func makeUIViewController(context: Context) -> QRCodeScannerViewController { + QRCodeScannerViewController(onScan: onScan) + } + + func updateUIViewController(_ uiViewController: QRCodeScannerViewController, context: Context) {} +} + +private final class QRCodeScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { + private let onScan: (String) -> Void + private let session = AVCaptureSession() + private var previewLayer: AVCaptureVideoPreviewLayer? + private var didScan = false + private let messageLabel = UILabel() + + init(onScan: @escaping (String) -> Void) { + self.onScan = onScan + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + return nil + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .black + configureMessageLabel() + requestCameraAccess() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + previewLayer?.frame = view.bounds + messageLabel.frame = CGRect( + x: 24, + y: view.safeAreaInsets.top + 24, + width: view.bounds.width - 48, + height: 64 + ) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + stopSession() + } + + func metadataOutput( + _ output: AVCaptureMetadataOutput, + didOutput metadataObjects: [AVMetadataObject], + from connection: AVCaptureConnection + ) { + guard !didScan, + let object = metadataObjects.compactMap({ $0 as? AVMetadataMachineReadableCodeObject }).first(where: { $0.type == .qr }), + let value = object.stringValue?.nilIfBlank else { + return + } + didScan = true + stopSession() + onScan(value) + } + + private func configureMessageLabel() { + messageLabel.text = "Scan the QR from simdeck pair" + messageLabel.textAlignment = .center + messageLabel.textColor = .white + messageLabel.font = .preferredFont(forTextStyle: .headline) + messageLabel.backgroundColor = UIColor.black.withAlphaComponent(0.45) + messageLabel.layer.cornerRadius = 14 + messageLabel.clipsToBounds = true + view.addSubview(messageLabel) + } + + private func requestCameraAccess() { + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: + configureSession() + case .notDetermined: + AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in + DispatchQueue.main.async { + granted ? self?.configureSession() : self?.showCameraDenied() + } + } + default: + showCameraDenied() + } + } + + private func configureSession() { + guard previewLayer == nil else { return } + guard let device = AVCaptureDevice.default(for: .video), + let input = try? AVCaptureDeviceInput(device: device), + session.canAddInput(input) else { + showScannerUnavailable() + return + } + session.addInput(input) + + let output = AVCaptureMetadataOutput() + guard session.canAddOutput(output) else { + showScannerUnavailable() + return + } + session.addOutput(output) + output.setMetadataObjectsDelegate(self, queue: .main) + output.metadataObjectTypes = [.qr] + + let previewLayer = AVCaptureVideoPreviewLayer(session: session) + previewLayer.videoGravity = .resizeAspectFill + previewLayer.frame = view.bounds + view.layer.insertSublayer(previewLayer, at: 0) + self.previewLayer = previewLayer + + DispatchQueue.global(qos: .userInitiated).async { [session] in + session.startRunning() + } + } + + private func stopSession() { + guard session.isRunning else { return } + DispatchQueue.global(qos: .userInitiated).async { [session] in + session.stopRunning() + } + } + + private func showCameraDenied() { + messageLabel.text = "Camera access is needed to scan pairing QR codes." + } + + private func showScannerUnavailable() { + messageLabel.text = "QR scanning is unavailable on this device." + } +} + +private struct EndpointRow: View { + let endpoint: SimDeckEndpoint + + var body: some View { + Label { + VStack(alignment: .leading, spacing: 2) { + Text(endpoint.name) + .lineLimit(1) + Text(endpoint.baseURL.absoluteString) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + } icon: { + Image(systemName: endpoint.source.systemImage) + .foregroundStyle(endpoint.requiresPairing ? .orange : .blue) + } + } +} + +struct SimulatorRow: View { + let simulator: SimulatorMetadata + + var body: some View { + Label { + VStack(alignment: .leading, spacing: 2) { + Text(simulator.name) + .lineLimit(1) + if !simulator.subtitle.isEmpty { + Text(simulator.subtitle) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + } + } icon: { + Image(systemName: simulator.systemImage) + .foregroundStyle(simulator.isBooted ? .green : .secondary) + } + } +} diff --git a/ios/SimDeckStudio/Views/SimulatorStreamView.swift b/ios/SimDeckStudio/Views/SimulatorStreamView.swift new file mode 100644 index 00000000..66d77d47 --- /dev/null +++ b/ios/SimDeckStudio/Views/SimulatorStreamView.swift @@ -0,0 +1,1326 @@ +import SwiftUI +import UIKit + +struct SimulatorStreamView: View { + @Bindable var model: AppModel + @Environment(\.colorScheme) private var colorScheme + @State private var activeTouchKind: StreamTouchKind? + @State private var activeTouchIndicatorID: UUID? + @State private var touchIndicators: [StreamTouchIndicator] = [] + @State private var touchOverlayRemovalTask: Task? + @State private var presentedSheet: StreamSheet? + @State private var keyboardCaptureActive = false + @State private var keyboardHeight: CGFloat = 0 + + var body: some View { + ZStack { + if model.selectedSimulator == nil { + ContentUnavailableView("No Simulator", systemImage: "iphone.slash") + } else { + streamViewport + } + + KeyboardCaptureView( + isActive: $keyboardCaptureActive, + onText: { model.sendKeyboardText($0) }, + onDelete: { model.sendKeyboardBackspace() } + ) + .frame(width: 1, height: 1) + .opacity(0.01) + .accessibilityHidden(true) + } + .navigationTitle("") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + StreamTitleButton(model: model) { + model.hapticSelection() + presentedSheet = .simulators + } + } + ToolbarItem(placement: .topBarTrailing) { + Menu { + Section("Stream") { + Text(model.streamConfig.summary) + Menu("Encoder") { + ForEach(StreamEncoder.allCases, id: \.self) { encoder in + Button { + model.setStreamEncoder(encoder) + } label: { + if model.streamConfig.encoder == encoder { + Label(encoder.label, systemImage: "checkmark") + } else { + Text(encoder.label) + } + } + } + } + Menu("Frame Rate") { + ForEach([15, 30, 60, 120], id: \.self) { fps in + Button { + model.setStreamFPS(fps) + } label: { + if model.streamConfig.fps == fps { + Label("\(fps) fps", systemImage: "checkmark") + } else { + Text("\(fps) fps") + } + } + } + } + Menu("Resolution") { + ForEach(StreamQualityPreset.allCases, id: \.self) { quality in + Button { + model.setStreamQuality(quality) + } label: { + if model.streamConfig.quality == quality { + Label(quality.label, systemImage: "checkmark") + } else { + Text(quality.label) + } + } + } + } + } + Section("Interaction") { + Toggle(isOn: Binding( + get: { model.touchOverlayVisible }, + set: { model.setTouchOverlayVisible($0) } + )) { + Label("Show Touch Overlay", systemImage: "hand.tap") + } + Button { + model.hapticSelection() + presentedSheet = .debugInfo + } label: { + Label("Debug Info", systemImage: "info.circle") + } + } + Divider() + Button { + model.hapticSelection() + Task { await model.refreshSimulators() } + } label: { + Label("Refresh", systemImage: "arrow.clockwise") + } + Button { + if model.selectedSimulator?.isBooted == true { + model.hapticSelection() + Task { await model.startStream() } + } else { + Task { await model.bootSelectedSimulator() } + } + } label: { + Label(model.selectedSimulator?.isBooted == true ? "Start Stream" : "Boot", systemImage: "play.circle") + } + .disabled(model.selectedSimulatorID == nil || model.endpoint == nil) + Button { + model.stopStream() + } label: { + Label("Stop", systemImage: "stop.circle") + } + .disabled(!model.canStopStream) + } label: { + Label("Stream Settings", systemImage: "gearshape") + } + } + } + .sheet(item: $presentedSheet) { sheet in + switch sheet { + case .simulators: + StreamSimulatorSelectionSheet(model: model) + case .debugInfo: + StreamDebugInfoSheet(model: model) + } + } + .safeAreaInset(edge: .bottom) { + if model.selectedSimulator?.isBooted == true { + StreamControlBar(model: model, keyboardCaptureActive: $keyboardCaptureActive) + } + } + .onChange(of: model.selectedSimulatorID) { _, _ in + keyboardCaptureActive = false + clearTouchOverlay() + } + .onChange(of: model.selectedSimulator?.isBooted == true) { _, isBooted in + if !isBooted { + keyboardCaptureActive = false + clearTouchOverlay() + } + } + .onChange(of: model.touchOverlayVisible) { _, isVisible in + if !isVisible { + clearTouchOverlay() + } + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in + updateKeyboardHeight(notification) + } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { notification in + updateKeyboardHeight(notification) + } + } + + private var streamViewport: some View { + GeometryReader { proxy in + let layout = DeviceViewportLayout( + chromeProfile: model.chromeProfile, + videoSize: model.videoSize, + availableSize: proxy.size + ) + let displayToken = model.streamDisplayToken + let screenMaskImage = model.chromeProfile?.hasScreenMask == true ? model.chromeScreenMask : nil + + ZStack(alignment: .topLeading) { + streamBackground + + Rectangle() + .fill(.black) + .frame(width: layout.screenBackingFrame.width, height: layout.screenBackingFrame.height) + .clippedToSimulatorScreen(cornerRadius: layout.screenCornerRadius + 2, maskImage: screenMaskImage) + .position(x: layout.screenBackingFrame.midX, y: layout.screenBackingFrame.midY) + + if showsCachedStreamFrame, let lastStreamFrame = model.lastStreamFrame { + CachedStreamFrameView( + image: lastStreamFrame, + cornerRadius: layout.screenCornerRadius + 1, + maskImage: screenMaskImage + ) + .frame(width: layout.videoFrame.width, height: layout.videoFrame.height) + .position(x: layout.videoFrame.midX, y: layout.videoFrame.midY) + .transition(.opacity) + } + + if model.selectedSimulator?.isBooted == true, model.currentStreamClient != nil { + WebRTCVideoView( + client: model.currentStreamClient, + onVideoSize: { size in + model.videoSize = size + }, + onFrameRendered: { + model.markStreamFrameRendered(displayToken: displayToken) + }, + onFrameSnapshot: { image in + model.updateLastStreamFrame(image, displayToken: displayToken) + } + ) + .id(displayToken) + .frame(width: layout.videoFrame.width, height: layout.videoFrame.height) + .clippedToSimulatorScreen(cornerRadius: layout.screenCornerRadius + 1, maskImage: screenMaskImage) + .position(x: layout.videoFrame.midX, y: layout.videoFrame.midY) + .opacity(model.hasCurrentStreamFrame ? 1 : 0) + } + + if let chromeImage = model.chromeImage, layout.usesChrome { + Image(uiImage: chromeImage) + .resizable() + .interpolation(.high) + .frame(width: layout.shellFrame.width, height: layout.shellFrame.height) + .position(x: layout.shellFrame.midX, y: layout.shellFrame.midY) + .shadow(color: .black.opacity(0.22), radius: 18, y: 10) + .allowsHitTesting(false) + } + + if model.selectedSimulator?.isBooted == true, + let chromeProfile = model.chromeProfile, + layout.usesChrome { + HardwareButtonLayer(model: model, chromeProfile: chromeProfile, layout: layout) + } + + if model.selectedSimulator?.isBooted == true, + model.touchOverlayVisible, + !touchIndicators.isEmpty { + TouchInteractionOverlay(indicators: touchIndicators) + .frame(width: layout.screenFrame.width, height: layout.screenFrame.height) + .clippedToSimulatorScreen(cornerRadius: layout.screenCornerRadius, maskImage: screenMaskImage) + .position(x: layout.screenFrame.midX, y: layout.screenFrame.midY) + .allowsHitTesting(false) + .transition(.opacity) + } + + if let simulator = model.selectedSimulator, !simulator.isBooted { + BootSimulatorOverlay(model: model, simulator: simulator) + .frame(width: layout.screenFrame.width, height: layout.screenFrame.height) + .clippedToSimulatorScreen(cornerRadius: layout.screenCornerRadius, maskImage: screenMaskImage) + .position(x: layout.screenFrame.midX, y: layout.screenFrame.midY) + } + + if showsFirstFrameSpinner { + StreamFirstFrameLoadingOverlay() + .frame(width: layout.screenFrame.width, height: layout.screenFrame.height) + .clippedToSimulatorScreen(cornerRadius: layout.screenCornerRadius, maskImage: screenMaskImage) + .position(x: layout.screenFrame.midX, y: layout.screenFrame.midY) + .transition(.opacity) + } + + if showsRetryOverlay { + StreamRetryOverlay(model: model) + .frame(width: layout.screenFrame.width, height: layout.screenFrame.height) + .clippedToSimulatorScreen(cornerRadius: layout.screenCornerRadius, maskImage: screenMaskImage) + .position(x: layout.screenFrame.midX, y: layout.screenFrame.midY) + .transition(.opacity) + } + } + .contentShape(Rectangle()) + .streamTouchGesture(model.selectedSimulator?.isBooted == true, gesture: touchGesture(in: layout.screenFrame)) + .animation(.snappy(duration: 0.3), value: keyboardCaptureActive) + .animation(.smooth(duration: 0.28), value: keyboardHeight) + } + .background(streamBackground) + } + + private var streamBackground: Color { + colorScheme == .dark ? Color(.systemBackground) : Color(.secondarySystemGroupedBackground) + } + + private var showsFirstFrameSpinner: Bool { + guard model.selectedSimulator?.isBooted == true else { return false } + return model.streamState == .connecting + || (model.currentStreamClient != nil && !model.hasCurrentStreamFrame) + } + + private var showsCachedStreamFrame: Bool { + guard model.selectedSimulator?.isBooted == true else { return false } + return model.lastStreamFrame != nil && !model.hasCurrentStreamFrame + } + + private var showsRetryOverlay: Bool { + guard model.selectedSimulator?.isBooted == true else { return false } + return model.streamState == .failed || model.streamState == .disconnected + } + + private func touchGesture(in screenFrame: CGRect) -> some Gesture { + DragGesture(minimumDistance: 0) + .onChanged { value in + if activeTouchKind == nil { + guard screenFrame.contains(value.startLocation), + let point = model.normalizedTouchPoint(location: value.startLocation, in: screenFrame) else { + return + } + activeTouchKind = point.y >= 0.93 ? .bottomEdge : .single + sendActiveTouch(location: value.location, in: screenFrame, phase: "began") + return + } + sendActiveTouch(location: value.location, in: screenFrame, phase: "moved") + } + .onEnded { value in + sendActiveTouch(location: value.location, in: screenFrame, phase: "ended") + activeTouchKind = nil + } + } + + private func sendActiveTouch(location: CGPoint, in screenFrame: CGRect, phase: String) { + updateTouchOverlay(location: location, in: screenFrame, phase: phase) + switch activeTouchKind { + case .bottomEdge: + model.sendEdgeTouch(location: location, in: screenFrame, phase: phase, edge: "bottom") + case .single: + model.sendTouch(location: location, in: screenFrame, phase: phase) + case nil: + break + } + } + + private func updateTouchOverlay(location: CGPoint, in screenFrame: CGRect, phase: String) { + guard model.touchOverlayVisible else { + clearTouchOverlay() + return + } + + let clampedLocation = clampedTouchPoint(location, in: screenFrame) + switch phase { + case "began": + touchOverlayRemovalTask?.cancel() + let id = UUID() + activeTouchIndicatorID = id + withAnimation(.snappy(duration: 0.12)) { + touchIndicators = [ + StreamTouchIndicator(id: id, start: clampedLocation, current: clampedLocation, isEnding: false) + ] + } + case "moved": + guard let activeTouchIndicatorID, + let index = touchIndicators.firstIndex(where: { $0.id == activeTouchIndicatorID }) else { + return + } + touchIndicators[index].current = clampedLocation + case "ended": + guard let activeTouchIndicatorID, + let index = touchIndicators.firstIndex(where: { $0.id == activeTouchIndicatorID }) else { + return + } + let endingID = activeTouchIndicatorID + touchIndicators[index].current = clampedLocation + touchIndicators[index].isEnding = true + self.activeTouchIndicatorID = nil + touchOverlayRemovalTask?.cancel() + touchOverlayRemovalTask = Task { @MainActor in + try? await Task.sleep(for: .milliseconds(240)) + withAnimation(.easeOut(duration: 0.16)) { + touchIndicators.removeAll { $0.id == endingID } + } + } + default: + break + } + } + + private func clampedTouchPoint(_ location: CGPoint, in screenFrame: CGRect) -> CGPoint { + CGPoint( + x: min(max(location.x - screenFrame.minX, 0), screenFrame.width), + y: min(max(location.y - screenFrame.minY, 0), screenFrame.height) + ) + } + + private func clearTouchOverlay() { + touchOverlayRemovalTask?.cancel() + touchOverlayRemovalTask = nil + activeTouchIndicatorID = nil + touchIndicators = [] + } + + private func updateKeyboardHeight(_ notification: Notification) { + let endFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? .zero + let duration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.28 + let height = notification.name == UIResponder.keyboardWillHideNotification + ? 0 + : max(0, UIScreen.main.bounds.height - endFrame.minY) + withAnimation(.easeOut(duration: duration)) { + keyboardHeight = height + } + if height <= 1 { + keyboardCaptureActive = false + } + } +} + +private enum StreamTouchKind { + case single + case bottomEdge +} + +private enum StreamSheet: Identifiable { + case simulators + case debugInfo + + var id: Self { self } +} + +private struct StreamTouchIndicator: Identifiable, Equatable { + let id: UUID + var start: CGPoint + var current: CGPoint + var isEnding: Bool +} + +private struct TouchInteractionOverlay: View { + let indicators: [StreamTouchIndicator] + + var body: some View { + ZStack(alignment: .topLeading) { + ForEach(indicators) { indicator in + Path { path in + path.move(to: indicator.start) + path.addLine(to: indicator.current) + } + .stroke(.white.opacity(indicator.isEnding ? 0.25 : 0.62), style: StrokeStyle(lineWidth: 4, lineCap: .round)) + .shadow(color: .black.opacity(0.28), radius: 4) + + Circle() + .fill(.white.opacity(indicator.isEnding ? 0.18 : 0.36)) + .stroke(.white.opacity(indicator.isEnding ? 0.36 : 0.86), lineWidth: 2) + .frame(width: indicator.isEnding ? 34 : 42, height: indicator.isEnding ? 34 : 42) + .position(x: indicator.current.x, y: indicator.current.y) + .shadow(color: .black.opacity(0.3), radius: 7) + .scaleEffect(indicator.isEnding ? 0.82 : 1) + } + } + .compositingGroup() + .accessibilityHidden(true) + } +} + +private struct BootSimulatorOverlay: View { + @Bindable var model: AppModel + let simulator: SimulatorMetadata + + var body: some View { + ZStack { + Color.black.opacity(0.08) + Button { + Task { await model.bootSelectedSimulator() } + } label: { + ZStack { + if model.isSelectedSimulatorBooting { + ProgressView() + .controlSize(.regular) + .tint(.white) + } else { + Image(systemName: "play.fill") + .font(.title2.weight(.semibold)) + .foregroundStyle(.white) + .offset(x: 2) + } + } + .frame(width: 72, height: 72) + .contentShape(Circle()) + } + .buttonStyle(.plain) + .disabled(model.isSelectedSimulatorBooting) + .modifier(StreamGlassCircleModifier(interactive: !model.isSelectedSimulatorBooting)) + .accessibilityLabel(model.isSelectedSimulatorBooting ? "Booting \(simulator.name)" : "Boot \(simulator.name)") + } + } +} + +private struct StreamFirstFrameLoadingOverlay: View { + var body: some View { + ZStack { + Color.clear + ProgressView() + .controlSize(.small) + .tint(.white) + } + .allowsHitTesting(false) + .accessibilityLabel("Loading stream") + } +} + +private struct CachedStreamFrameView: View { + let image: UIImage + let cornerRadius: CGFloat + let maskImage: UIImage? + + var body: some View { + Image(uiImage: image) + .resizable() + .scaledToFill() + .saturation(0.82) + .brightness(-0.08) + .overlay(Color.black.opacity(0.28)) + .clippedToSimulatorScreen(cornerRadius: cornerRadius, maskImage: maskImage) + .shadow(color: .black.opacity(0.34), radius: 16, y: 8) + .clipped() + .allowsHitTesting(false) + .accessibilityHidden(true) + } +} + +private struct StreamRetryOverlay: View { + @Bindable var model: AppModel + + var body: some View { + ZStack { + Color.black.opacity(0.06) + VStack(spacing: 10) { + Button { + model.retryStream() + } label: { + Image(systemName: "arrow.clockwise") + .font(.title3.weight(.semibold)) + .foregroundStyle(.white) + .frame(width: 52, height: 52) + .contentShape(Circle()) + } + .buttonStyle(.plain) + .modifier(StreamGlassCircleModifier(interactive: true)) + .accessibilityLabel("Retry Stream") + + Text("Retry") + .font(.caption.weight(.medium)) + .foregroundStyle(.white.opacity(0.88)) + } + } + } +} + +private struct StreamTitleButton: View { + @Bindable var model: AppModel + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack(spacing: 10) { + Circle() + .fill(statusColor) + .frame(width: 7, height: 7) + Spacer(minLength: 4) + VStack(alignment: .center, spacing: 1) { + Text(model.selectedSimulator?.name ?? "Select Simulator") + .font(.headline) + .lineLimit(1) + .multilineTextAlignment(.center) + Text(model.streamNavigationSubtitle) + .font(.caption2) + .foregroundStyle(.secondary) + .lineLimit(1) + .multilineTextAlignment(.center) + } + Spacer(minLength: 4) + Image(systemName: "chevron.down") + .font(.caption2.weight(.semibold)) + .foregroundStyle(.secondary) + } + .padding(.leading, 14) + .padding(.trailing, 12) + .padding(.vertical, 5) + .contentShape(Capsule()) + } + .buttonStyle(.plain) + .frame(minWidth: 190, maxWidth: 260) + .frame(height: 42) + .modifier(StreamGlassCapsuleModifier(interactive: true)) + .accessibilityElement(children: .combine) + } + + private var statusColor: Color { + if let selectedSimulator = model.selectedSimulator, !selectedSimulator.isBooted { + return model.isSelectedSimulatorBooting ? .orange : .secondary + } + switch model.streamState { + case .connected: + return .green + case .connecting: + return .orange + case .failed: + return .red + case .disconnected, .idle: + return .secondary + } + } +} + +private struct StreamSimulatorSelectionSheet: View { + @Bindable var model: AppModel + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + List { + ForEach(model.simulators) { simulator in + Button { + model.hapticSelection() + model.selectSimulator(simulator.udid) + dismiss() + } label: { + HStack(spacing: 12) { + SimulatorRow(simulator: simulator) + Spacer() + if model.selectedSimulatorID == simulator.udid { + Image(systemName: "checkmark") + .font(.headline) + .foregroundStyle(.tint) + } + } + } + .buttonStyle(.plain) + } + } + .navigationTitle("Simulators") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Done") { + model.hapticSelection() + dismiss() + } + } + ToolbarItem(placement: .primaryAction) { + Button { + model.hapticSelection() + Task { await model.refreshSimulators() } + } label: { + Label("Refresh", systemImage: "arrow.clockwise") + } + } + } + } + .presentationDetents([.medium, .large]) + } +} + +private struct StreamDebugInfoSheet: View { + @Bindable var model: AppModel + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + List { + Section("Stream") { + DebugInfoRow("State", value: model.streamState.rawValue) + DebugInfoRow("FPS", value: formattedDecimal(model.streamDiagnostics.renderedFps)) + DebugInfoRow("Decoded FPS", value: formattedDecimal(model.streamDiagnostics.decodedFps)) + DebugInfoRow("Packet FPS", value: formattedDecimal(model.streamDiagnostics.packetFps)) + DebugInfoRow("Resolution", value: resolution) + DebugInfoRow("Path", value: "webrtc") + DebugInfoRow("Config", value: model.streamConfig.summary) + DebugInfoRow("Codec", value: model.streamDiagnostics.codec.nilIfBlank ?? "-") + } + + Section("Frames") { + DebugInfoRow("Packets", value: "\(model.streamDiagnostics.receivedPackets)") + DebugInfoRow("Packet Loss", value: "\(model.streamDiagnostics.packetsLost)") + DebugInfoRow("Decoded", value: "\(model.streamDiagnostics.decodedFrames)") + DebugInfoRow("Rendered", value: "\(model.streamDiagnostics.renderedFrames)") + DebugInfoRow("Decode Drops", value: "\(model.streamDiagnostics.decoderDroppedFrames)") + DebugInfoRow("Present Drops", value: "\(model.streamDiagnostics.presentationDroppedFrames)") + DebugInfoRow("Frame Gap", value: formattedMilliseconds(model.streamDiagnostics.latestFrameGapMs)) + DebugInfoRow("Packet Gap", value: formattedMilliseconds(model.streamDiagnostics.latestPacketGapMs)) + } + + Section("Connection") { + DebugInfoRow("Peer", value: model.streamDiagnostics.peerConnectionState.nilIfBlank ?? "-") + DebugInfoRow("ICE", value: model.streamDiagnostics.iceConnectionState.nilIfBlank ?? "-") + DebugInfoRow("Gathering", value: model.streamDiagnostics.iceGatheringState.nilIfBlank ?? "-") + DebugInfoRow("Signaling", value: model.streamDiagnostics.signalingState.nilIfBlank ?? "-") + DebugInfoRow("Reconnects", value: "\(model.streamReconnects)") + DebugInfoRow("Reconnect Reason", value: model.streamReconnectReason.nilIfBlank ?? "-") + DebugInfoRow("Candidate Pair", value: model.streamDiagnostics.selectedCandidatePair.nilIfBlank ?? "-") + } + + Section("Target") { + DebugInfoRow("Server", value: model.endpoint?.baseURL.absoluteString ?? "-") + DebugInfoRow("Simulator", value: model.selectedSimulator?.name ?? "-") + DebugInfoRow("UDID", value: model.selectedSimulatorID ?? "-") + DebugInfoRow("Updated", value: model.streamDiagnostics.timestamp.formatted(date: .omitted, time: .standard)) + } + } + .navigationTitle("Debug Info") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { + model.hapticSelection() + dismiss() + } + } + } + } + .presentationDetents([.medium, .large]) + } + + private var resolution: String { + let diagnostics = model.streamDiagnostics + if diagnostics.width > 0, diagnostics.height > 0 { + return "\(diagnostics.width)x\(diagnostics.height)" + } + if model.videoSize.width > 0, model.videoSize.height > 0 { + return "\(Int(model.videoSize.width))x\(Int(model.videoSize.height))" + } + return "-" + } + + private func formattedDecimal(_ value: Double) -> String { + guard value.isFinite else { return "0.0" } + return value.formatted(.number.precision(.fractionLength(1))) + } + + private func formattedMilliseconds(_ value: Double) -> String { + guard value.isFinite, value > 0 else { return "-" } + return "\(value.formatted(.number.precision(.fractionLength(1)))) ms" + } +} + +private struct DebugInfoRow: View { + let title: LocalizedStringKey + let value: String + + init(_ title: LocalizedStringKey, value: String) { + self.title = title + self.value = value + } + + var body: some View { + LabeledContent(title) { + Text(value) + .fontDesign(.monospaced) + .foregroundStyle(.secondary) + .multilineTextAlignment(.trailing) + .textSelection(.enabled) + } + } +} + +private struct StreamBadge: View { + let state: StreamState + let size: CGSize + + var body: some View { + HStack(spacing: 8) { + Circle() + .fill(color) + .frame(width: 8, height: 8) + Text(label) + .font(.caption) + .monospacedDigit() + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(.thinMaterial, in: Capsule()) + } + + private var label: String { + if size.width > 0, size.height > 0 { + "\(state.rawValue) \(Int(size.width))x\(Int(size.height))" + } else { + state.rawValue + } + } + + private var color: Color { + switch state { + case .connected: .green + case .connecting: .orange + case .failed: .red + case .disconnected: .secondary + case .idle: .secondary + } + } +} + +private struct StreamControlBar: View { + @Bindable var model: AppModel + @Binding var keyboardCaptureActive: Bool + + var body: some View { + if #available(iOS 26.0, *) { + LiquidGlassStreamControlBar(model: model, keyboardCaptureActive: $keyboardCaptureActive) + } else { + LegacyStreamControlBar(model: model, keyboardCaptureActive: $keyboardCaptureActive) + } + } +} + +@available(iOS 26.0, *) +private struct LiquidGlassStreamControlBar: View { + @Bindable var model: AppModel + @Binding var keyboardCaptureActive: Bool + + var body: some View { + GlassEffectContainer(spacing: 14) { + StreamControlButtons(model: model, keyboardCaptureActive: $keyboardCaptureActive) + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + } +} + +private struct LegacyStreamControlBar: View { + @Bindable var model: AppModel + @Binding var keyboardCaptureActive: Bool + + var body: some View { + StreamControlButtons(model: model, keyboardCaptureActive: $keyboardCaptureActive) + .buttonStyle(StreamToolbarButtonStyle()) + .padding(.horizontal, 16) + .padding(.vertical, 10) + } +} + +private struct StreamControlButtons: View { + @Bindable var model: AppModel + @Binding var keyboardCaptureActive: Bool + + var body: some View { + HStack(spacing: 8) { + StreamHardwareControlButton("Home", systemImage: "house", buttonName: "home", model: model) + + StreamControlButton("Switcher", systemImage: "square.on.square") { model.sendAppSwitcher() } + + Spacer(minLength: 4) + + StreamControlButton("Appearance", systemImage: "circle.lefthalf.filled") { model.toggleAppearance() } + + StreamControlButton("Rotate Right", systemImage: "rotate.right") { model.rotateRight() } + + Spacer(minLength: 4) + + StreamHardwareControlButton("Lock", systemImage: "lock", buttonName: "power", model: model) + + StreamKeyboardControlButton(model: model, isActive: $keyboardCaptureActive) + } + } +} + +private struct StreamControlButton: View { + let title: LocalizedStringKey + let systemImage: String + let action: () -> Void + + init(_ title: LocalizedStringKey, systemImage: String, action: @escaping () -> Void) { + self.title = title + self.systemImage = systemImage + self.action = action + } + + var body: some View { + Button(action: action) { + StreamControlIconLabel(title: title, systemImage: systemImage) + } + .buttonStyle(.plain) + .buttonBorderShape(.circle) + } +} + +private struct StreamHardwareControlButton: View { + let title: LocalizedStringKey + let systemImage: String + let buttonName: String + @Bindable var model: AppModel + @State private var isPressed = false + + init(_ title: LocalizedStringKey, systemImage: String, buttonName: String, model: AppModel) { + self.title = title + self.systemImage = systemImage + self.buttonName = buttonName + self.model = model + } + + var body: some View { + StreamControlIconLabel(title: title, systemImage: systemImage) + .opacity(isPressed ? 0.45 : 1) + .gesture( + DragGesture(minimumDistance: 0) + .onChanged { _ in pressDown() } + .onEnded { _ in pressUp() } + ) + .onDisappear { + pressUp() + } + .accessibilityLabel(title) + .accessibilityAddTraits(.isButton) + .accessibilityAction { + model.tapHardwareButton(named: buttonName) + } + } + + private func pressDown() { + guard !isPressed else { return } + isPressed = true + model.sendHardwareButton(named: buttonName, phase: .down) + } + + private func pressUp() { + guard isPressed else { return } + isPressed = false + model.sendHardwareButton(named: buttonName, phase: .up) + } +} + +private struct StreamKeyboardControlButton: View { + @Bindable var model: AppModel + @Binding var isActive: Bool + + var body: some View { + Button { + model.hapticSelection() + withAnimation(.snappy(duration: 0.25)) { + isActive.toggle() + } + if !isActive { + model.dismissSimulatorKeyboard() + } + } label: { + StreamControlIconLabel(title: "Keyboard", systemImage: "keyboard") + .opacity(isActive ? 1 : 0.86) + .scaleEffect(isActive ? 1.04 : 1) + } + .buttonStyle(.plain) + .buttonBorderShape(.circle) + .accessibilityLabel("Keyboard") + .accessibilityValue(isActive ? "Active" : "Inactive") + } +} + +private struct StreamControlIconLabel: View { + let title: LocalizedStringKey + let systemImage: String + + @ViewBuilder + var body: some View { + let content = Label(title, systemImage: systemImage) + .labelStyle(.iconOnly) + .font(.body) + .foregroundStyle(.primary) + .frame(width: 44, height: 44) + .contentShape(Circle()) + if #available(iOS 26.0, *) { + content + .glassEffect(.regular.interactive(), in: .circle) + } else { + content + .background(.ultraThinMaterial, in: Circle()) + } + } +} + +private struct HardwareButtonLayer: View { + @Bindable var model: AppModel + let chromeProfile: ChromeProfile + let layout: DeviceViewportLayout + + var body: some View { + ForEach(chromeProfile.buttons ?? [], id: \.self) { button in + if let buttonName = button.hardwareWireName, button.width > 0, button.height > 0 { + HardwareButtonHitArea( + model: model, + button: button, + buttonName: buttonName, + frame: layout.chromeButtonFrame(button) + ) + } + } + } +} + +private struct HardwareButtonHitArea: View { + @Bindable var model: AppModel + let button: ChromeButtonProfile + let buttonName: String + let frame: CGRect + @State private var isPressed = false + + var body: some View { + Color.clear + .frame(width: hitFrame.width, height: hitFrame.height) + .contentShape(Rectangle()) + .position(x: hitFrame.midX, y: hitFrame.midY) + .gesture( + DragGesture(minimumDistance: 0) + .onChanged { _ in pressDown() } + .onEnded { _ in pressUp() } + ) + .onDisappear { + pressUp() + } + .accessibilityLabel(Text(button.label ?? button.name)) + .accessibilityAddTraits(.isButton) + .accessibilityAction { + model.tapHardwareButton(named: buttonName, usagePage: button.usagePage, usage: button.usage) + } + } + + private var hitFrame: CGRect { + let minimumTarget: CGFloat = 34 + let width = max(frame.width, minimumTarget) + let height = max(frame.height, minimumTarget) + return CGRect( + x: frame.midX - width / 2, + y: frame.midY - height / 2, + width: width, + height: height + ) + } + + private func pressDown() { + guard !isPressed else { return } + isPressed = true + model.sendHardwareButton( + named: buttonName, + phase: .down, + usagePage: button.usagePage, + usage: button.usage + ) + } + + private func pressUp() { + guard isPressed else { return } + isPressed = false + model.sendHardwareButton( + named: buttonName, + phase: .up, + usagePage: button.usagePage, + usage: button.usage + ) + } +} + +private struct KeyboardCaptureView: UIViewRepresentable { + @Binding var isActive: Bool + let onText: (String) -> Void + let onDelete: () -> Void + + func makeCoordinator() -> Coordinator { + Coordinator(isActive: $isActive) + } + + func makeUIView(context: Context) -> KeyboardCaptureTextView { + let view = KeyboardCaptureTextView() + view.delegate = context.coordinator + view.backgroundColor = .clear + view.tintColor = .clear + view.textColor = .clear + view.isScrollEnabled = false + view.autocorrectionType = .no + view.autocapitalizationType = .none + view.spellCheckingType = .no + view.smartDashesType = .no + view.smartInsertDeleteType = .no + view.smartQuotesType = .no + view.keyboardType = .default + view.returnKeyType = .default + view.textContentType = nil + view.textContainerInset = .zero + view.textContainer.lineFragmentPadding = 0 + view.inputAssistantItem.leadingBarButtonGroups = [] + view.inputAssistantItem.trailingBarButtonGroups = [] + return view + } + + func updateUIView(_ view: KeyboardCaptureTextView, context: Context) { + view.onText = onText + view.onDelete = onDelete + if isActive, !view.isFirstResponder { + DispatchQueue.main.async { + view.becomeFirstResponder() + } + } else if !isActive, view.isFirstResponder { + DispatchQueue.main.async { + view.resignFirstResponder() + } + } + } + + final class Coordinator: NSObject, UITextViewDelegate { + var isActive: Binding + + init(isActive: Binding) { + self.isActive = isActive + } + + func textViewDidEndEditing(_ textView: UITextView) { + isActive.wrappedValue = false + } + } +} + +private final class KeyboardCaptureTextView: UITextView { + var onText: ((String) -> Void)? + var onDelete: (() -> Void)? + + override var canBecomeFirstResponder: Bool { + true + } + + override var hasText: Bool { + true + } + + override func insertText(_ text: String) { + onText?(text) + } + + override func deleteBackward() { + onDelete?() + } + + override func paste(_ sender: Any?) { + guard let text = UIPasteboard.general.string, !text.isEmpty else { return } + onText?(text) + } +} + +private struct StreamGlassCapsuleModifier: ViewModifier { + let interactive: Bool + + func body(content: Content) -> some View { + if #available(iOS 26.0, *) { + if interactive { + content.glassEffect(.regular.interactive(), in: .capsule) + } else { + content.glassEffect(.regular, in: .capsule) + } + } else { + content.background(.ultraThinMaterial, in: Capsule()) + } + } +} + +private struct StreamGlassCircleModifier: ViewModifier { + let interactive: Bool + + func body(content: Content) -> some View { + if #available(iOS 26.0, *) { + if interactive { + content.glassEffect(.regular.interactive(), in: .circle) + } else { + content.glassEffect(.regular, in: .circle) + } + } else { + content.background(.ultraThinMaterial, in: Circle()) + } + } +} + +private struct StreamToolbarButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .foregroundStyle(.primary) + .frame(width: 44, height: 44) + .contentShape(Rectangle()) + .opacity(configuration.isPressed ? 0.45 : 1) + } +} + +private struct DeviceViewportLayout { + let shellFrame: CGRect + let screenFrame: CGRect + let screenBackingFrame: CGRect + let videoFrame: CGRect + let screenCornerRadius: CGFloat + let usesChrome: Bool + private let chromeCoordinateScale: CGFloat + + init(chromeProfile: ChromeProfile?, videoSize: CGSize, availableSize: CGSize) { + let viewport = CGRect(origin: .zero, size: availableSize) + .insetBy(dx: min(20, availableSize.width * 0.045), dy: 16) + + if let chromeProfile, + chromeProfile.totalWidth > 0, + chromeProfile.totalHeight > 0, + chromeProfile.screenWidth > 0, + chromeProfile.screenHeight > 0, + viewport.width > 0, + viewport.height > 0 { + let profileSize = CGSize(width: CGFloat(chromeProfile.totalWidth), height: CGFloat(chromeProfile.totalHeight)) + let shell = profileSize.aspectFit(in: viewport) + let scale = shell.width / profileSize.width + let screenRect = Self.chromeScreenRect(profile: chromeProfile) + shellFrame = shell + chromeCoordinateScale = scale + screenFrame = CGRect( + x: shell.minX + screenRect.minX * scale, + y: shell.minY + screenRect.minY * scale, + width: screenRect.width * scale, + height: screenRect.height * scale + ) + screenBackingFrame = screenFrame.insetBy(dx: -2, dy: -2) + videoFrame = screenFrame + screenCornerRadius = Self.screenCornerRadius( + profile: chromeProfile, + profileScreenRect: screenRect, + scale: scale + ) + usesChrome = true + return + } + + let fallbackSize = videoSize.width > 0 && videoSize.height > 0 + ? videoSize + : CGSize(width: 440, height: 956) + let screen = fallbackSize.aspectFit(in: viewport) + shellFrame = screen + screenFrame = screen + screenBackingFrame = screen + videoFrame = screen + screenCornerRadius = min(44, screen.width * 0.14) + usesChrome = false + chromeCoordinateScale = 1 + } + + func chromeButtonFrame(_ button: ChromeButtonProfile) -> CGRect { + guard usesChrome else { return .zero } + return CGRect( + x: shellFrame.minX + CGFloat(button.x) * chromeCoordinateScale, + y: shellFrame.minY + CGFloat(button.y) * chromeCoordinateScale, + width: CGFloat(button.width) * chromeCoordinateScale, + height: CGFloat(button.height) * chromeCoordinateScale + ) + } + + private static func chromeScreenRect(profile: ChromeProfile) -> CGRect { + CGRect( + x: CGFloat(profile.screenX), + y: CGFloat(profile.screenY), + width: CGFloat(profile.screenWidth), + height: CGFloat(profile.screenHeight) + ) + } + + private static func screenCornerRadius(profile: ChromeProfile, profileScreenRect: CGRect, scale: CGFloat) -> CGFloat { + let fullScreen = CGRect( + x: CGFloat(profile.screenX), + y: CGFloat(profile.screenY), + width: CGFloat(profile.screenWidth), + height: CGFloat(profile.screenHeight) + ) + guard abs(profileScreenRect.minX - fullScreen.minX) <= 0.5, + abs(profileScreenRect.minY - fullScreen.minY) <= 0.5, + abs(profileScreenRect.maxX - fullScreen.maxX) <= 0.5, + abs(profileScreenRect.maxY - fullScreen.maxY) <= 0.5 else { + return 0 + } + return min( + profileScreenRect.width * scale / 2, + profileScreenRect.height * scale / 2, + CGFloat(profile.cornerRadius) * scale + ) + } +} + +private extension ChromeButtonProfile { + var hardwareWireName: String? { + switch name.lowercased() { + case "action": + "action" + case "digital-crown", "crown": + "digital-crown" + case "home": + "home" + case "left-side-button": + "left-side-button" + case "lock", "power": + "power" + case "mute": + "mute" + case "side-button": + "side-button" + case "volume-down": + "volume-down" + case "volume-up": + "volume-up" + default: + nil + } + } +} + +private extension CGSize { + func aspectFit(in rect: CGRect) -> CGRect { + guard width > 0, height > 0, rect.width > 0, rect.height > 0 else { + return .zero + } + let scale = min(rect.width / width, rect.height / height) + let fittedSize = CGSize(width: width * scale, height: height * scale) + return CGRect( + x: rect.midX - fittedSize.width / 2, + y: rect.midY - fittedSize.height / 2, + width: fittedSize.width, + height: fittedSize.height + ) + } +} + +private extension View { + @ViewBuilder + func clippedToSimulatorScreen(cornerRadius: CGFloat, maskImage: UIImage?) -> some View { + if let maskImage { + self.mask( + Image(uiImage: maskImage) + .resizable() + .scaledToFill() + ) + } else { + self.clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)) + } + } + + @ViewBuilder + func streamTouchGesture(_ enabled: Bool, gesture: G) -> some View { + if enabled { + self.gesture(gesture) + } else { + self + } + } +} diff --git a/ios/Vendor/WebRTC.xcframework/Info.plist b/ios/Vendor/WebRTC.xcframework/Info.plist new file mode 100644 index 00000000..ffc98c42 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/Info.plist @@ -0,0 +1,40 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-arm64 + LibraryPath + WebRTC.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-x86_64_arm64-simulator + LibraryPath + WebRTC.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/ios/Vendor/WebRTC.xcframework/LICENSE b/ios/Vendor/WebRTC.xcframework/LICENSE new file mode 100644 index 00000000..4c41b7b2 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioDevice.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioDevice.h new file mode 100644 index 00000000..c28076ca --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioDevice.h @@ -0,0 +1,335 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef OSStatus (^RTC_OBJC_TYPE(RTCAudioDeviceGetPlayoutDataBlock))( + AudioUnitRenderActionFlags *_Nonnull actionFlags, + const AudioTimeStamp *_Nonnull timestamp, + NSInteger inputBusNumber, + UInt32 frameCount, + AudioBufferList *_Nonnull outputData); + +typedef OSStatus (^RTC_OBJC_TYPE(RTCAudioDeviceRenderRecordedDataBlock))( + AudioUnitRenderActionFlags *_Nonnull actionFlags, + const AudioTimeStamp *_Nonnull timestamp, + NSInteger inputBusNumber, + UInt32 frameCount, + AudioBufferList *_Nonnull inputData, + void *_Nullable renderContext); + +typedef OSStatus (^RTC_OBJC_TYPE(RTCAudioDeviceDeliverRecordedDataBlock))( + AudioUnitRenderActionFlags *_Nonnull actionFlags, + const AudioTimeStamp *_Nonnull timestamp, + NSInteger inputBusNumber, + UInt32 frameCount, + const AudioBufferList *_Nullable inputData, + void *_Nullable renderContext, + NS_NOESCAPE RTC_OBJC_TYPE( + RTCAudioDeviceRenderRecordedDataBlock) _Nullable renderBlock); + +/** + * Delegate object provided by native ADM during RTCAudioDevice initialization. + * Provides blocks to poll playback audio samples from native ADM and to feed + * recorded audio samples into native ADM. + */ +RTC_OBJC_EXPORT @protocol RTC_OBJC_TYPE +(RTCAudioDeviceDelegate) + /** + * Implementation of RTCAudioSource should call this block to feed recorded + * PCM (16-bit integer) into native ADM. Stereo data is expected to be + * interleaved starting with the left channel. Either `inputData` with + * pre-filled audio data must be provided during block call or `renderBlock` + * must be provided which must fill provided audio buffer with recorded + * samples. + * + * NOTE: Implementation of RTCAudioDevice is expected to call the block on + * the same thread until `notifyAudioInterrupted` is called. When + * `notifyAudioInterrupted` is called implementation can call the block on a + * different thread. + */ + @property(readonly, nonnull) + RTC_OBJC_TYPE(RTCAudioDeviceDeliverRecordedDataBlock) + deliverRecordedData; + +/** + * Provides input sample rate preference as it preferred by native ADM. + */ +@property(readonly) double preferredInputSampleRate; + +/** + * Provides input IO buffer duration preference as it preferred by native ADM. + */ +@property(readonly) NSTimeInterval preferredInputIOBufferDuration; + +/** + * Provides output sample rate preference as it preferred by native ADM. + */ +@property(readonly) double preferredOutputSampleRate; + +/** + * Provides output IO buffer duration preference as it preferred by native ADM. + */ +@property(readonly) NSTimeInterval preferredOutputIOBufferDuration; + +/** + * Implementation of RTCAudioDevice should call this block to request PCM + * (16-bit integer) from native ADM to play. Stereo data is interleaved starting + * with the left channel. + * + * NOTE: Implementation of RTCAudioDevice is expected to invoke of this block on + * the same thread until `notifyAudioInterrupted` is called. When + * `notifyAudioInterrupted` is called implementation can call the block from a + * different thread. + */ +@property(readonly, nonnull) RTC_OBJC_TYPE(RTCAudioDeviceGetPlayoutDataBlock) + getPlayoutData; + +/** + * Notifies native ADM that some of the audio input parameters of RTCAudioDevice + * like samle rate and/or IO buffer duration and/or IO latency had possibly + * changed. Native ADM will adjust its audio input buffer to match current + * parameters of audio device. + * + * NOTE: Must be called within block executed via `dispatchAsync` or + * `dispatchSync`. + */ +- (void)notifyAudioInputParametersChange; + +/** + * Notifies native ADM that some of the audio output parameters of + * RTCAudioDevice like samle rate and/or IO buffer duration and/or IO latency + * had possibly changed. Native ADM will adjust its audio output buffer to match + * current parameters of audio device. + * + * NOTE: Must be called within block executed via `dispatchAsync` or + * `dispatchSync`. + */ +- (void)notifyAudioOutputParametersChange; + +/** + * Notifies native ADM that audio input is interrupted and further audio playout + * and recording might happen on a different thread. + * + * NOTE: Must be called within block executed via `dispatchAsync` or + * `dispatchSync`. + */ +- (void)notifyAudioInputInterrupted; + +/** + * Notifies native ADM that audio output is interrupted and further audio + * playout and recording might happen on a different thread. + * + * NOTE: Must be called within block executed via `dispatchAsync` or + * `dispatchSync`. + */ +- (void)notifyAudioOutputInterrupted; + +/** + * Asynchronously execute block of code within the context of + * thread which owns native ADM. + * + * NOTE: Intended to be used to invoke `notifyAudioInputParametersChange`, + * `notifyAudioOutputParametersChange`, `notifyAudioInputInterrupted`, + * `notifyAudioOutputInterrupted` on native ADM thread. + * Also could be used by `RTCAudioDevice` implementation to tie + * mutations of underlying audio objects (AVAudioEngine, AudioUnit, etc) + * to the native ADM thread. Could be useful to handle events like audio route + * change, which could lead to audio parameters change. + */ +- (void)dispatchAsync:(dispatch_block_t)block; + +/** + * Synchronously execute block of code within the context of + * thread which owns native ADM. Allows reentrancy. + * + * NOTE: Intended to be used to invoke `notifyAudioInputParametersChange`, + * `notifyAudioOutputParametersChange`, `notifyAudioInputInterrupted`, + * `notifyAudioOutputInterrupted` on native ADM thread and make sure + * aforementioned is completed before `dispatchSync` returns. Could be useful + * when implementation of `RTCAudioDevice` tie mutation to underlying audio + * objects (AVAudioEngine, AudioUnit, etc) to own thread to satisfy requirement + * that native ADM audio parameters must be kept in sync with current audio + * parameters before audio is actually played or recorded. + */ +- (void)dispatchSync:(dispatch_block_t)block; + +@end + +/** + * Protocol to abstract platform specific ways to implement playback and + * recording. + * + * NOTE: All the members of protocol are called by native ADM from the same + * thread between calls to `initializeWithDelegate` and `terminate`. NOTE: + * Implementation is fully responsible for configuring application's + * AVAudioSession. An example implementation of RTCAudioDevice: + * https://github.com/mstyura/RTCAudioDevice + * TODO(yura.yaroshevich): Implement custom RTCAudioDevice for AppRTCMobile demo + * app. + */ +RTC_OBJC_EXPORT @protocol RTC_OBJC_TYPE +(RTCAudioDevice) + + /** + * Indicates current sample rate of audio recording. Changes to this + * property must be notified back to native ADM via + * `-[RTCAudioDeviceDelegate notifyAudioParametersChange]`. + */ + @property(readonly) double deviceInputSampleRate; + +/** + * Indicates current size of record buffer. Changes to this property + * must be notified back to native ADM via `-[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) NSTimeInterval inputIOBufferDuration; + +/** + * Indicates current number of recorded audio channels. Changes to this property + * must be notified back to native ADM via `-[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) NSInteger inputNumberOfChannels; + +/** + * Indicates current input latency + */ +@property(readonly) NSTimeInterval inputLatency; + +/** + * Indicates current sample rate of audio playback. Changes to this property + * must be notified back to native ADM via `-[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) double deviceOutputSampleRate; + +/** + * Indicates current size of playback buffer. Changes to this property + * must be notified back to native ADM via `-[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) NSTimeInterval outputIOBufferDuration; + +/** + * Indicates current number of playback audio channels. Changes to this property + * must be notified back to WebRTC via `[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) NSInteger outputNumberOfChannels; + +/** + * Indicates current output latency + */ +@property(readonly) NSTimeInterval outputLatency; + +/** + * Indicates if invocation of `initializeWithDelegate` required before usage of + * RTCAudioDevice. YES indicates that `initializeWithDelegate` was called + * earlier without subsequent call to `terminate`. NO indicates that either + * `initializeWithDelegate` not called or `terminate` called. + */ +@property(readonly) BOOL isInitialized; + +/** + * Initializes RTCAudioDevice with RTCAudioDeviceDelegate. + * Implementation must return YES if RTCAudioDevice initialized successfully and + * NO otherwise. + */ +- (BOOL)initializeWithDelegate: + (id)delegate; + +/** + * De-initializes RTCAudioDevice. Implementation should forget about `delegate` + * provided in `initializeWithDelegate`. + */ +- (BOOL)terminateDevice; + +/** + * Property to indicate if `initializePlayout` call required before invocation + * of `startPlayout`. YES indicates that `initializePlayout` was successfully + * invoked earlier or not necessary, NO indicates that `initializePlayout` + * invocation required. + */ +@property(readonly) BOOL isPlayoutInitialized; + +/** + * Prepares RTCAudioDevice to play audio. + * Called by native ADM before invocation of `startPlayout`. + * Implementation is expected to return YES in case of successful playout + * initialization and NO otherwise. + */ +- (BOOL)initializePlayout; + +/** + * Property to indicate if RTCAudioDevice should be playing according to + * earlier calls of `startPlayout` and `stopPlayout`. + */ +@property(readonly) BOOL isPlaying; + +/** + * Method is called when native ADM wants to play audio. + * Implementation is expected to return YES if playback start request + * successfully handled and NO otherwise. + */ +- (BOOL)startPlayout; + +/** + * Method is called when native ADM no longer needs to play audio. + * Implementation is expected to return YES if playback stop request + * successfully handled and NO otherwise. + */ +- (BOOL)stopPlayout; + +/** + * Property to indicate if `initializeRecording` call required before usage of + * `startRecording`. YES indicates that `initializeRecording` was successfully + * invoked earlier or not necessary, NO indicates that `initializeRecording` + * invocation required. + */ +@property(readonly) BOOL isRecordingInitialized; + +/** + * Prepares RTCAudioDevice to record audio. + * Called by native ADM before invocation of `startRecording`. + * Implementation may use this method to prepare resources required to record + * audio. Implementation is expected to return YES in case of successful record + * initialization and NO otherwise. + */ +- (BOOL)initializeRecording; + +/** + * Property to indicate if RTCAudioDevice should record audio according to + * earlier calls to `startRecording` and `stopRecording`. + */ +@property(readonly) BOOL isRecording; + +/** + * Method is called when native ADM wants to record audio. + * Implementation is expected to return YES if recording start request + * successfully handled and NO otherwise. + */ +- (BOOL)startRecording; + +/** + * Method is called when native ADM no longer needs to record audio. + * Implementation is expected to return YES if recording stop request + * successfully handled and NO otherwise. + */ +- (BOOL)stopRecording; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSession.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSession.h new file mode 100644 index 00000000..08ecabfa --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSession.h @@ -0,0 +1,289 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const kRTCAudioSessionErrorDomain; +/** Method that requires lock was called without lock. */ +extern NSInteger const kRTCAudioSessionErrorLockRequired; +/** Unknown configuration error occurred. */ +extern NSInteger const kRTCAudioSessionErrorConfiguration; + +@class RTC_OBJC_TYPE(RTCAudioSession); +@class RTC_OBJC_TYPE(RTCAudioSessionConfiguration); + +// Surfaces AVAudioSession events. WebRTC will listen directly for notifications +// from AVAudioSession and handle them before calling these delegate methods, +// at which point applications can perform additional processing if required. +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCAudioSessionDelegate) + + @optional +/** Called on a system notification thread when AVAudioSession starts an + * interruption event. + */ +- (void)audioSessionDidBeginInterruption: + (RTC_OBJC_TYPE(RTCAudioSession) *)session; + +/** Called on a system notification thread when AVAudioSession ends an + * interruption event. + */ +- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session + shouldResumeSession:(BOOL)shouldResumeSession; + +/** Called on a system notification thread when AVAudioSession changes the + * route. + */ +- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session + reason:(AVAudioSessionRouteChangeReason)reason + previousRoute: + (AVAudioSessionRouteDescription *)previousRoute; + +/** Called on a system notification thread when AVAudioSession media server + * terminates. + */ +- (void)audioSessionMediaServerTerminated: + (RTC_OBJC_TYPE(RTCAudioSession) *)session; + +/** Called on a system notification thread when AVAudioSession media server + * restarts. + */ +- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session; + +// TODO(tkchin): Maybe handle SilenceSecondaryAudioHintNotification. + +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)session + didChangeCanPlayOrRecord:(BOOL)canPlayOrRecord; + +/** Called on a WebRTC thread when the audio device is notified to begin + * playback or recording. + */ +- (void)audioSessionDidStartPlayOrRecord: + (RTC_OBJC_TYPE(RTCAudioSession) *)session; + +/** Called on a WebRTC thread when the audio device is notified to stop + * playback or recording. + */ +- (void)audioSessionDidStopPlayOrRecord: + (RTC_OBJC_TYPE(RTCAudioSession) *)session; + +/** Called when the AVAudioSession output volume value changes. */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + didChangeOutputVolume:(float)outputVolume; + +/** Called when the audio device detects a playout glitch. The argument is the + * number of glitches detected so far in the current audio playout session. + */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + didDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches; + +/** Called when the audio session is about to change the active state. + */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + willSetActive:(BOOL)active; + +/** Called after the audio session sucessfully changed the active state. + */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + didSetActive:(BOOL)active; + +/** Called after the audio session failed to change the active state. + */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + failedToSetActive:(BOOL)active + error:(NSError *)error; + +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + audioUnitStartFailedWithError:(NSError *)error; + +@end + +/** This is a protocol used to inform RTCAudioSession when the audio session + * activation state has changed outside of RTCAudioSession. The current known + * use case of this is when CallKit activates the audio session for the + * application + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCAudioSessionActivationDelegate) + + /** Called when the audio session is activated outside of the app by iOS. */ + - (void)audioSessionDidActivate : (AVAudioSession *)session; + +/** Called when the audio session is deactivated outside of the app by iOS. */ +- (void)audioSessionDidDeactivate:(AVAudioSession *)session; + +@end + +/** Proxy class for AVAudioSession that adds a locking mechanism similar to + * AVCaptureDevice. This is used to that interleaving configurations between + * WebRTC and the application layer are avoided. + * + * RTCAudioSession also coordinates activation so that the audio session is + * activated only once. See `setActive:error:`. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCAudioSession) : NSObject + +/** Convenience property to access the AVAudioSession singleton. Callers should + * not call setters on AVAudioSession directly, but other method invocations + * are fine. + */ +@property(nonatomic, readonly) AVAudioSession *session; + +/** Our best guess at whether the session is active based on results of calls to + * AVAudioSession. + */ +@property(nonatomic, readonly) BOOL isActive; + +/** If YES, WebRTC will not initialize the audio unit automatically when an + * audio track is ready for playout or recording. Instead, applications should + * call setIsAudioEnabled. If NO, WebRTC will initialize the audio unit + * as soon as an audio track is ready for playout or recording. + */ +@property(nonatomic, assign) BOOL useManualAudio; + +/** This property is only effective if useManualAudio is YES. + * Represents permission for WebRTC to initialize the VoIP audio unit. + * When set to NO, if the VoIP audio unit used by WebRTC is active, it will be + * stopped and uninitialized. This will stop incoming and outgoing audio. + * When set to YES, WebRTC will initialize and start the audio unit when it is + * needed (e.g. due to establishing an audio connection). + * This property was introduced to work around an issue where if an AVPlayer is + * playing audio while the VoIP audio unit is initialized, its audio would be + * either cut off completely or played at a reduced volume. By preventing + * the audio unit from being initialized until after the audio has completed, + * we are able to prevent the abrupt cutoff. + */ +@property(nonatomic, assign) BOOL isAudioEnabled; + +// Proxy properties. +@property(readonly) NSString *category; +@property(readonly) AVAudioSessionCategoryOptions categoryOptions; +@property(readonly) NSString *mode; +@property(readonly) BOOL secondaryAudioShouldBeSilencedHint; +@property(readonly) AVAudioSessionRouteDescription *currentRoute; +@property(readonly) NSInteger maximumInputNumberOfChannels; +@property(readonly) NSInteger maximumOutputNumberOfChannels; +@property(readonly) float inputGain; +@property(readonly) BOOL inputGainSettable; +@property(readonly) BOOL inputAvailable; +@property(readonly, nullable) + NSArray *inputDataSources; +@property(readonly, nullable) + AVAudioSessionDataSourceDescription *inputDataSource; +@property(readonly, nullable) + NSArray *outputDataSources; +@property(readonly, nullable) + AVAudioSessionDataSourceDescription *outputDataSource; +@property(readonly) double sampleRate; +@property(readonly) double preferredSampleRate; +@property(readonly) NSInteger inputNumberOfChannels; +@property(readonly) NSInteger outputNumberOfChannels; +@property(readonly) float outputVolume; +@property(readonly) NSTimeInterval inputLatency; +@property(readonly) NSTimeInterval outputLatency; +@property(readonly) NSTimeInterval IOBufferDuration; +@property(readonly) NSTimeInterval preferredIOBufferDuration; + +/** + When YES, calls to -setConfiguration:error: and -setConfiguration:active:error: + ignore errors in configuring the audio session's "preferred" attributes (e.g. + preferredInputNumberOfChannels). Typically, configurations to preferred + attributes are optimizations, and ignoring this type of configuration error + allows code flow to continue along the happy path when these optimization are + not available. The default value of this property is NO. + */ +@property(nonatomic) BOOL ignoresPreferredAttributeConfigurationErrors; + +/** Default constructor. */ ++ (instancetype)sharedInstance; +- (instancetype)init NS_UNAVAILABLE; + +/** Adds a delegate, which is held weakly. */ +- (void)addDelegate:(id)delegate; +/** Removes an added delegate. */ +- (void)removeDelegate:(id)delegate; + +/** Request exclusive access to the audio session for configuration. This call + * will block if the lock is held by another object. + */ +- (void)lockForConfiguration; +/** Relinquishes exclusive access to the audio session. */ +- (void)unlockForConfiguration; + +/** If `active`, activates the audio session if it isn't already active. + * Successful calls must be balanced with a setActive:NO when activation is no + * longer required. If not `active`, deactivates the audio session if one is + * active and this is the last balanced call. When deactivating, the + * AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation option is passed to + * AVAudioSession. + */ +- (BOOL)setActive:(BOOL)active error:(NSError **)outError; + +// The following methods are proxies for the associated methods on +// AVAudioSession. `lockForConfiguration` must be called before using them +// otherwise they will fail with kRTCAudioSessionErrorLockRequired. + +- (BOOL)setCategory:(AVAudioSessionCategory)category + mode:(AVAudioSessionMode)mode + options:(AVAudioSessionCategoryOptions)options + error:(NSError **)outError; +- (BOOL)setCategory:(AVAudioSessionCategory)category + withOptions:(AVAudioSessionCategoryOptions)options + error:(NSError **)outError; +- (BOOL)setMode:(AVAudioSessionMode)mode error:(NSError **)outError; +- (BOOL)setInputGain:(float)gain error:(NSError **)outError; +- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError; +- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration + error:(NSError **)outError; +- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count + error:(NSError **)outError; +- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count + error:(NSError **)outError; +- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride + error:(NSError **)outError; +- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort + error:(NSError **)outError; +- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource + error:(NSError **)outError; +- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource + error:(NSError **)outError; +@end + +@interface RTC_OBJC_TYPE (RTCAudioSession) +(Configuration) + + /** Applies the configuration to the current session. Attempts to set all + * properties even if previous ones fail. Only the last error will be + * returned. + * `lockForConfiguration` must be called first. + */ + - (BOOL)setConfiguration + : (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration error + : (NSError **)outError; + +/** Convenience method that calls both setConfiguration and setActive. + * `lockForConfiguration` must be called first. + */ +- (BOOL)setConfiguration: + (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration + active:(BOOL)active + error:(NSError **)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSessionConfiguration.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSessionConfiguration.h new file mode 100644 index 00000000..b937f160 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSessionConfiguration.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_EXTERN const int kRTCAudioSessionPreferredNumberOfChannels; +RTC_EXTERN const double kRTCAudioSessionHighPerformanceSampleRate; +RTC_EXTERN const double kRTCAudioSessionHighPerformanceIOBufferDuration; + +// Struct to hold configuration values. +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCAudioSessionConfiguration) : NSObject + +@property(nonatomic, strong) NSString *category; +@property(nonatomic, assign) AVAudioSessionCategoryOptions categoryOptions; +@property(nonatomic, strong) NSString *mode; +@property(nonatomic, assign) double sampleRate; +@property(nonatomic, assign) NSTimeInterval ioBufferDuration; +@property(nonatomic, assign) NSInteger inputNumberOfChannels; +@property(nonatomic, assign) NSInteger outputNumberOfChannels; + +/** Initializes configuration to defaults. */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** Returns the current configuration of the audio session. */ ++ (instancetype)currentConfiguration; +/** Returns the configuration that WebRTC needs. */ ++ (instancetype)webRTCConfiguration; +/** Provide a way to override the default configuration. */ ++ (void)setWebRTCConfiguration: + (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSource.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSource.h new file mode 100644 index 00000000..784864a7 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioSource.h @@ -0,0 +1,32 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCAudioSource) : RTC_OBJC_TYPE(RTCMediaSource) + +- (instancetype)init NS_UNAVAILABLE; + +// Sets the volume for the RTCMediaSource. `volume` is a gain value in the range +// [0, 10]. +// Temporary fix to be able to modify volume of remote audio tracks. +// TODO(kthelgason): Property stays here temporarily until a proper volume-api +// is available on the surface exposed by webrtc. +@property(nonatomic, assign) double volume; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioTrack.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioTrack.h new file mode 100644 index 00000000..3c6d1dcf --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCAudioTrack.h @@ -0,0 +1,28 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCAudioSource); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCAudioTrack) : RTC_OBJC_TYPE(RTCMediaStreamTrack) + +- (instancetype)init NS_UNAVAILABLE; + +/** The audio source for this audio track. */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCAudioSource) * source; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCVPixelBuffer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCVPixelBuffer.h new file mode 100644 index 00000000..dd112b4e --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCVPixelBuffer.h @@ -0,0 +1,52 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** RTCVideoFrameBuffer containing a CVPixelBufferRef */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCVPixelBuffer) : NSObject + +@property(nonatomic, readonly) CVPixelBufferRef pixelBuffer; +@property(nonatomic, readonly) int cropX; +@property(nonatomic, readonly) int cropY; +@property(nonatomic, readonly) int cropWidth; +@property(nonatomic, readonly) int cropHeight; + ++ (NSSet *)supportedPixelFormats; + +- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer; +- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer + adaptedWidth:(int)adaptedWidth + adaptedHeight:(int)adaptedHeight + cropWidth:(int)cropWidth + cropHeight:(int)cropHeight + cropX:(int)cropX + cropY:(int)cropY; + +- (BOOL)requiresCropping; +- (BOOL)requiresScalingToWidth:(int)width height:(int)height; +- (int)bufferSizeForCroppingAndScalingToWidth:(int)width height:(int)height; + +/** The minimum size of the `tmpBuffer` must be the number of bytes returned + * from the bufferSizeForCroppingAndScalingToWidth:height: method. If that size + * is 0, the `tmpBuffer` may be nil. + */ +- (BOOL)cropAndScaleTo:(CVPixelBufferRef)outputPixelBuffer + withTempBuffer:(nullable uint8_t *)tmpBuffer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCallbackLogger.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCallbackLogger.h new file mode 100644 index 00000000..7e2745b6 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCallbackLogger.h @@ -0,0 +1,41 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^RTCCallbackLoggerMessageHandler)(NSString *message); +typedef void (^RTCCallbackLoggerMessageAndSeverityHandler)( + NSString *message, RTCLoggingSeverity severity); + +// This class intercepts WebRTC logs and forwards them to a registered block. +// This class is not threadsafe. +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCallbackLogger) : NSObject + +// The severity level to capture. The default is kRTCLoggingSeverityInfo. +@property(nonatomic, assign) RTCLoggingSeverity severity; + +// The callback handler will be called on the same thread that does the +// logging, so if the logging callback can be slow it may be a good idea +// to implement dispatching to some other queue. +- (void)start:(nullable RTCCallbackLoggerMessageHandler)handler; +- (void)startWithMessageAndSeverityHandler: + (nullable RTCCallbackLoggerMessageAndSeverityHandler)handler; + +- (void)stop; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCameraPreviewView.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCameraPreviewView.h new file mode 100644 index 00000000..710f2e79 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCameraPreviewView.h @@ -0,0 +1,30 @@ +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +@class AVCaptureSession; + +/** RTCCameraPreviewView is a view that renders local video from an + * AVCaptureSession. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCameraPreviewView) : UIView + +/** The capture session being rendered in the view. Capture session + * is assigned to AVCaptureVideoPreviewLayer async in the same + * queue that the AVCaptureSession is started/stopped. + */ +@property(nonatomic, strong) AVCaptureSession* captureSession; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCameraVideoCapturer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCameraVideoCapturer.h new file mode 100644 index 00000000..de69c5d2 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCameraVideoCapturer.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// Camera capture that implements RTCVideoCapturer. Delivers frames to a +// RTCVideoCapturerDelegate (usually RTCVideoSource). +NS_EXTENSION_UNAVAILABLE_IOS("Camera not available in app extensions.") +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCameraVideoCapturer) : RTC_OBJC_TYPE(RTCVideoCapturer) + +// Capture session that is used for capturing. Valid from initialization to dealloc. +@property(readonly, nonatomic) AVCaptureSession *captureSession; + +// Returns list of available capture devices that support video capture. ++ (NSArray *)captureDevices; +// Returns list of formats that are supported by this class for this device. ++ (NSArray *)supportedFormatsForDevice: + (AVCaptureDevice *)device; + +// Returns the most efficient supported output pixel format for this capturer. +- (FourCharCode)preferredOutputPixelFormat; + +// Starts the capture session asynchronously and notifies callback on +// completion. The device will capture video in the format given in the `format` +// parameter. If the pixel format in `format` is supported by the WebRTC +// pipeline, the same pixel format will be used for the output. Otherwise, the +// format returned by `preferredOutputPixelFormat` will be used. +- (void)startCaptureWithDevice:(AVCaptureDevice *)device + format:(AVCaptureDeviceFormat *)format + fps:(NSInteger)fps + completionHandler: + (nullable void (^)(NSError *_Nullable))completionHandler; +// Stops the capture session asynchronously and notifies callback on completion. +- (void)stopCaptureWithCompletionHandler: + (nullable void (^)(void))completionHandler; + +// Starts the capture session asynchronously. +- (void)startCaptureWithDevice:(AVCaptureDevice *)device + format:(AVCaptureDeviceFormat *)format + fps:(NSInteger)fps; +// Stops the capture session asynchronously. +- (void)stopCapture; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCertificate.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCertificate.h new file mode 100644 index 00000000..e300febb --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCertificate.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCertificate) : NSObject + +/** Private key in PEM. */ +@property(nonatomic, readonly, copy) NSString *private_key; + +/** Public key in an x509 cert encoded in PEM. */ +@property(nonatomic, readonly, copy) NSString *certificate; + +/** + * Initialize an RTCCertificate with PEM strings for private_key and + * certificate. + */ +- (instancetype)initWithPrivateKey:(NSString *)private_key + certificate:(NSString *)certificate + NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +/** Generate a new certificate for 're' use. + * + * Optional dictionary of parameters. Defaults to KeyType ECDSA if none are + * provided. + * - name: "ECDSA" or "RSASSA-PKCS1-v1_5" + */ ++ (nullable RTC_OBJC_TYPE(RTCCertificate) *)generateCertificateWithParams: + (NSDictionary *)params; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCodecSpecificInfo.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCodecSpecificInfo.h new file mode 100644 index 00000000..39f7c183 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCodecSpecificInfo.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Implement this protocol to pass codec specific info from the encoder. + * Corresponds to webrtc::CodecSpecificInfo. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCCodecSpecificInfo) @end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCodecSpecificInfoH264.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCodecSpecificInfoH264.h new file mode 100644 index 00000000..b6f34a54 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCodecSpecificInfoH264.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +/** Class for H264 specific config. */ +typedef NS_ENUM(NSUInteger, RTCH264PacketizationMode) { + RTCH264PacketizationModeNonInterleaved = + 0, // Mode 1 - STAP-A, FU-A is allowed + RTCH264PacketizationModeSingleNalUnit // Mode 0 - only single NALU allowed +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCodecSpecificInfoH264) : NSObject + +@property(nonatomic, assign) RTCH264PacketizationMode packetizationMode; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCConfiguration.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCConfiguration.h new file mode 100644 index 00000000..21f12fcd --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCConfiguration.h @@ -0,0 +1,268 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +@class RTC_OBJC_TYPE(RTCIceServer); + +/** + * Represents the ice transport policy. This exposes the same states in C++, + * which include one more state than what exists in the W3C spec. + */ +typedef NS_ENUM(NSInteger, RTCIceTransportPolicy) { + RTCIceTransportPolicyNone, + RTCIceTransportPolicyRelay, + RTCIceTransportPolicyNoHost, + RTCIceTransportPolicyAll +}; + +/** Represents the bundle policy. */ +typedef NS_ENUM(NSInteger, RTCBundlePolicy) { + RTCBundlePolicyBalanced, + RTCBundlePolicyMaxCompat, + RTCBundlePolicyMaxBundle +}; + +/** Represents the rtcp mux policy. */ +typedef NS_ENUM(NSInteger, RTCRtcpMuxPolicy) { + RTCRtcpMuxPolicyNegotiate, + RTCRtcpMuxPolicyRequire +}; + +/** Represents the tcp candidate policy. */ +typedef NS_ENUM(NSInteger, RTCTcpCandidatePolicy) { + RTCTcpCandidatePolicyEnabled, + RTCTcpCandidatePolicyDisabled +}; + +/** Represents the candidate network policy. */ +typedef NS_ENUM(NSInteger, RTCCandidateNetworkPolicy) { + RTCCandidateNetworkPolicyAll, + RTCCandidateNetworkPolicyLowCost +}; + +/** Represents the continual gathering policy. */ +typedef NS_ENUM(NSInteger, RTCContinualGatheringPolicy) { + RTCContinualGatheringPolicyGatherOnce, + RTCContinualGatheringPolicyGatherContinually +}; + +/** Represents the encryption key type. */ +typedef NS_ENUM(NSInteger, RTCEncryptionKeyType) { + RTCEncryptionKeyTypeRSA, + RTCEncryptionKeyTypeECDSA, +}; + +/** Represents the chosen SDP semantics for the RTCPeerConnection. */ +typedef NS_ENUM(NSInteger, RTCSdpSemantics) { + // TODO(https://crbug.com/webrtc/13528): Remove support for Plan B. + RTCSdpSemanticsPlanB, + RTCSdpSemanticsUnifiedPlan, +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCConfiguration) : NSObject + +/** If true, allows DSCP codes to be set on outgoing packets, configured using + * networkPriority field of RTCRtpEncodingParameters. Defaults to false. + */ +@property(nonatomic, assign) BOOL enableDscp; + +/** An array of Ice Servers available to be used by ICE. */ +@property(nonatomic, copy) NSArray *iceServers; + +/** An RTCCertificate for 're' use. */ +@property(nonatomic, nullable) RTC_OBJC_TYPE(RTCCertificate) * certificate; + +/** Which candidates the ICE agent is allowed to use. The W3C calls it + * `iceTransportPolicy`, while in C++ it is called `type`. */ +@property(nonatomic, assign) RTCIceTransportPolicy iceTransportPolicy; + +/** The media-bundling policy to use when gathering ICE candidates. */ +@property(nonatomic, assign) RTCBundlePolicy bundlePolicy; + +/** The rtcp-mux policy to use when gathering ICE candidates. */ +@property(nonatomic, assign) RTCRtcpMuxPolicy rtcpMuxPolicy; +@property(nonatomic, assign) RTCTcpCandidatePolicy tcpCandidatePolicy; +@property(nonatomic, assign) RTCCandidateNetworkPolicy candidateNetworkPolicy; +@property(nonatomic, assign) + RTCContinualGatheringPolicy continualGatheringPolicy; + +/** If set to YES, don't gather IPv6 ICE candidates on Wi-Fi. + * Only intended to be used on specific devices. Certain phones disable IPv6 + * when the screen is turned off and it would be better to just disable the + * IPv6 ICE candidates on Wi-Fi in those cases. + * Default is NO. + */ +@property(nonatomic, assign) BOOL disableIPV6OnWiFi; + +/** By default, the PeerConnection will use a limited number of IPv6 network + * interfaces, in order to avoid too many ICE candidate pairs being created + * and delaying ICE completion. + * + * Can be set to INT_MAX to effectively disable the limit. + */ +@property(nonatomic, assign) int maxIPv6Networks; + +/** Exclude link-local network interfaces + * from considertaion for gathering ICE candidates. + * Defaults to NO. + */ +@property(nonatomic, assign) BOOL disableLinkLocalNetworks; + +@property(nonatomic, assign) int audioJitterBufferMaxPackets; +@property(nonatomic, assign) BOOL audioJitterBufferFastAccelerate; +@property(nonatomic, assign) int iceConnectionReceivingTimeout; +@property(nonatomic, assign) int iceBackupCandidatePairPingInterval; + +/** Key type used to generate SSL identity. Default is ECDSA. */ +@property(nonatomic, assign) RTCEncryptionKeyType keyType; + +/** ICE candidate pool size as defined in JSEP. Default is 0. */ +@property(nonatomic, assign) int iceCandidatePoolSize; + +/** Prune turn ports on the same network to the same turn server. + * Default is NO. + */ +@property(nonatomic, assign) BOOL shouldPruneTurnPorts; + +/** If set to YES, this means the ICE transport should presume TURN-to-TURN + * candidate pairs will succeed, even before a binding response is received. + */ +@property(nonatomic, assign) BOOL shouldPresumeWritableWhenFullyRelayed; + +/* This flag is only effective when `continualGatheringPolicy` is + * RTCContinualGatheringPolicyGatherContinually. + * + * If YES, after the ICE transport type is changed such that new types of + * ICE candidates are allowed by the new transport type, e.g. from + * RTCIceTransportPolicyRelay to RTCIceTransportPolicyAll, candidates that + * have been gathered by the ICE transport but not matching the previous + * transport type and as a result not observed by PeerConnectionDelegateAdapter, + * will be surfaced to the delegate. + */ +@property(nonatomic, assign) + BOOL shouldSurfaceIceCandidatesOnIceTransportTypeChanged; + +/** If set to non-nil, controls the minimal interval between consecutive ICE + * check packets. + */ +@property(nonatomic, copy, nullable) NSNumber *iceCheckMinInterval; + +/** + * Configure the SDP semantics used by this PeerConnection. By default, this + * is RTCSdpSemanticsUnifiedPlan which is compliant to the WebRTC 1.0 + * specification. It is possible to overrwite this to the deprecated + * RTCSdpSemanticsPlanB SDP format, but note that RTCSdpSemanticsPlanB will be + * deleted at some future date, see https://crbug.com/webrtc/13528. + * + * RTCSdpSemanticsUnifiedPlan will cause RTCPeerConnection to create offers and + * answers with multiple m= sections where each m= section maps to one + * RTCRtpSender and one RTCRtpReceiver (an RTCRtpTransceiver), either both audio + * or both video. This will also cause RTCPeerConnection to ignore all but the + * first a=ssrc lines that form a Plan B stream. + * + * RTCSdpSemanticsPlanB will cause RTCPeerConnection to create offers and + * answers with at most one audio and one video m= section with multiple + * RTCRtpSenders and RTCRtpReceivers specified as multiple a=ssrc lines within + * the section. This will also cause RTCPeerConnection to ignore all but the + * first m= section of the same media type. + */ +@property(nonatomic, assign) RTCSdpSemantics sdpSemantics; + +/** Actively reset the SRTP parameters when the DTLS transports underneath are + * changed after offer/answer negotiation. This is only intended to be a + * workaround for crbug.com/835958 + */ +@property(nonatomic, assign) BOOL activeResetSrtpParams; + +/** + * Defines advanced optional cryptographic settings related to SRTP and + * frame encryption for native WebRTC. Setting this will overwrite any + * options set through the PeerConnectionFactory (which is deprecated). + */ +@property(nonatomic, nullable) RTC_OBJC_TYPE(RTCCryptoOptions) * cryptoOptions; + +/** + * An optional string that will be attached to the TURN_ALLOCATE_REQUEST which + * which can be used to correlate client logs with backend logs. + */ +@property(nonatomic, nullable, copy) NSString *turnLoggingId; + +/** + * Time interval between audio RTCP reports. + */ +@property(nonatomic, assign) int rtcpAudioReportIntervalMs; + +/** + * Time interval between video RTCP reports. + */ +@property(nonatomic, assign) int rtcpVideoReportIntervalMs; + +/** + * Allow implicit rollback of local description when remote description + * conflicts with local description. + * See: https://w3c.github.io/webrtc-pc/#dom-peerconnection-setremotedescription + */ +@property(nonatomic, assign) BOOL enableImplicitRollback; + +/** + * Control if "a=extmap-allow-mixed" is included in the offer. + * See: https://www.chromestatus.com/feature/6269234631933952 + */ +@property(nonatomic, assign) BOOL offerExtmapAllowMixed; + +/** + * Defines the interval applied to ALL candidate pairs + * when ICE is strongly connected, and it overrides the + * default value of this interval in the ICE implementation; + */ +@property(nonatomic, copy, nullable) + NSNumber *iceCheckIntervalStrongConnectivity; + +/** + * Defines the counterpart for ALL pairs when ICE is + * weakly connected, and it overrides the default value of + * this interval in the ICE implementation + */ +@property(nonatomic, copy, nullable) NSNumber *iceCheckIntervalWeakConnectivity; + +/** + * The min time period for which a candidate pair must wait for response to + * connectivity checks before it becomes unwritable. This parameter + * overrides the default value in the ICE implementation if set. + */ +@property(nonatomic, copy, nullable) NSNumber *iceUnwritableTimeout; + +/** + * The min number of connectivity checks that a candidate pair must sent + * without receiving response before it becomes unwritable. This parameter + * overrides the default value in the ICE implementation if set. + */ +@property(nonatomic, copy, nullable) NSNumber *iceUnwritableMinChecks; + +/** + * The min time period for which a candidate pair must wait for response to + * connectivity checks it becomes inactive. This parameter overrides the + * default value in the ICE implementation if set. + */ +@property(nonatomic, copy, nullable) NSNumber *iceInactiveTimeout; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCryptoOptions.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCryptoOptions.h new file mode 100644 index 00000000..a4c85d78 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCCryptoOptions.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Objective-C bindings for webrtc::CryptoOptions. This API had to be flattened + * as Objective-C doesn't support nested structures. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCryptoOptions) : NSObject + +/** + * Enable GCM crypto suites from RFC 7714 for SRTP. GCM will only be used + * if both sides enable it + */ +@property(nonatomic, assign) BOOL srtpEnableGcmCryptoSuites; +/** + * If set to true, the (potentially insecure) crypto cipher + * kSrtpAes128CmSha1_32 will be included in the list of supported ciphers + * during negotiation. It will only be used if both peers support it and no + * other ciphers get preferred. + */ +@property(nonatomic, assign) BOOL srtpEnableAes128Sha1_32CryptoCipher; +/** + * If set to true, encrypted RTP header extensions as defined in RFC 6904 + * will be negotiated. They will only be used if both peers support them. + */ +@property(nonatomic, assign) BOOL srtpEnableEncryptedRtpHeaderExtensions; + +/** + * If set all RtpSenders must have an FrameEncryptor attached to them before + * they are allowed to send packets. All RtpReceivers must have a + * FrameDecryptor attached to them before they are able to receive packets. + */ +@property(nonatomic, assign) BOOL sframeRequireFrameEncryption; + +/** + * Initializes CryptoOptions with all possible options set explicitly. This + * is done when converting from a native RTCConfiguration.crypto_options. + */ +- (instancetype) + initWithSrtpEnableGcmCryptoSuites:(BOOL)srtpEnableGcmCryptoSuites + srtpEnableAes128Sha1_32CryptoCipher: + (BOOL)srtpEnableAes128Sha1_32CryptoCipher + srtpEnableEncryptedRtpHeaderExtensions: + (BOOL)srtpEnableEncryptedRtpHeaderExtensions + sframeRequireFrameEncryption:(BOOL)sframeRequireFrameEncryption + NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDataChannel.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDataChannel.h new file mode 100644 index 00000000..c5c2c9a1 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDataChannel.h @@ -0,0 +1,134 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDataBuffer) : NSObject + +/** NSData representation of the underlying buffer. */ +@property(nonatomic, readonly) NSData *data; + +/** Indicates whether `data` contains UTF-8 or binary data. */ +@property(nonatomic, readonly) BOOL isBinary; + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initialize an RTCDataBuffer from NSData. `isBinary` indicates whether `data` + * contains UTF-8 or binary data. + */ +- (instancetype)initWithData:(NSData *)data isBinary:(BOOL)isBinary; + +@end + +@class RTC_OBJC_TYPE(RTCDataChannel); +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCDataChannelDelegate) + + /** The data channel state changed. */ + - (void)dataChannelDidChangeState + : (RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel; + +/** The data channel successfully received a data buffer. */ +- (void)dataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel + didReceiveMessageWithBuffer:(RTC_OBJC_TYPE(RTCDataBuffer) *)buffer; + +@optional +/** The data channel's `bufferedAmount` changed. */ +- (void)dataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel + didChangeBufferedAmount:(uint64_t)amount; + +@end + +/** Represents the state of the data channel. */ +typedef NS_ENUM(NSInteger, RTCDataChannelState) { + RTCDataChannelStateConnecting, + RTCDataChannelStateOpen, + RTCDataChannelStateClosing, + RTCDataChannelStateClosed, +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDataChannel) : NSObject + +/** + * A label that can be used to distinguish this data channel from other data + * channel objects. + */ +@property(nonatomic, readonly) NSString *label; + +/** Whether the data channel can send messages in unreliable mode. */ +@property(nonatomic, readonly) BOOL isReliable DEPRECATED_ATTRIBUTE; + +/** Returns whether this data channel is ordered or not. */ +@property(nonatomic, readonly) BOOL isOrdered; + +/** Deprecated. Use maxPacketLifeTime. */ +@property(nonatomic, readonly) + NSUInteger maxRetransmitTime DEPRECATED_ATTRIBUTE; + +/** + * The length of the time window (in milliseconds) during which transmissions + * and retransmissions may occur in unreliable mode. + */ +@property(nonatomic, readonly) uint16_t maxPacketLifeTime; + +/** + * The maximum number of retransmissions that are attempted in unreliable mode. + */ +@property(nonatomic, readonly) uint16_t maxRetransmits; + +/** + * The name of the sub-protocol used with this data channel, if any. Otherwise + * this returns an empty string. + */ +@property(nonatomic, readonly) NSString *protocol; + +/** + * Returns whether this data channel was negotiated by the application or not. + */ +@property(nonatomic, readonly) BOOL isNegotiated; + +/** Deprecated. Use channelId. */ +@property(nonatomic, readonly) NSInteger streamId DEPRECATED_ATTRIBUTE; + +/** The identifier for this data channel. */ +@property(nonatomic, readonly) int channelId; + +/** The state of the data channel. */ +@property(nonatomic, readonly) RTCDataChannelState readyState; + +/** + * The number of bytes of application data that have been queued using + * `sendData:` but that have not yet been transmitted to the network. + */ +@property(nonatomic, readonly) uint64_t bufferedAmount; + +/** The delegate for this data channel. */ +@property(nonatomic, weak) id delegate; + +- (instancetype)init NS_UNAVAILABLE; + +/** Closes the data channel. */ +- (void)close; + +/** Attempt to send `data` on this data channel's underlying data transport. */ +- (BOOL)sendData:(RTC_OBJC_TYPE(RTCDataBuffer) *)data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDataChannelConfiguration.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDataChannelConfiguration.h new file mode 100644 index 00000000..b1d8d770 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDataChannelConfiguration.h @@ -0,0 +1,52 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDataChannelConfiguration) : NSObject + +/** Set to YES if ordered delivery is required. */ +@property(nonatomic, assign) BOOL isOrdered; + +/** Deprecated. Use maxPacketLifeTime. */ +@property(nonatomic, assign) NSInteger maxRetransmitTimeMs DEPRECATED_ATTRIBUTE; + +/** + * Max period in milliseconds in which retransmissions will be sent. After this + * time, no more retransmissions will be sent. -1 if unset. + */ +@property(nonatomic, assign) int maxPacketLifeTime; + +/** The max number of retransmissions. -1 if unset. */ +@property(nonatomic, assign) int maxRetransmits; + +/** Set to YES if the channel has been externally negotiated and we do not send + * an in-band signalling in the form of an "open" message. + */ +@property(nonatomic, assign) BOOL isNegotiated; + +/** Deprecated. Use channelId. */ +@property(nonatomic, assign) int streamId DEPRECATED_ATTRIBUTE; + +/** The id of the data channel. */ +@property(nonatomic, assign) int channelId; + +/** Set by the application and opaque to the WebRTC implementation. */ +@property(nonatomic) NSString* protocol; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDefaultVideoDecoderFactory.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDefaultVideoDecoderFactory.h new file mode 100644 index 00000000..88b1d9c8 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDefaultVideoDecoderFactory.h @@ -0,0 +1,26 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This decoder factory include support for all codecs bundled with WebRTC. If + * using custom codecs, create custom implementations of RTCVideoEncoderFactory + * and RTCVideoDecoderFactory. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDefaultVideoDecoderFactory) : NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDefaultVideoEncoderFactory.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDefaultVideoEncoderFactory.h new file mode 100644 index 00000000..6defc80c --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDefaultVideoEncoderFactory.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This encoder factory include support for all codecs bundled with WebRTC. If + * using custom codecs, create custom implementations of RTCVideoEncoderFactory + * and RTCVideoDecoderFactory. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDefaultVideoEncoderFactory) : NSObject + +@property(nonatomic, retain) RTC_OBJC_TYPE(RTCVideoCodecInfo) *preferredCodec; + ++ (NSArray *)supportedCodecs; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDispatcher.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDispatcher.h new file mode 100644 index 00000000..bc44b478 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDispatcher.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSInteger, RTCDispatcherQueueType) { + // Main dispatcher queue. + RTCDispatcherTypeMain, + // Used for starting/stopping AVCaptureSession, and assigning + // capture session to AVCaptureVideoPreviewLayer. + RTCDispatcherTypeCaptureSession, + // Used for operations on AVAudioSession. + RTCDispatcherTypeAudioSession, + // Used for operations on NWPathMonitor. + RTCDispatcherTypeNetworkMonitor, +}; + +/** Dispatcher that asynchronously dispatches blocks to a specific + * shared dispatch queue. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDispatcher) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** Dispatch the block asynchronously on the queue for dispatchType. + * @param dispatchType The queue type to dispatch on. + * @param block The block to dispatch asynchronously. + */ ++ (void)dispatchAsyncOnType:(RTCDispatcherQueueType)dispatchType + block:(dispatch_block_t)block; + +/** Returns YES if run on queue for the dispatchType otherwise NO. + * Useful for asserting that a method is run on a correct queue. + */ ++ (BOOL)isOnQueueForType:(RTCDispatcherQueueType)dispatchType; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDtmfSender.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDtmfSender.h new file mode 100644 index 00000000..33d98c57 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCDtmfSender.h @@ -0,0 +1,73 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCDtmfSender) + + /** + * Returns true if this RTCDtmfSender is capable of sending DTMF. Otherwise + * returns false. To be able to send DTMF, the associated RTCRtpSender must + * be able to send packets, and a "telephone-event" codec must be + * negotiated. + */ + @property(nonatomic, readonly) BOOL canInsertDtmf; + +/** + * Queues a task that sends the DTMF tones. The tones parameter is treated + * as a series of characters. The characters 0 through 9, A through D, #, and * + * generate the associated DTMF tones. The characters a to d are equivalent + * to A to D. The character ',' indicates a delay of 2 seconds before + * processing the next character in the tones parameter. + * + * Unrecognized characters are ignored. + * + * @param duration The parameter indicates the duration to use for each + * character passed in the tones parameter. The duration cannot be more + * than 6000 or less than 70 ms. + * + * @param interToneGap The parameter indicates the gap between tones. + * This parameter must be at least 50 ms but should be as short as + * possible. + * + * If InsertDtmf is called on the same object while an existing task for this + * object to generate DTMF is still running, the previous task is canceled. + * Returns true on success and false on failure. + */ +- (BOOL)insertDtmf:(nonnull NSString *)tones + duration:(NSTimeInterval)duration + interToneGap:(NSTimeInterval)interToneGap; + +/** The tones remaining to be played out */ +- (nonnull NSString *)remainingTones; + +/** + * The current tone duration value. This value will be the value last set via + * the insertDtmf method, or the default value of 100 ms if insertDtmf was never + * called. + */ +- (NSTimeInterval)duration; + +/** + * The current value of the between-tone gap. This value will be the value last + * set via the insertDtmf() method, or the default value of 50 ms if + * insertDtmf() was never called. + */ +- (NSTimeInterval)interToneGap; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCEAGLVideoView.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCEAGLVideoView.h new file mode 100644 index 00000000..75cf9aed --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCEAGLVideoView.h @@ -0,0 +1,45 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCEAGLVideoView); + +/** + * RTCEAGLVideoView is an RTCVideoRenderer which renders video frames + * in its bounds using OpenGLES 2.0 or OpenGLES 3.0. + */ +NS_EXTENSION_UNAVAILABLE_IOS("Rendering not available in app extensions.") +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCEAGLVideoView) : UIView + +@property(nonatomic, weak) id delegate; + +- (instancetype)initWithFrame:(CGRect)frame + shader:(id)shader + NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithCoder:(NSCoder *)aDecoder + shader:(id)shader + NS_DESIGNATED_INITIALIZER; + +/** @abstract Wrapped RTCVideoRotation, or nil. + */ +@property(nonatomic, nullable) NSValue *rotationOverride; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCEncodedImage.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCEncodedImage.h new file mode 100644 index 00000000..97f29ed3 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCEncodedImage.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Represents an encoded frame's type. */ +typedef NS_ENUM(NSUInteger, RTCFrameType) { + RTCFrameTypeEmptyFrame = 0, + RTCFrameTypeAudioFrameSpeech = 1, + RTCFrameTypeAudioFrameCN = 2, + RTCFrameTypeVideoFrameKey = 3, + RTCFrameTypeVideoFrameDelta = 4, +}; + +typedef NS_ENUM(NSUInteger, RTCVideoContentType) { + RTCVideoContentTypeUnspecified, + RTCVideoContentTypeScreenshare, +}; + +/** Represents an encoded frame. Corresponds to webrtc::EncodedImage. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCEncodedImage) : NSObject + +@property(nonatomic, strong) NSData *buffer; +@property(nonatomic, assign) int32_t encodedWidth; +@property(nonatomic, assign) int32_t encodedHeight; +@property(nonatomic, assign) uint32_t timeStamp; +@property(nonatomic, assign) int64_t captureTimeMs; +@property(nonatomic, assign) int64_t ntpTimeMs; +@property(nonatomic, assign) uint8_t flags; +@property(nonatomic, assign) int64_t encodeStartMs; +@property(nonatomic, assign) int64_t encodeFinishMs; +@property(nonatomic, assign) RTCFrameType frameType; +@property(nonatomic, assign) RTCVideoRotation rotation; +@property(nonatomic, strong) NSNumber *qp; +@property(nonatomic, assign) RTCVideoContentType contentType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFieldTrials.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFieldTrials.h new file mode 100644 index 00000000..fa27322f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFieldTrials.h @@ -0,0 +1,34 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +/** The only valid value for the following if set is kRTCFieldTrialEnabledValue. + */ +RTC_EXTERN NSString *const kRTCFieldTrialAudioForceABWENoTWCCKey; +RTC_EXTERN NSString *const kRTCFieldTrialFlexFec03AdvertisedKey; +RTC_EXTERN NSString *const kRTCFieldTrialFlexFec03Key; +RTC_EXTERN NSString *const kRTCFieldTrialH264HighProfileKey; +RTC_EXTERN NSString *const kRTCFieldTrialMinimizeResamplingOnMobileKey; +RTC_EXTERN NSString *const kRTCFieldTrialUseNWPathMonitor; + +/** The valid value for field trials above. */ +RTC_EXTERN NSString *const kRTCFieldTrialEnabledValue; + +/** Initialize field trials using a dictionary mapping field trial keys to their + * values. See above for valid keys and values. Must be called before any other + * call into WebRTC. See: webrtc/system_wrappers/include/field_trial.h + */ +// TODO: bugs.webrtc.org/42220378 - Delete after January 1, 2026. +RTC_OBJC_DEPRECATED("Pass field trials when building PeerConnectionFactory") +RTC_EXTERN void RTCInitFieldTrialDictionary( + NSDictionary *fieldTrials); diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFileLogger.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFileLogger.h new file mode 100644 index 00000000..551a895f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFileLogger.h @@ -0,0 +1,76 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSUInteger, RTCFileLoggerSeverity) { + RTCFileLoggerSeverityVerbose, + RTCFileLoggerSeverityInfo, + RTCFileLoggerSeverityWarning, + RTCFileLoggerSeverityError +}; + +typedef NS_ENUM(NSUInteger, RTCFileLoggerRotationType) { + RTCFileLoggerTypeCall, + RTCFileLoggerTypeApp, +}; + +NS_ASSUME_NONNULL_BEGIN + +// This class intercepts WebRTC logs and saves them to a file. The file size +// will not exceed the given maximum bytesize. When the maximum bytesize is +// reached, logs are rotated according to the rotationType specified. +// For kRTCFileLoggerTypeCall, logs from the beginning and the end +// are preserved while the middle section is overwritten instead. +// For kRTCFileLoggerTypeApp, the oldest log is overwritten. +// This class is not threadsafe. +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCFileLogger) : NSObject + +// The severity level to capture. The default is kRTCFileLoggerSeverityInfo. +@property(nonatomic, assign) RTCFileLoggerSeverity severity; + +// The rotation type for this file logger. The default is +// kRTCFileLoggerTypeCall. +@property(nonatomic, readonly) RTCFileLoggerRotationType rotationType; + +// Disables buffering disk writes. Should be set before `start`. Buffering +// is enabled by default for performance. +@property(nonatomic, assign) BOOL shouldDisableBuffering; + +// Default constructor provides default settings for dir path, file size and +// rotation type. +- (instancetype)init; + +// Create file logger with default rotation type. +- (instancetype)initWithDirPath:(NSString *)dirPath + maxFileSize:(NSUInteger)maxFileSize; + +- (instancetype)initWithDirPath:(NSString *)dirPath + maxFileSize:(NSUInteger)maxFileSize + rotationType:(RTCFileLoggerRotationType)rotationType + NS_DESIGNATED_INITIALIZER; + +// Starts writing WebRTC logs to disk if not already started. Overwrites any +// existing file(s). +- (void)start; + +// Stops writing WebRTC logs to disk. This method is also called on dealloc. +- (void)stop; + +// Returns the current contents of the logs, or nil if start has been called +// without a stop. +- (nullable NSData *)logData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFileVideoCapturer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFileVideoCapturer.h new file mode 100644 index 00000000..38f65f81 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCFileVideoCapturer.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Error passing block. + */ +typedef void (^RTCFileVideoCapturerErrorBlock)(NSError *error); + +/** + * Captures buffers from bundled video file. + * + * See @c RTCVideoCapturer for more info on capturers. + */ +RTC_OBJC_EXPORT + +NS_CLASS_AVAILABLE_IOS(10) +@interface RTC_OBJC_TYPE (RTCFileVideoCapturer) : RTC_OBJC_TYPE(RTCVideoCapturer) + +/** + * Starts asynchronous capture of frames from video file. + * + * Capturing is not started if error occurs. Underlying error will be + * relayed in the errorBlock if one is provided. + * Successfully captured video frames will be passed to the delegate. + * + * @param nameOfFile The name of the bundled video file to be read. + * @errorBlock block to be executed upon error. + */ +- (void)startCapturingFromFileNamed:(NSString *)nameOfFile + onError:(__nullable RTCFileVideoCapturerErrorBlock)errorBlock; + +/** + * Immediately stops capture. + */ +- (void)stopCapture; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCH264ProfileLevelId.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCH264ProfileLevelId.h new file mode 100644 index 00000000..67bcae16 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCH264ProfileLevelId.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +RTC_EXTERN NSString *const kRTCVideoCodecH264Name; +RTC_EXTERN NSString *const kRTCLevel31ConstrainedHigh; +RTC_EXTERN NSString *const kRTCLevel31ConstrainedBaseline; +RTC_EXTERN NSString *const kRTCMaxSupportedH264ProfileLevelConstrainedHigh; +RTC_EXTERN NSString *const kRTCMaxSupportedH264ProfileLevelConstrainedBaseline; + +/** H264 Profiles and levels. */ +typedef NS_ENUM(NSUInteger, RTCH264Profile) { + RTCH264ProfileConstrainedBaseline, + RTCH264ProfileBaseline, + RTCH264ProfileMain, + RTCH264ProfileConstrainedHigh, + RTCH264ProfileHigh, +}; + +typedef NS_ENUM(NSUInteger, RTCH264Level) { + RTCH264Level1_b = 0, + RTCH264Level1 = 10, + RTCH264Level1_1 = 11, + RTCH264Level1_2 = 12, + RTCH264Level1_3 = 13, + RTCH264Level2 = 20, + RTCH264Level2_1 = 21, + RTCH264Level2_2 = 22, + RTCH264Level3 = 30, + RTCH264Level3_1 = 31, + RTCH264Level3_2 = 32, + RTCH264Level4 = 40, + RTCH264Level4_1 = 41, + RTCH264Level4_2 = 42, + RTCH264Level5 = 50, + RTCH264Level5_1 = 51, + RTCH264Level5_2 = 52 +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCH264ProfileLevelId) : NSObject + +@property(nonatomic, readonly) RTCH264Profile profile; +@property(nonatomic, readonly) RTCH264Level level; +@property(nonatomic, readonly) NSString *hexString; + +- (instancetype)initWithHexString:(NSString *)hexString; +- (instancetype)initWithProfile:(RTCH264Profile)profile + level:(RTCH264Level)level; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCI420Buffer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCI420Buffer.h new file mode 100644 index 00000000..54c32408 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCI420Buffer.h @@ -0,0 +1,22 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Protocol for RTCYUVPlanarBuffers containing I420 data */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCI420Buffer) @end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceCandidate.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceCandidate.h new file mode 100644 index 00000000..23b4fece --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceCandidate.h @@ -0,0 +1,50 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCIceCandidate) : NSObject + +/** + * If present, the identifier of the "media stream identification" for the media + * component this candidate is associated with. + */ +@property(nonatomic, readonly, nullable) NSString *sdpMid; + +/** + * The index (starting at zero) of the media description this candidate is + * associated with in the SDP. + */ +@property(nonatomic, readonly) int sdpMLineIndex; + +/** The SDP string for this candidate. */ +@property(nonatomic, readonly) NSString *sdp; + +/** The URL of the ICE server which this candidate is gathered from. */ +@property(nonatomic, readonly, nullable) NSString *serverUrl; + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initialize an RTCIceCandidate from SDP. + */ +- (instancetype)initWithSdp:(NSString *)sdp + sdpMLineIndex:(int)sdpMLineIndex + sdpMid:(nullable NSString *)sdpMid + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceCandidateErrorEvent.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceCandidateErrorEvent.h new file mode 100644 index 00000000..fb8e853f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceCandidateErrorEvent.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCIceCandidateErrorEvent) : NSObject + +/** The local IP address used to communicate with the STUN or TURN server. */ +@property(nonatomic, readonly) NSString *address; + +/** The port used to communicate with the STUN or TURN server. */ +@property(nonatomic, readonly) int port; + +/** The STUN or TURN URL that identifies the STUN or TURN server for which the + * failure occurred. */ +@property(nonatomic, readonly) NSString *url; + +/** The numeric STUN error code returned by the STUN or TURN server. If no host + * candidate can reach the server, errorCode will be set to the value 701 which + * is outside the STUN error code range. This error is only fired once per + * server URL while in the RTCIceGatheringState of "gathering". */ +@property(nonatomic, readonly) int errorCode; + +/** The STUN reason text returned by the STUN or TURN server. If the server + * could not be reached, errorText will be set to an implementation-specific + * value providing details about the error. */ +@property(nonatomic, readonly) NSString *errorText; + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceServer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceServer.h new file mode 100644 index 00000000..6f6c7eaa --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCIceServer.h @@ -0,0 +1,115 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSUInteger, RTCTlsCertPolicy) { + RTCTlsCertPolicySecure, + RTCTlsCertPolicyInsecureNoCheck +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCIceServer) : NSObject + +/** URI(s) for this server represented as NSStrings. */ +@property(nonatomic, readonly) NSArray *urlStrings; + +/** Username to use if this RTCIceServer object is a TURN server. */ +@property(nonatomic, readonly, nullable) NSString *username; + +/** Credential to use if this RTCIceServer object is a TURN server. */ +@property(nonatomic, readonly, nullable) NSString *credential; + +/** + * TLS certificate policy to use if this RTCIceServer object is a TURN server. + */ +@property(nonatomic, readonly) RTCTlsCertPolicy tlsCertPolicy; + +/** + If the URIs in `urls` only contain IP addresses, this field can be used + to indicate the hostname, which may be necessary for TLS (using the SNI + extension). If `urls` itself contains the hostname, this isn't necessary. + */ +@property(nonatomic, readonly, nullable) NSString *hostname; + +/** List of protocols to be used in the TLS ALPN extension. */ +@property(nonatomic, readonly) NSArray *tlsAlpnProtocols; + +/** + List elliptic curves to be used in the TLS elliptic curves extension. + Only curve names supported by OpenSSL should be used (eg. "P-256","X25519"). + */ +@property(nonatomic, readonly) NSArray *tlsEllipticCurves; + +- (nonnull instancetype)init NS_UNAVAILABLE; + +/** Convenience initializer for a server with no authentication (e.g. STUN). */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, and credentialType. + */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, and TLS cert policy. + */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential + tlsCertPolicy:(RTCTlsCertPolicy)tlsCertPolicy; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, TLS cert policy and hostname. + */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential + tlsCertPolicy:(RTCTlsCertPolicy)tlsCertPolicy + hostname:(nullable NSString *)hostname; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, TLS cert policy, hostname and ALPN protocols. + */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential + tlsCertPolicy:(RTCTlsCertPolicy)tlsCertPolicy + hostname:(nullable NSString *)hostname + tlsAlpnProtocols:(NSArray *)tlsAlpnProtocols; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, TLS cert policy, hostname, ALPN protocols and + * elliptic curves. + */ +- (instancetype) + initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential + tlsCertPolicy:(RTCTlsCertPolicy)tlsCertPolicy + hostname:(nullable NSString *)hostname + tlsAlpnProtocols:(nullable NSArray *)tlsAlpnProtocols + tlsEllipticCurves:(nullable NSArray *)tlsEllipticCurves + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCLegacyStatsReport.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCLegacyStatsReport.h new file mode 100644 index 00000000..c9ce8e38 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCLegacyStatsReport.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This does not currently conform to the spec. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCLegacyStatsReport) : NSObject + +/** Time since 1970-01-01T00:00:00Z in milliseconds. */ +@property(nonatomic, readonly) CFTimeInterval timestamp; + +/** The type of stats held by this object. */ +@property(nonatomic, readonly) NSString *type; + +/** The identifier for this object. */ +@property(nonatomic, readonly) NSString *reportId; + +/** A dictionary holding the actual stats. */ +@property(nonatomic, readonly) NSDictionary *values; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCLogging.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCLogging.h new file mode 100644 index 00000000..4cc29010 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCLogging.h @@ -0,0 +1,70 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +// Subset of webrtc::LoggingSeverity. +typedef NS_ENUM(NSInteger, RTCLoggingSeverity) { + RTCLoggingSeverityVerbose, + RTCLoggingSeverityInfo, + RTCLoggingSeverityWarning, + RTCLoggingSeverityError, + RTCLoggingSeverityNone, +}; + +// Wrapper for C++ RTC_LOG(sev) macros. +// Logs the log string to the webrtc logstream for the given severity. +RTC_EXTERN void RTCLogEx(RTCLoggingSeverity severity, NSString* log_string); + +// Wrapper for webrtc::LogMessage::LogToDebug. +// Sets the minimum severity to be logged to console. +RTC_EXTERN void RTCSetMinDebugLogLevel(RTCLoggingSeverity severity); + +// Returns the filename with the path prefix removed. +RTC_EXTERN NSString* RTCFileName(const char* filePath); + +// Some convenience macros. + +#define RTCLogString(format, ...) \ + [NSString stringWithFormat:@"(%@:%d %s): " format, \ + RTCFileName(__FILE__), \ + __LINE__, \ + __FUNCTION__, \ + ##__VA_ARGS__] + +#define RTCLogFormat(severity, format, ...) \ + do { \ + NSString* log_string = RTCLogString(format, ##__VA_ARGS__); \ + RTCLogEx(severity, log_string); \ + } while (false) + +#define RTCLogVerbose(format, ...) \ + RTCLogFormat(RTCLoggingSeverityVerbose, format, ##__VA_ARGS__) + +#define RTCLogInfo(format, ...) \ + RTCLogFormat(RTCLoggingSeverityInfo, format, ##__VA_ARGS__) + +#define RTCLogWarning(format, ...) \ + RTCLogFormat(RTCLoggingSeverityWarning, format, ##__VA_ARGS__) + +#define RTCLogError(format, ...) \ + RTCLogFormat(RTCLoggingSeverityError, format, ##__VA_ARGS__) + +#if !defined(NDEBUG) +#define RTCLogDebug(format, ...) RTCLogInfo(format, ##__VA_ARGS__) +#else +#define RTCLogDebug(format, ...) \ + do { \ + } while (false) +#endif + +#define RTCLog(format, ...) RTCLogInfo(format, ##__VA_ARGS__) diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMTLVideoView.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMTLVideoView.h new file mode 100644 index 00000000..f93ab591 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMTLVideoView.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * RTCMTLVideoView is thin wrapper around MTKView. + * + * It has id property that renders video frames in the view's + * bounds using Metal. + */ +NS_CLASS_AVAILABLE_IOS(9) + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMTLVideoView) : UIView + +@property(nonatomic, weak) id delegate; + +@property(nonatomic) UIViewContentMode videoContentMode; + +/** @abstract Enables/disables rendering. + */ +@property(nonatomic, getter=isEnabled) BOOL enabled; + +/** @abstract Wrapped RTCVideoRotation, or nil. + */ +@property(nonatomic, nullable) NSValue* rotationOverride; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMacros.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMacros.h new file mode 100644 index 00000000..cb943b4b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMacros.h @@ -0,0 +1,67 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SDK_OBJC_BASE_RTCMACROS_H_ +#define SDK_OBJC_BASE_RTCMACROS_H_ + +#ifdef WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT + +#if defined(WEBRTC_LIBRARY_IMPL) +#define RTC_OBJC_EXPORT __attribute__((visibility("default"))) +#endif + +#endif // WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT + +#ifndef RTC_OBJC_EXPORT +#define RTC_OBJC_EXPORT +#endif + +// Macro used to mark a function as deprecated. +#define RTC_OBJC_DEPRECATED(msg) __attribute__((deprecated(msg))) + +// Internal macros used to correctly concatenate symbols. +#define RTC_SYMBOL_CONCAT_HELPER(a, b) a##b +#define RTC_SYMBOL_CONCAT(a, b) RTC_SYMBOL_CONCAT_HELPER(a, b) + +// RTC_OBJC_TYPE_PREFIX +// +// Macro used to prepend a prefix to the API types that are exported with +// RTC_OBJC_EXPORT. +// +// Clients can patch the definition of this macro locally and build +// WebRTC.framework with their own prefix in case symbol clashing is a +// problem. +// +// This macro must be defined uniformily across all the translation units. +#ifndef RTC_OBJC_TYPE_PREFIX +#define RTC_OBJC_TYPE_PREFIX +#endif + +// RCT_OBJC_TYPE +// +// Macro used internally to declare API types. Declaring an API type without +// using this macro will not include the declared type in the set of types +// that will be affected by the configurable RTC_OBJC_TYPE_PREFIX. +#define RTC_OBJC_TYPE(type_name) \ + RTC_SYMBOL_CONCAT(RTC_OBJC_TYPE_PREFIX, type_name) + +#if defined(__cplusplus) +#define RTC_EXTERN extern "C" RTC_OBJC_EXPORT +#else +#define RTC_EXTERN extern RTC_OBJC_EXPORT +#endif + +#ifdef __OBJC__ +#define RTC_FWD_DECL_OBJC_CLASS(classname) @class classname +#else +#define RTC_FWD_DECL_OBJC_CLASS(classname) typedef struct objc_object classname +#endif + +#endif // SDK_OBJC_BASE_RTCMACROS_H_ diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaConstraints.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaConstraints.h new file mode 100644 index 00000000..8d002b97 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaConstraints.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Constraint keys for media sources. */ +/** The value for this key should be a base64 encoded string containing + * the data from the serialized configuration proto. + */ +RTC_EXTERN NSString *const kRTCMediaConstraintsAudioNetworkAdaptorConfig; + +/** Constraint keys for generating offers and answers. */ +RTC_EXTERN NSString *const kRTCMediaConstraintsIceRestart; +RTC_EXTERN NSString *const kRTCMediaConstraintsOfferToReceiveAudio; +RTC_EXTERN NSString *const kRTCMediaConstraintsOfferToReceiveVideo; +RTC_EXTERN NSString *const kRTCMediaConstraintsVoiceActivityDetection; + +/** Constraint values for Boolean parameters. */ +RTC_EXTERN NSString *const kRTCMediaConstraintsValueTrue; +RTC_EXTERN NSString *const kRTCMediaConstraintsValueFalse; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMediaConstraints) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** Initialize with mandatory and/or optional constraints. */ +- (instancetype)initWithMandatoryConstraints: + (nullable NSDictionary *)mandatory + optionalConstraints: + (nullable NSDictionary *) + optional NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaSource.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaSource.h new file mode 100644 index 00000000..51ceb605 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaSource.h @@ -0,0 +1,34 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSInteger, RTCSourceState) { + RTCSourceStateInitializing, + RTCSourceStateLive, + RTCSourceStateEnded, + RTCSourceStateMuted, +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMediaSource) : NSObject + +/** The current state of the RTCMediaSource. */ +@property(nonatomic, readonly) RTCSourceState state; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaStream.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaStream.h new file mode 100644 index 00000000..ce3eec5d --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaStream.h @@ -0,0 +1,50 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCAudioTrack); +@class RTC_OBJC_TYPE(RTCPeerConnectionFactory); +@class RTC_OBJC_TYPE(RTCVideoTrack); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMediaStream) : NSObject + +/** The audio tracks in this stream. */ +@property(nonatomic, strong, readonly) NSArray *audioTracks; + +/** The video tracks in this stream. */ +@property(nonatomic, strong, readonly) + NSArray *videoTracks; + +/** An identifier for this media stream. */ +@property(nonatomic, readonly) NSString *streamId; + +- (instancetype)init NS_UNAVAILABLE; + +/** Adds the given audio track to this media stream. */ +- (void)addAudioTrack:(RTC_OBJC_TYPE(RTCAudioTrack) *)audioTrack; + +/** Adds the given video track to this media stream. */ +- (void)addVideoTrack:(RTC_OBJC_TYPE(RTCVideoTrack) *)videoTrack; + +/** Removes the given audio track to this media stream. */ +- (void)removeAudioTrack:(RTC_OBJC_TYPE(RTCAudioTrack) *)audioTrack; + +/** Removes the given video track to this media stream. */ +- (void)removeVideoTrack:(RTC_OBJC_TYPE(RTCVideoTrack) *)videoTrack; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaStreamTrack.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaStreamTrack.h new file mode 100644 index 00000000..52658794 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMediaStreamTrack.h @@ -0,0 +1,50 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +/** + * Represents the state of the track. This exposes the same states in C++. + */ +typedef NS_ENUM(NSInteger, RTCMediaStreamTrackState) { + RTCMediaStreamTrackStateLive, + RTCMediaStreamTrackStateEnded +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_EXTERN NSString *const kRTCMediaStreamTrackKindAudio; +RTC_EXTERN NSString *const kRTCMediaStreamTrackKindVideo; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMediaStreamTrack) : NSObject + +/** + * The kind of track. For example, "audio" if this track represents an audio + * track and "video" if this track represents a video track. + */ +@property(nonatomic, readonly) NSString *kind; + +/** An identifier string. */ +@property(nonatomic, readonly) NSString *trackId; + +/** The enabled state of the track. */ +@property(nonatomic, assign) BOOL isEnabled; + +/** The state of the track. */ +@property(nonatomic, readonly) RTCMediaStreamTrackState readyState; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMetrics.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMetrics.h new file mode 100644 index 00000000..fffb451a --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMetrics.h @@ -0,0 +1,24 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +/** + * Enables gathering of metrics (which can be fetched with + * RTCGetAndResetMetrics). Must be called before any other call into WebRTC. + */ +RTC_EXTERN void RTCEnableMetrics(void); + +/** Gets and clears native histograms. */ +RTC_EXTERN NSArray* + RTCGetAndResetMetrics(void); diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMetricsSampleInfo.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMetricsSampleInfo.h new file mode 100644 index 00000000..18afdc0b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMetricsSampleInfo.h @@ -0,0 +1,48 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMetricsSampleInfo) : NSObject + +/** + * Example of RTCMetricsSampleInfo: + * name: "WebRTC.Video.InputFramesPerSecond" + * min: 1 + * max: 100 + * bucketCount: 50 + * samples: [29]:2 [30]:1 + */ + +/** The name of the histogram. */ +@property(nonatomic, readonly) NSString *name; + +/** The minimum bucket value. */ +@property(nonatomic, readonly) int min; + +/** The maximum bucket value. */ +@property(nonatomic, readonly) int max; + +/** The number of buckets. */ +@property(nonatomic, readonly) int bucketCount; + +/** A dictionary holding the samples . */ +@property(nonatomic, readonly) NSDictionary *samples; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMutableI420Buffer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMutableI420Buffer.h new file mode 100644 index 00000000..7685234f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMutableI420Buffer.h @@ -0,0 +1,24 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Extension of the I420 buffer with mutable data access */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCMutableI420Buffer) @end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMutableYUVPlanarBuffer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMutableYUVPlanarBuffer.h new file mode 100644 index 00000000..feb7417b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCMutableYUVPlanarBuffer.h @@ -0,0 +1,28 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Extension of the YUV planar data buffer with mutable data access */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCMutableYUVPlanarBuffer) + + @property(nonatomic, readonly) uint8_t *mutableDataY; +@property(nonatomic, readonly) uint8_t *mutableDataU; +@property(nonatomic, readonly) uint8_t *mutableDataV; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNativeI420Buffer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNativeI420Buffer.h new file mode 100644 index 00000000..c5a0ddf7 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNativeI420Buffer.h @@ -0,0 +1,23 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** RTCI420Buffer implements the RTCI420Buffer protocol */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCI420Buffer) : NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNativeMutableI420Buffer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNativeMutableI420Buffer.h new file mode 100644 index 00000000..b300731a --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNativeMutableI420Buffer.h @@ -0,0 +1,24 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Mutable version of RTCI420Buffer */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMutableI420Buffer) : RTC_OBJC_TYPE(RTCI420Buffer) +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNetworkMonitor.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNetworkMonitor.h new file mode 100644 index 00000000..21d22f54 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCNetworkMonitor.h @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Listens for NWPathMonitor updates and forwards the results to a C++ + * observer. + */ +@interface RTCNetworkMonitor : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnection.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnection.h new file mode 100644 index 00000000..7ddb5ba4 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnection.h @@ -0,0 +1,422 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +@class RTC_OBJC_TYPE(RTCConfiguration); +@class RTC_OBJC_TYPE(RTCDataChannel); +@class RTC_OBJC_TYPE(RTCDataChannelConfiguration); +@class RTC_OBJC_TYPE(RTCIceCandidate); +@class RTC_OBJC_TYPE(RTCIceCandidateErrorEvent); +@class RTC_OBJC_TYPE(RTCMediaConstraints); +@class RTC_OBJC_TYPE(RTCMediaStream); +@class RTC_OBJC_TYPE(RTCMediaStreamTrack); +@class RTC_OBJC_TYPE(RTCPeerConnectionFactory); +@class RTC_OBJC_TYPE(RTCRtpReceiver); +@class RTC_OBJC_TYPE(RTCRtpSender); +@class RTC_OBJC_TYPE(RTCRtpTransceiver); +@class RTC_OBJC_TYPE(RTCRtpTransceiverInit); +@class RTC_OBJC_TYPE(RTCSessionDescription); +@class RTC_OBJC_TYPE(RTCStatisticsReport); +@class RTC_OBJC_TYPE(RTCLegacyStatsReport); + +typedef NS_ENUM(NSInteger, RTCRtpMediaType); + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const kRTCPeerConnectionErrorDomain; +extern int const kRTCSessionDescriptionErrorCode; + +/** Represents the signaling state of the peer connection. */ +typedef NS_ENUM(NSInteger, RTCSignalingState) { + RTCSignalingStateStable, + RTCSignalingStateHaveLocalOffer, + RTCSignalingStateHaveLocalPrAnswer, + RTCSignalingStateHaveRemoteOffer, + RTCSignalingStateHaveRemotePrAnswer, + // Not an actual state, represents the total number of states. + RTCSignalingStateClosed, +}; + +/** Represents the ice connection state of the peer connection. */ +typedef NS_ENUM(NSInteger, RTCIceConnectionState) { + RTCIceConnectionStateNew, + RTCIceConnectionStateChecking, + RTCIceConnectionStateConnected, + RTCIceConnectionStateCompleted, + RTCIceConnectionStateFailed, + RTCIceConnectionStateDisconnected, + RTCIceConnectionStateClosed, + RTCIceConnectionStateCount, +}; + +/** Represents the combined ice+dtls connection state of the peer connection. */ +typedef NS_ENUM(NSInteger, RTCPeerConnectionState) { + RTCPeerConnectionStateNew, + RTCPeerConnectionStateConnecting, + RTCPeerConnectionStateConnected, + RTCPeerConnectionStateDisconnected, + RTCPeerConnectionStateFailed, + RTCPeerConnectionStateClosed, +}; + +/** Represents the ice gathering state of the peer connection. */ +typedef NS_ENUM(NSInteger, RTCIceGatheringState) { + RTCIceGatheringStateNew, + RTCIceGatheringStateGathering, + RTCIceGatheringStateComplete, +}; + +/** Represents the stats output level. */ +typedef NS_ENUM(NSInteger, RTCStatsOutputLevel) { + RTCStatsOutputLevelStandard, + RTCStatsOutputLevelDebug, +}; + +typedef void (^RTCCreateSessionDescriptionCompletionHandler)( + RTC_OBJC_TYPE(RTCSessionDescription) *_Nullable sdp, + NSError *_Nullable error); + +typedef void (^RTCSetSessionDescriptionCompletionHandler)( + NSError *_Nullable error); + +@class RTC_OBJC_TYPE(RTCPeerConnection); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCPeerConnectionDelegate) + + /** Called when the SignalingState changed. */ + - (void)peerConnection + : (RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection didChangeSignalingState + : (RTCSignalingState)stateChanged; + +/** Called when media is received on a new stream from remote peer. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didAddStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream; + +/** Called when a remote peer closes a stream. + * This is not called when RTCSdpSemanticsUnifiedPlan is specified. + */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didRemoveStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream; + +/** Called when negotiation is needed, for example ICE has restarted. */ +- (void)peerConnectionShouldNegotiate: + (RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection; + +/** Called any time the IceConnectionState changes. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeIceConnectionState:(RTCIceConnectionState)newState; + +/** Called any time the IceGatheringState changes. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeIceGatheringState:(RTCIceGatheringState)newState; + +/** New ice candidate has been found. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didGenerateIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate; + +/** Called when a group of local Ice candidates have been removed. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didRemoveIceCandidates: + (NSArray *)candidates; + +/** New data channel has been opened. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didOpenDataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel; + +/** Called when signaling indicates a transceiver will be receiving media from + * the remote endpoint. + * This is only called with RTCSdpSemanticsUnifiedPlan specified. + */ +@optional +/** Called any time the IceConnectionState changes following standardized + * transition. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeStandardizedIceConnectionState:(RTCIceConnectionState)newState; + +/** Called any time the PeerConnectionState changes. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeConnectionState:(RTCPeerConnectionState)newState; + +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didStartReceivingOnTransceiver: + (RTC_OBJC_TYPE(RTCRtpTransceiver) *)transceiver; + +/** Called when a receiver and its track are created. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didAddReceiver:(RTC_OBJC_TYPE(RTCRtpReceiver) *)rtpReceiver + streams:(NSArray *)mediaStreams; + +/** Called when the receiver and its track are removed. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didRemoveReceiver:(RTC_OBJC_TYPE(RTCRtpReceiver) *)rtpReceiver; + +/** Called when the selected ICE candidate pair is changed. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeLocalCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)local + remoteCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)remote + lastReceivedMs:(int)lastDataReceivedMs + changeReason:(NSString *)reason; + +/** Called when gathering of an ICE candidate failed. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didFailToGatherIceCandidate: + (RTC_OBJC_TYPE(RTCIceCandidateErrorEvent) *)event; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCPeerConnection) : NSObject + +/** The object that will be notifed about events such as state changes and + * streams being added or removed. + */ +@property(nonatomic, weak, nullable) id delegate; +/** This property is not available with RTCSdpSemanticsUnifiedPlan. Please use + * `senders` instead. + */ +@property(nonatomic, readonly) + NSArray *localStreams; +@property(nonatomic, readonly, nullable) RTC_OBJC_TYPE(RTCSessionDescription) * + localDescription; +@property(nonatomic, readonly, nullable) RTC_OBJC_TYPE(RTCSessionDescription) * + remoteDescription; +@property(nonatomic, readonly) RTCSignalingState signalingState; +@property(nonatomic, readonly) RTCIceConnectionState iceConnectionState; +@property(nonatomic, readonly) RTCPeerConnectionState connectionState; +@property(nonatomic, readonly) RTCIceGatheringState iceGatheringState; +@property(nonatomic, readonly, copy) RTC_OBJC_TYPE(RTCConfiguration) * + configuration; + +/** Gets all RTCRtpSenders associated with this peer connection. + * Note: reading this property returns different instances of RTCRtpSender. + * Use isEqual: instead of == to compare RTCRtpSender instances. + */ +@property(nonatomic, readonly) NSArray *senders; + +/** Gets all RTCRtpReceivers associated with this peer connection. + * Note: reading this property returns different instances of RTCRtpReceiver. + * Use isEqual: instead of == to compare RTCRtpReceiver instances. + */ +@property(nonatomic, readonly) + NSArray *receivers; + +/** Gets all RTCRtpTransceivers associated with this peer connection. + * Note: reading this property returns different instances of + * RTCRtpTransceiver. Use isEqual: instead of == to compare + * RTCRtpTransceiver instances. This is only available with + * RTCSdpSemanticsUnifiedPlan specified. + */ +@property(nonatomic, readonly) + NSArray *transceivers; + +- (instancetype)init NS_UNAVAILABLE; + +/** Sets the PeerConnection's global configuration to `configuration`. + * Any changes to STUN/TURN servers or ICE candidate policy will affect the + * next gathering phase, and cause the next call to createOffer to generate + * new ICE credentials. Note that the BUNDLE and RTCP-multiplexing policies + * cannot be changed with this method. + */ +- (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCConfiguration) *)configuration; + +/** Terminate all media and close the transport. */ +- (void)close; + +/** Provide a remote candidate to the ICE Agent. */ +- (void)addIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate + DEPRECATED_MSG_ATTRIBUTE( + "Please use addIceCandidate:completionHandler: instead"); + +/** Provide a remote candidate to the ICE Agent. */ +- (void)addIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate + completionHandler:(void (^)(NSError *_Nullable error))completionHandler; + +/** Remove a group of remote candidates from the ICE Agent. */ +- (void)removeIceCandidates: + (NSArray *)candidates; + +/** Add a new media stream to be sent on this peer connection. + * This method is not supported with RTCSdpSemanticsUnifiedPlan. Please use + * addTrack instead. + */ +- (void)addStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream; + +/** Remove the given media stream from this peer connection. + * This method is not supported with RTCSdpSemanticsUnifiedPlan. Please use + * removeTrack instead. + */ +- (void)removeStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream; + +/** Add a new media stream track to be sent on this peer connection, and return + * the newly created RTCRtpSender. The RTCRtpSender will be + * associated with the streams specified in the `streamIds` list. + * + * Errors: If an error occurs, returns nil. An error can occur if: + * - A sender already exists for the track. + * - The peer connection is closed. + */ +- (nullable RTC_OBJC_TYPE(RTCRtpSender) *) + addTrack:(RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track + streamIds:(NSArray *)streamIds; + +/** With PlanB semantics, removes an RTCRtpSender from this peer connection. + * + * With UnifiedPlan semantics, sets sender's track to null and removes the + * send component from the associated RTCRtpTransceiver's direction. + * + * Returns YES on success. + */ +- (BOOL)removeTrack:(RTC_OBJC_TYPE(RTCRtpSender) *)sender; + +/** addTransceiver creates a new RTCRtpTransceiver and adds it to the set of + * transceivers. Adding a transceiver will cause future calls to CreateOffer + * to add a media description for the corresponding transceiver. + * + * The initial value of `mid` in the returned transceiver is nil. Setting a + * new session description may change it to a non-nil value. + * + * https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver + * + * Optionally, an RtpTransceiverInit structure can be specified to configure + * the transceiver from construction. If not specified, the transceiver will + * default to having a direction of kSendRecv and not be part of any streams. + * + * These methods are only available when Unified Plan is enabled (see + * RTCConfiguration). + */ + +/** Adds a transceiver with a sender set to transmit the given track. The kind + * of the transceiver (and sender/receiver) will be derived from the kind of + * the track. + */ +- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *)addTransceiverWithTrack: + (RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track; +- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *) + addTransceiverWithTrack:(RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track + init:(RTC_OBJC_TYPE(RTCRtpTransceiverInit) *)init; + +/** Adds a transceiver with the given kind. Can either be RTCRtpMediaTypeAudio + * or RTCRtpMediaTypeVideo. + */ +- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *)addTransceiverOfType: + (RTCRtpMediaType)mediaType; +- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *) + addTransceiverOfType:(RTCRtpMediaType)mediaType + init:(RTC_OBJC_TYPE(RTCRtpTransceiverInit) *)init; + +/** Tells the PeerConnection that ICE should be restarted. This triggers a need + * for negotiation and subsequent offerForConstraints:completionHandler call + * will act as if RTCOfferAnswerOptions::ice_restart is true. + */ +- (void)restartIce; + +/** Generate an SDP offer. */ +- (void)offerForConstraints:(RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints + completionHandler: + (RTCCreateSessionDescriptionCompletionHandler)completionHandler; + +/** Generate an SDP answer. */ +- (void)answerForConstraints:(RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints + completionHandler: + (RTCCreateSessionDescriptionCompletionHandler)completionHandler; + +/** Apply the supplied RTCSessionDescription as the local description. */ +- (void)setLocalDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp + completionHandler: + (RTCSetSessionDescriptionCompletionHandler)completionHandler; + +/** Creates an offer or answer (depending on current signaling state) and sets + * it as the local session description. */ +- (void)setLocalDescriptionWithCompletionHandler: + (RTCSetSessionDescriptionCompletionHandler)completionHandler; + +/** Apply the supplied RTCSessionDescription as the remote description. */ +- (void)setRemoteDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp + completionHandler: + (RTCSetSessionDescriptionCompletionHandler)completionHandler; + +/** Limits the bandwidth allocated for all RTP streams sent by this + * PeerConnection. Nil parameters will be unchanged. Setting + * `currentBitrateBps` will force the available bitrate estimate to the given + * value. Returns YES if the parameters were successfully updated. + */ +- (BOOL)setBweMinBitrateBps:(nullable NSNumber *)minBitrateBps + currentBitrateBps:(nullable NSNumber *)currentBitrateBps + maxBitrateBps:(nullable NSNumber *)maxBitrateBps; + +/** Start or stop recording an Rtc EventLog. */ +- (BOOL)startRtcEventLogWithFilePath:(NSString *)filePath + maxSizeInBytes:(int64_t)maxSizeInBytes; +- (void)stopRtcEventLog; + +@end + +@interface RTC_OBJC_TYPE (RTCPeerConnection) +(Media) + + /** Create an RTCRtpSender with the specified kind and media stream ID. + * See RTCMediaStreamTrack.h for available kinds. + * This method is not supported with RTCSdpSemanticsUnifiedPlan. Please use + * addTransceiver instead. + */ + - (RTC_OBJC_TYPE(RTCRtpSender) *)senderWithKind : (NSString *)kind streamId + : (NSString *)streamId; + +@end + +@interface RTC_OBJC_TYPE (RTCPeerConnection) +(DataChannel) + + /** Create a new data channel with the given label and configuration. */ + - (nullable RTC_OBJC_TYPE(RTCDataChannel) *)dataChannelForLabel + : (NSString *)label configuration + : (RTC_OBJC_TYPE(RTCDataChannelConfiguration) *)configuration; + +@end + +typedef void (^RTCStatisticsCompletionHandler)( + RTC_OBJC_TYPE(RTCStatisticsReport) *); + +@interface RTC_OBJC_TYPE (RTCPeerConnection) +(Stats) + + /** Gather stats for the given RTCMediaStreamTrack. If `mediaStreamTrack` is + * nil statistics are gathered for all tracks. + */ + - (void)statsForTrack : (nullable RTC_OBJC_TYPE(RTCMediaStreamTrack) *) + mediaStreamTrack statsOutputLevel + : (RTCStatsOutputLevel)statsOutputLevel completionHandler + : (nullable void (^)(NSArray *stats)) + completionHandler; + +/** Gather statistic through the v2 statistics API. */ +- (void)statisticsWithCompletionHandler: + (RTCStatisticsCompletionHandler)completionHandler; + +/** Spec-compliant getStats() performing the stats selection algorithm with the + * sender. + */ +- (void)statisticsForSender:(RTC_OBJC_TYPE(RTCRtpSender) *)sender + completionHandler:(RTCStatisticsCompletionHandler)completionHandler; + +/** Spec-compliant getStats() performing the stats selection algorithm with the + * receiver. + */ +- (void)statisticsForReceiver:(RTC_OBJC_TYPE(RTCRtpReceiver) *)receiver + completionHandler:(RTCStatisticsCompletionHandler)completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnectionFactory.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnectionFactory.h new file mode 100644 index 00000000..7396beab --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnectionFactory.h @@ -0,0 +1,147 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCRtpCapabilities); +@class RTC_OBJC_TYPE(RTCAudioSource); +@class RTC_OBJC_TYPE(RTCAudioTrack); +@class RTC_OBJC_TYPE(RTCConfiguration); +@class RTC_OBJC_TYPE(RTCMediaConstraints); +@class RTC_OBJC_TYPE(RTCMediaStream); +@class RTC_OBJC_TYPE(RTCPeerConnection); +@class RTC_OBJC_TYPE(RTCVideoSource); +@class RTC_OBJC_TYPE(RTCVideoTrack); +@class RTC_OBJC_TYPE(RTCPeerConnectionFactoryOptions); +@protocol RTC_OBJC_TYPE +(RTCPeerConnectionDelegate); +@protocol RTC_OBJC_TYPE +(RTCVideoDecoderFactory); +@protocol RTC_OBJC_TYPE +(RTCVideoEncoderFactory); +@protocol RTC_OBJC_TYPE +(RTCSSLCertificateVerifier); +@protocol RTC_OBJC_TYPE +(RTCAudioDevice); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCPeerConnectionFactory) : NSObject + +/* Initialize object with default H264 video encoder/decoder factories and default ADM */ +- (instancetype)init; + +/* Initialize object with injectable video encoder/decoder factories and default + * ADM */ +- (instancetype) + initWithEncoderFactory: + (nullable id)encoderFactory + decoderFactory:(nullable id) + decoderFactory; + +/* Initialize object with injectable video encoder/decoder factories and + * injectable ADM */ +- (instancetype) + initWithEncoderFactory: + (nullable id)encoderFactory + decoderFactory:(nullable id) + decoderFactory + audioDevice: + (nullable id)audioDevice; + +/** + * Valid kind values are kRTCMediaStreamTrackKindAudio and + * kRTCMediaStreamTrackKindVideo. + */ +- (RTC_OBJC_TYPE(RTCRtpCapabilities) *)rtpSenderCapabilitiesForKind: + (NSString *)kind; + +/** + * Valid kind values are kRTCMediaStreamTrackKindAudio and + * kRTCMediaStreamTrackKindVideo. + */ +- (RTC_OBJC_TYPE(RTCRtpCapabilities) *)rtpReceiverCapabilitiesForKind: + (NSString *)kind; + +/** Initialize an RTCAudioSource with constraints. */ +- (RTC_OBJC_TYPE(RTCAudioSource) *)audioSourceWithConstraints: + (nullable RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints; + +/** Initialize an RTCAudioTrack with an id. Convenience ctor to use an audio + * source with no constraints. + */ +- (RTC_OBJC_TYPE(RTCAudioTrack) *)audioTrackWithTrackId:(NSString *)trackId; + +/** Initialize an RTCAudioTrack with a source and an id. */ +- (RTC_OBJC_TYPE(RTCAudioTrack) *)audioTrackWithSource: + (RTC_OBJC_TYPE(RTCAudioSource) *)source + trackId:(NSString *)trackId; + +/** Initialize a generic RTCVideoSource. The RTCVideoSource should be + * passed to a RTCVideoCapturer implementation, e.g. + * RTCCameraVideoCapturer, in order to produce frames. + */ +- (RTC_OBJC_TYPE(RTCVideoSource) *)videoSource; + +/** Initialize a generic RTCVideoSource with he posibility of marking + * it as usable for screen sharing. The RTCVideoSource should be + * passed to a RTCVideoCapturer implementation, e.g. + * RTCCameraVideoCapturer, in order to produce frames. + */ +- (RTC_OBJC_TYPE(RTCVideoSource) *)videoSourceForScreenCast:(BOOL)forScreenCast; + +/** Initialize an RTCVideoTrack with a source and an id. */ +- (RTC_OBJC_TYPE(RTCVideoTrack) *)videoTrackWithSource: + (RTC_OBJC_TYPE(RTCVideoSource) *)source + trackId:(NSString *)trackId; + +/** Initialize an RTCMediaStream with an id. */ +- (RTC_OBJC_TYPE(RTCMediaStream) *)mediaStreamWithStreamId:(NSString *)streamId; + +/** Initialize an RTCPeerConnection with a configuration, constraints, and + * delegate. + */ +- (nullable RTC_OBJC_TYPE(RTCPeerConnection) *) + peerConnectionWithConfiguration: + (RTC_OBJC_TYPE(RTCConfiguration) *)configuration + constraints: + (RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints + delegate:(nullable id)delegate; + +- (nullable RTC_OBJC_TYPE(RTCPeerConnection) *) + peerConnectionWithConfiguration: + (RTC_OBJC_TYPE(RTCConfiguration) *)configuration + constraints: + (RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints + certificateVerifier: + (id) + certificateVerifier + delegate:(nullable id)delegate; + +/** Set the options to be used for subsequently created RTCPeerConnections */ +- (void)setOptions: + (nonnull RTC_OBJC_TYPE(RTCPeerConnectionFactoryOptions) *)options; + +/** Start an AecDump recording. This API call will likely change in the future. + */ +- (BOOL)startAecDumpWithFilePath:(NSString *)filePath + maxSizeInBytes:(int64_t)maxSizeInBytes; + +/* Stop an active AecDump recording */ +- (void)stopAecDump; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnectionFactoryOptions.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnectionFactoryOptions.h new file mode 100644 index 00000000..1c7a10d1 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCPeerConnectionFactoryOptions.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCPeerConnectionFactoryOptions) : NSObject + +@property(nonatomic, assign) BOOL disableEncryption; + +@property(nonatomic, assign) BOOL disableNetworkMonitor; + +@property(nonatomic, assign) BOOL ignoreLoopbackNetworkAdapter; + +@property(nonatomic, assign) BOOL ignoreVPNNetworkAdapter; + +@property(nonatomic, assign) BOOL ignoreCellularNetworkAdapter; + +@property(nonatomic, assign) BOOL ignoreWiFiNetworkAdapter; + +@property(nonatomic, assign) BOOL ignoreEthernetNetworkAdapter; + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtcpParameters.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtcpParameters.h new file mode 100644 index 00000000..8449500f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtcpParameters.h @@ -0,0 +1,30 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtcpParameters) : NSObject + +/** The Canonical Name used by RTCP. */ +@property(nonatomic, readonly, copy) NSString *cname; + +/** Whether reduced size RTCP is configured or compound RTCP. */ +@property(nonatomic, assign) BOOL isReducedSize; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCapabilities.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCapabilities.h new file mode 100644 index 00000000..c44d0b86 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCapabilities.h @@ -0,0 +1,31 @@ +/* + * Copyright 2024 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCRtpCodecCapability); +@class RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpCapabilities) : NSObject + +@property(nonatomic, copy) NSArray *codecs; +@property(nonatomic, copy) + NSArray *headerExtensions; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCodecCapability.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCodecCapability.h new file mode 100644 index 00000000..ee953e13 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCodecCapability.h @@ -0,0 +1,58 @@ +/* + * Copyright 2024 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpCodecCapability) : NSObject + +/** The preferred RTP payload type. */ +@property(nonatomic, readonly, nullable) NSNumber *preferredPayloadType; + +/** + * The codec MIME subtype. Valid types are listed in: + * http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-2 + * + * Several supported types are represented by the constants above. + */ +@property(nonatomic, readonly) NSString *name; + +/** + * The media type of this codec. Equivalent to MIME top-level type. + * + * Valid values are kRTCMediaStreamTrackKindAudio and + * kRTCMediaStreamTrackKindVideo. + */ +@property(nonatomic, readonly) NSString *kind; + +/** The codec clock rate expressed in Hertz. */ +@property(nonatomic, readonly, nullable) NSNumber *clockRate; + +/** + * The number of audio channels (mono=1, stereo=2). + * Set to null for video codecs. + **/ +@property(nonatomic, readonly, nullable) NSNumber *numChannels; + +/** The "format specific parameters" field from the "a=fmtp" line in the SDP */ +@property(nonatomic, readonly) NSDictionary *parameters; + +/** The MIME type of the codec. */ +@property(nonatomic, readonly) NSString *mimeType; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCodecParameters.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCodecParameters.h new file mode 100644 index 00000000..9f96220c --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpCodecParameters.h @@ -0,0 +1,72 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_EXTERN const NSString *const kRTCRtxCodecName; +RTC_EXTERN const NSString *const kRTCRedCodecName; +RTC_EXTERN const NSString *const kRTCUlpfecCodecName; +RTC_EXTERN const NSString *const kRTCFlexfecCodecName; +RTC_EXTERN const NSString *const kRTCOpusCodecName; +RTC_EXTERN const NSString *const kRTCIsacCodecName; +RTC_EXTERN const NSString *const kRTCL16CodecName; +RTC_EXTERN const NSString *const kRTCG722CodecName; +RTC_EXTERN const NSString *const kRTCPcmuCodecName; +RTC_EXTERN const NSString *const kRTCPcmaCodecName; +RTC_EXTERN const NSString *const kRTCDtmfCodecName; +RTC_EXTERN const NSString *const kRTCComfortNoiseCodecName; +RTC_EXTERN const NSString *const kRTCVp8CodecName; +RTC_EXTERN const NSString *const kRTCVp9CodecName; +RTC_EXTERN const NSString *const kRTCH264CodecName; + +/** Defined in https://www.w3.org/TR/webrtc/#idl-def-rtcrtpcodecparameters */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpCodecParameters) : NSObject + +/** The RTP payload type. */ +@property(nonatomic, assign) int payloadType; + +/** + * The codec MIME subtype. Valid types are listed in: + * http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-2 + * + * Several supported types are represented by the constants above. + */ +@property(nonatomic, readonly, nonnull) NSString *name; + +/** + * The media type of this codec. Equivalent to MIME top-level type. + * + * Valid values are kRTCMediaStreamTrackKindAudio and + * kRTCMediaStreamTrackKindVideo. + */ +@property(nonatomic, readonly, nonnull) NSString *kind; + +/** The codec clock rate expressed in Hertz. */ +@property(nonatomic, readonly, nullable) NSNumber *clockRate; + +/** + * The number of channels (mono=1, stereo=2). + * Set to null for video codecs. + **/ +@property(nonatomic, readonly, nullable) NSNumber *numChannels; + +/** The "format specific parameters" field from the "a=fmtp" line in the SDP */ +@property(nonatomic, readonly, nonnull) NSDictionary *parameters; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpEncodingParameters.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpEncodingParameters.h new file mode 100644 index 00000000..a4ad10a3 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpEncodingParameters.h @@ -0,0 +1,77 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Corresponds to webrtc::Priority. */ +typedef NS_ENUM(NSInteger, RTCPriority) { + RTCPriorityVeryLow, + RTCPriorityLow, + RTCPriorityMedium, + RTCPriorityHigh +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpEncodingParameters) : NSObject + +/** The idenfifier for the encoding layer. This is used in simulcast. */ +@property(nonatomic, copy, nullable) NSString *rid; + +/** Controls whether the encoding is currently transmitted. */ +@property(nonatomic, assign) BOOL isActive; + +/** The maximum bitrate to use for the encoding, or nil if there is no + * limit. + */ +@property(nonatomic, copy, nullable) NSNumber *maxBitrateBps; + +/** The minimum bitrate to use for the encoding, or nil if there is no + * limit. + */ +@property(nonatomic, copy, nullable) NSNumber *minBitrateBps; + +/** The maximum framerate to use for the encoding, or nil if there is no + * limit. + */ +@property(nonatomic, copy, nullable) NSNumber *maxFramerate; + +/** The requested number of temporal layers to use for the encoding, or nil + * if the default should be used. + */ +@property(nonatomic, copy, nullable) NSNumber *numTemporalLayers; + +/** Scale the width and height down by this factor for video. If nil, + * implementation default scaling factor will be used. + */ +@property(nonatomic, copy, nullable) NSNumber *scaleResolutionDownBy; + +/** The SSRC being used by this encoding. */ +@property(nonatomic, readonly, nullable) NSNumber *ssrc; + +/** The relative bitrate priority. */ +@property(nonatomic, assign) double bitratePriority; + +/** The relative DiffServ Code Point priority. */ +@property(nonatomic, assign) RTCPriority networkPriority; + +/** Allow dynamic frame length changes for audio: + https://w3c.github.io/webrtc-extensions/#dom-rtcrtpencodingparameters-adaptiveptime + */ +@property(nonatomic, assign) BOOL adaptiveAudioPacketTime; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpHeaderExtension.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpHeaderExtension.h new file mode 100644 index 00000000..d3f1fda8 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpHeaderExtension.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpHeaderExtension) : NSObject + +/** The URI of the RTP header extension, as defined in RFC5285. */ +@property(nonatomic, readonly, copy) NSString *uri; + +/** The value put in the RTP packet to identify the header extension. */ +@property(nonatomic, readonly) int id; + +/** Whether the header extension is encrypted or not. */ +@property(nonatomic, readonly, getter=isEncrypted) BOOL encrypted; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpHeaderExtensionCapability.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpHeaderExtensionCapability.h new file mode 100644 index 00000000..2fbcce36 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpHeaderExtensionCapability.h @@ -0,0 +1,39 @@ +/* + * Copyright 2024 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSInteger, RTCRtpTransceiverDirection); + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpHeaderExtensionCapability) : NSObject + +/** The URI of the RTP header extension, as defined in RFC5285. */ +@property(nonatomic, readonly, copy) NSString *uri; + +/** The value put in the RTP packet to identify the header extension. */ +@property(nonatomic, readonly, nullable) NSNumber* preferredId; + +/** Whether the header extension is encrypted or not. */ +@property(nonatomic, readonly, getter=isPreferredEncrypted) + BOOL preferredEncrypted; + +/** Direction of the header extension. */ +@property(nonatomic) RTCRtpTransceiverDirection direction; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpParameters.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpParameters.h new file mode 100644 index 00000000..0e973643 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpParameters.h @@ -0,0 +1,65 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Corresponds to webrtc::DegradationPreference. */ +typedef NS_ENUM(NSInteger, RTCDegradationPreference) { + RTCDegradationPreferenceMaintainFramerateAndResolution, + // TODO(webrtc:450044904): Switch downstream projects to + // RTCDegradationPreferenceMaintainFramerateAndResolution and remove + // RTCDegradationPreferenceDisabled. + RTCDegradationPreferenceDisabled = + RTCDegradationPreferenceMaintainFramerateAndResolution, + RTCDegradationPreferenceMaintainFramerate, + RTCDegradationPreferenceMaintainResolution, + RTCDegradationPreferenceBalanced +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpParameters) : NSObject + +/** A unique identifier for the last set of parameters applied. */ +@property(nonatomic, copy) NSString *transactionId; + +/** Parameters used for RTCP. */ +@property(nonatomic, readonly, copy) RTC_OBJC_TYPE(RTCRtcpParameters) * rtcp; + +/** An array containing parameters for RTP header extensions. */ +@property(nonatomic, readonly, copy) + NSArray *headerExtensions; + +/** The currently active encodings in the order of preference. */ +@property(nonatomic, copy) + NSArray *encodings; + +/** The negotiated set of send codecs in order of preference. */ +@property(nonatomic, copy) + NSArray *codecs; + +/** + * Degradation preference in case of CPU adaptation or constrained bandwidth. + * If nil, implementation default degradation preference will be used. + */ +@property(nonatomic, copy, nullable) NSNumber *degradationPreference; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpReceiver.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpReceiver.h new file mode 100644 index 00000000..ad5a6c10 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpReceiver.h @@ -0,0 +1,105 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Represents the media type of the RtpReceiver. */ +typedef NS_ENUM(NSInteger, RTCRtpMediaType) { + RTCRtpMediaTypeAudio, + RTCRtpMediaTypeVideo, + RTCRtpMediaTypeData, + RTCRtpMediaTypeUnsupported, + RTCRtpMediaTypeAny, +}; + +@class RTC_OBJC_TYPE(RTCRtpReceiver); +@class RTC_OBJC_TYPE(RTCRtpSource); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpReceiverDelegate) + + /** Called when the first RTP packet is received. + * + * Note: Currently if there are multiple RtpReceivers of the same media + * type, they will all call OnFirstPacketReceived at once. + * + * For example, if we create three audio receivers, A/B/C, they will listen + * to the same signal from the underneath network layer. Whenever the first + * audio packet is received, the underneath signal will be fired. All the + * receivers A/B/C will be notified and the callback of the receiver's + * delegate will be called. + * + * The process is the same for video receivers. + */ + - (void)rtpReceiver : (RTC_OBJC_TYPE(RTCRtpReceiver) *) + rtpReceiver didReceiveFirstPacketForMediaType + : (RTCRtpMediaType)mediaType; +/** Called when the first RTP packet is received after a change in + * receptiveness. + */ +// TODO: crbug.com/40821064 - remove @optional. +@optional +- (void)rtpReceiver:(RTC_OBJC_TYPE(RTCRtpReceiver) *)rtpReceiver + didReceiveFirstPacketForMediaTypeAfterReceptiveChange: + (RTCRtpMediaType)mediaType; + +@end + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpReceiver) + + /** A unique identifier for this receiver. */ + @property(nonatomic, readonly) NSString *receiverId; + +/** The currently active RTCRtpParameters, as defined in + * https://www.w3.org/TR/webrtc/#idl-def-RTCRtpParameters. + * + * The WebRTC specification only defines RTCRtpParameters in terms of senders, + * but this API also applies them to receivers, similar to ORTC: + * http://ortc.org/wp-content/uploads/2016/03/ortc.html#rtcrtpparameters*. + */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCRtpParameters) * parameters; + +/** The RTCMediaStreamTrack associated with the receiver. + * Note: reading this property returns a new instance of + * RTCMediaStreamTrack. Use isEqual: instead of == to compare + * RTCMediaStreamTrack instances. + */ +@property(nonatomic, readonly, nullable) RTC_OBJC_TYPE(RTCMediaStreamTrack) * + track; + +/** +Returns an array that contains an object for each unique SSRC (synchronization +source) identifier and for each unique CSRC (contributing source) received by +the current RTCRtpReceiver in the last ten seconds. +*/ +@property(nonatomic, readonly) NSArray *sources; + +/** The delegate for this RtpReceiver. */ +@property(nonatomic, weak) id delegate; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpReceiver) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpSender.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpSender.h new file mode 100644 index 00000000..841a65e8 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpSender.h @@ -0,0 +1,55 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpSender) + + /** A unique identifier for this sender. */ + @property(nonatomic, readonly) NSString *senderId; + +/** The currently active RTCRtpParameters, as defined in + * https://www.w3.org/TR/webrtc/#idl-def-RTCRtpParameters. + */ +@property(nonatomic, copy) RTC_OBJC_TYPE(RTCRtpParameters) * parameters; + +/** The RTCMediaStreamTrack associated with the sender. + * Note: reading this property returns a new instance of + * RTCMediaStreamTrack. Use isEqual: instead of == to compare + * RTCMediaStreamTrack instances. + */ +@property(nonatomic, copy, nullable) RTC_OBJC_TYPE(RTCMediaStreamTrack) * track; + +/** IDs of streams associated with the RTP sender */ +@property(nonatomic, copy) NSArray *streamIds; + +/** The RTCDtmfSender accociated with the RTP sender. */ +@property(nonatomic, readonly, nullable) id + dtmfSender; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpSender) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpSource.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpSource.h new file mode 100644 index 00000000..a36a0bc1 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpSource.h @@ -0,0 +1,66 @@ +/* + * Copyright 2024 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Represents the source type of received media. */ +typedef NS_ENUM(NSInteger, RTCRtpSourceType) { + RTCRtpSourceTypeSSRC, + RTCRtpSourceTypeCSRC, +}; + +@class RTC_OBJC_TYPE(RTCRtpSource); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpSource) + + /** + A positive integer value specifying the CSRC identifier of the contributing + source or SSRC identifier of the synchronization source. This uniquely + identifies the source of the particular stream RTP packets. */ + @property(nonatomic, readonly) uint32_t sourceId; + +@property(nonatomic, readonly) RTCRtpSourceType sourceType; + +/** +A floating-point value between 0.0 and 1.0 specifying the audio level contained +in the last RTP packet played from the contributing source. +*/ +@property(nonatomic, readonly, nullable) NSNumber *audioLevel; + +/** +A timestamp indicating the most recent time at which a frame originating from +this source was delivered to the receiver's track +*/ +@property(nonatomic, readonly) CFTimeInterval timestampUs; + +/** +The RTP timestamp of the media. This source-generated timestamp indicates the +time at which the media in this packet, scheduled for play out at the time +indicated by timestamp, was initially sampled or generated. It may be useful for +sequencing and synchronization purposes. +*/ +@property(nonatomic, readonly) uint32_t rtpTimestamp; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpSource) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpTransceiver.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpTransceiver.h new file mode 100644 index 00000000..2c5084bb --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCRtpTransceiver.h @@ -0,0 +1,177 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const kRTCRtpTransceiverErrorDomain; + +/** https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiverdirection */ +typedef NS_ENUM(NSInteger, RTCRtpTransceiverDirection) { + RTCRtpTransceiverDirectionSendRecv, + RTCRtpTransceiverDirectionSendOnly, + RTCRtpTransceiverDirectionRecvOnly, + RTCRtpTransceiverDirectionInactive, + RTCRtpTransceiverDirectionStopped +}; + +/** Structure for initializing an RTCRtpTransceiver in a call to + * RTCPeerConnection.addTransceiver. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiverinit + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpTransceiverInit) : NSObject + +/** Direction of the RTCRtpTransceiver. See RTCRtpTransceiver.direction. */ +@property(nonatomic) RTCRtpTransceiverDirection direction; + +/** The added RTCRtpTransceiver will be added to these streams. */ +@property(nonatomic) NSArray *streamIds; + +/** TODO(bugs.webrtc.org/7600): Not implemented. */ +@property(nonatomic) + NSArray *sendEncodings; + +@end + +@class RTC_OBJC_TYPE(RTCRtpTransceiver); +@class RTC_OBJC_TYPE(RTCRtpCodecCapability); +@class RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability); + +/** The RTCRtpTransceiver maps to the RTCRtpTransceiver defined by the + * WebRTC specification. A transceiver represents a combination of an + * RTCRtpSender and an RTCRtpReceiver that share a common mid. As defined in + * JSEP, an RTCRtpTransceiver is said to be associated with a media description + * if its mid property is non-nil; otherwise, it is said to be disassociated. + * JSEP: https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24 + * + * Note that RTCRtpTransceivers are only supported when using + * RTCPeerConnection with Unified Plan SDP. + * + * WebRTC specification for RTCRtpTransceiver, the JavaScript analog: + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpTransceiver) + + /** Media type of the transceiver. The sender and receiver will also have + * this type. + */ + @property(nonatomic, readonly) RTCRtpMediaType mediaType; + +/** The mid attribute is the mid negotiated and present in the local and + * remote descriptions. Before negotiation is complete, the mid value may be + * nil. After rollbacks, the value may change from a non-nil value to nil. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-mid + */ +@property(nonatomic, readonly) NSString *mid; + +/** The sender attribute exposes the RTCRtpSender corresponding to the RTP + * media that may be sent with the transceiver's mid. The sender is always + * present, regardless of the direction of media. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-sender + */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCRtpSender) * sender; + +/** The receiver attribute exposes the RTCRtpReceiver corresponding to the RTP + * media that may be received with the transceiver's mid. The receiver is + * always present, regardless of the direction of media. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-receiver + */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCRtpReceiver) * receiver; + +/** The isStopped attribute indicates that the sender of this transceiver will + * no longer send, and that the receiver will no longer receive. It is true if + * either stop has been called or if setting the local or remote description + * has caused the RTCRtpTransceiver to be stopped. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stopped + */ +@property(nonatomic, readonly) BOOL isStopped; + +/** The direction attribute indicates the preferred direction of this + * transceiver, which will be used in calls to createOffer and createAnswer. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction + */ +@property(nonatomic, readonly) RTCRtpTransceiverDirection direction; + +/** It will contain all the RTP header extensions that are supported. + * The direction attribute for all extensions that are mandatory to use MUST be + * initialized to an appropriate value other than + * RTCRtpTransceiverDirectionStopped. The direction attribute for extensions + * that will not be offered by default in an initial offer MUST be initialized + * to RTCRtpTransceiverDirectionStopped. + */ +@property(nonatomic, readonly, copy) + NSArray + *headerExtensionsToNegotiate; +@property(nonatomic, readonly, copy) + NSArray + *negotiatedHeaderExtensions; + +/** The currentDirection attribute indicates the current direction negotiated + * for this transceiver. If this transceiver has never been represented in an + * offer/answer exchange, or if the transceiver is stopped, the value is not + * present and this method returns NO. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-currentdirection + */ +- (BOOL)currentDirection:(RTCRtpTransceiverDirection *)currentDirectionOut; + +/** The stop method irreversibly stops the RTCRtpTransceiver. The sender of + * this transceiver will no longer send, the receiver will no longer receive. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stop + */ +- (void)stopInternal; + +/** The setCodecPreferences method overrides the default codec preferences used + * by WebRTC for this transceiver. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-setcodecpreferences + */ +- (BOOL)setCodecPreferences: + (NSArray *_Nullable)codecs + error:(NSError **_Nullable)error; + +/** Deprecated version of [RTCRtpTransceiver setCodecPreferences:error:] */ +- (void)setCodecPreferences: + (NSArray *_Nullable)codecs + RTC_OBJC_DEPRECATED("Use setCodecPreferences:error: instead."); + +/** The setHeaderExtensionsToNegotiate method overrides the default header + * extensions used by WebRTC for this transceiver. + * https://w3c.github.io/webrtc-extensions/#ref-for-dom-rtcrtptransceiver-setheaderextensionstonegotiate + */ +- (BOOL)setHeaderExtensionsToNegotiate: + (NSArray *) + extensions + error:(NSError **)error; + +/** An update of directionality does not take effect immediately. Instead, + * future calls to createOffer and createAnswer mark the corresponding media + * descriptions as sendrecv, sendonly, recvonly, or inactive. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction + */ +- (void)setDirection:(RTCRtpTransceiverDirection)direction + error:(NSError **)error; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpTransceiver) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSSLAdapter.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSSLAdapter.h new file mode 100644 index 00000000..a0da827c --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSSLAdapter.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +/** + * Initialize and clean up the SSL library. Failure is fatal. These call the + * corresponding functions in webrtc/rtc_base/ssladapter.h. + */ +RTC_EXTERN BOOL RTCInitializeSSL(void); +RTC_EXTERN BOOL RTCCleanupSSL(void); diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSSLCertificateVerifier.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSSLCertificateVerifier.h new file mode 100644 index 00000000..d5acafad --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSSLCertificateVerifier.h @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT @protocol RTC_OBJC_TYPE +(RTCSSLCertificateVerifier) + + /** The certificate to verify */ + - (BOOL)verify : (NSData *)derCertificate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSessionDescription.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSessionDescription.h new file mode 100644 index 00000000..e513c168 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCSessionDescription.h @@ -0,0 +1,49 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +/** + * Represents the session description type. This exposes the same types that are + * in C++, which doesn't include the rollback type that is in the W3C spec. + */ +typedef NS_ENUM(NSInteger, RTCSdpType) { + RTCSdpTypeOffer, + RTCSdpTypePrAnswer, + RTCSdpTypeAnswer, + RTCSdpTypeRollback, +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCSessionDescription) : NSObject + +/** The type of session description. */ +@property(nonatomic, readonly) RTCSdpType type; + +/** The SDP string representation of this session description. */ +@property(nonatomic, readonly) NSString *sdp; + +- (instancetype)init NS_UNAVAILABLE; + +/** Initialize a session description with a type and SDP string. */ +- (instancetype)initWithType:(RTCSdpType)type + sdp:(NSString *)sdp NS_DESIGNATED_INITIALIZER; + ++ (NSString *)stringForType:(RTCSdpType)type; + ++ (RTCSdpType)typeForString:(NSString *)string; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCStatisticsReport.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCStatisticsReport.h new file mode 100644 index 00000000..785d9499 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCStatisticsReport.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +@class RTC_OBJC_TYPE(RTCStatistics); + +NS_ASSUME_NONNULL_BEGIN + +/** A statistics report. Encapsulates a number of RTCStatistics objects. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCStatisticsReport) : NSObject + +/** The timestamp of the report in microseconds since 1970-01-01T00:00:00Z. */ +@property(nonatomic, readonly) CFTimeInterval timestamp_us; + +/** RTCStatistics objects by id. */ +@property(nonatomic, readonly) + NSDictionary *statistics; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +/** A part of a report (a subreport) covering a certain area. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCStatistics) : NSObject + +/** The id of this subreport, e.g. "RTCMediaStreamTrack_receiver_2". */ +@property(nonatomic, readonly) NSString *id; + +/** The timestamp of the subreport in microseconds since 1970-01-01T00:00:00Z. + */ +@property(nonatomic, readonly) CFTimeInterval timestamp_us; + +/** The type of the subreport, e.g. "track", "codec". */ +@property(nonatomic, readonly) NSString *type; + +/** The keys and values of the subreport, e.g. "totalFramesDuration = 5.551". + The values are either NSNumbers or NSStrings or NSArrays encapsulating + NSNumbers or NSStrings, or NSDictionary of NSString keys to NSNumber values. + */ +@property(nonatomic, readonly) NSDictionary *values; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCTracing.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCTracing.h new file mode 100644 index 00000000..899a5700 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCTracing.h @@ -0,0 +1,21 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +RTC_EXTERN void RTCSetupInternalTracer(void); +/** Starts capture to specified file. Must be a valid writable path. + * Returns YES if capture starts. + */ +RTC_EXTERN BOOL RTCStartInternalCapture(NSString* filePath); +RTC_EXTERN void RTCStopInternalCapture(void); +RTC_EXTERN void RTCShutdownInternalTracer(void); diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCapturer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCapturer.h new file mode 100644 index 00000000..78b1befc --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCapturer.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCVideoCapturer); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoCapturerDelegate) - + (void)capturer + : (RTC_OBJC_TYPE(RTCVideoCapturer) *)capturer didCaptureVideoFrame + : (RTC_OBJC_TYPE(RTCVideoFrame) *)frame; +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoCapturer) : NSObject + +@property(nonatomic, weak) id delegate; + +- (instancetype)initWithDelegate: + (id)delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCodecConstants.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCodecConstants.h new file mode 100644 index 00000000..0277bfc4 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCodecConstants.h @@ -0,0 +1,17 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +RTC_EXTERN NSString* const kRTCVideoCodecVp8Name; +RTC_EXTERN NSString* const kRTCVideoCodecVp9Name; +RTC_EXTERN NSString* const kRTCVideoCodecAv1Name; diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCodecInfo.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCodecInfo.h new file mode 100644 index 00000000..0a2c2859 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoCodecInfo.h @@ -0,0 +1,43 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Holds information to identify a codec. Corresponds to + * webrtc::SdpVideoFormat. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoCodecInfo) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithName:(NSString *)name; + +- (instancetype)initWithName:(NSString *)name + parameters:(nullable NSDictionary *) + parameters; + +- (instancetype)initWithName:(NSString *)name + parameters:(NSDictionary *)parameters + scalabilityModes:(NSArray *)scalabilityModes + NS_DESIGNATED_INITIALIZER; + +- (BOOL)isEqualToCodecInfo:(RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info; + +@property(nonatomic, readonly) NSString *name; +@property(nonatomic, readonly) NSDictionary *parameters; +@property(nonatomic, readonly) NSArray *scalabilityModes; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoder.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoder.h new file mode 100644 index 00000000..e7bd6365 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoder.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Callback block for decoder. */ +typedef void (^RTCVideoDecoderCallback)(RTC_OBJC_TYPE(RTCVideoFrame) * frame); + +/** Protocol for decoder implementations. */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoDecoder) + + - (void)setCallback : (RTCVideoDecoderCallback)callback; +- (NSInteger)startDecodeWithNumberOfCores:(int)numberOfCores; +- (NSInteger)releaseDecoder; +// TODO(bugs.webrtc.org/15444): Remove obsolete missingFrames param. +- (NSInteger)decode:(RTC_OBJC_TYPE(RTCEncodedImage) *)encodedImage + missingFrames:(BOOL)missingFrames + codecSpecificInfo:(nullable id)info + renderTimeMs:(int64_t)renderTimeMs; +- (NSString *)implementationName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderAV1.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderAV1.h new file mode 100644 index 00000000..c6cb38c2 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderAV1.h @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderAV1) : NSObject + +/* This returns a AV1 decoder that can be returned from a RTCVideoDecoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoDecoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (id)av1Decoder; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderFactory.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderFactory.h new file mode 100644 index 00000000..5eb598a0 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderFactory.h @@ -0,0 +1,33 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** RTCVideoDecoderFactory is an Objective-C version of + * webrtc::VideoDecoderFactory. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoDecoderFactory) + + - (nullable id)createDecoder + : (RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info; +- (NSArray *) + supportedCodecs; // TODO(andersc): "supportedFormats" instead? + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderFactoryH264.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderFactoryH264.h new file mode 100644 index 00000000..d5ce05ed --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderFactoryH264.h @@ -0,0 +1,18 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderFactoryH264) : NSObject +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderH264.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderH264.h new file mode 100644 index 00000000..f1c84689 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderH264.h @@ -0,0 +1,18 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderH264) : NSObject +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderVP8.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderVP8.h new file mode 100644 index 00000000..9e3af0dc --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderVP8.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderVP8) : NSObject + +/* This returns a VP8 decoder that can be returned from a RTCVideoDecoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoDecoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (id)vp8Decoder; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderVP9.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderVP9.h new file mode 100644 index 00000000..6c76ebad --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoDecoderVP9.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderVP9) : NSObject + +/* This returns a VP9 decoder that can be returned from a RTCVideoDecoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoDecoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (id)vp9Decoder; + ++ (bool)isSupported; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoder.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoder.h new file mode 100644 index 00000000..1b0a16d9 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoder.h @@ -0,0 +1,62 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Callback block for encoder. */ +typedef BOOL (^RTCVideoEncoderCallback)( + RTC_OBJC_TYPE(RTCEncodedImage) * frame, + id info); + +/** Protocol for encoder implementations. */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoEncoder) + + - (void)setCallback : (nullable RTCVideoEncoderCallback)callback; +- (NSInteger)startEncodeWithSettings: + (RTC_OBJC_TYPE(RTCVideoEncoderSettings) *)settings + numberOfCores:(int)numberOfCores; +- (NSInteger)releaseEncoder; +- (NSInteger)encode:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame + codecSpecificInfo:(nullable id)info + frameTypes:(NSArray *)frameTypes; +- (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate; +- (NSString *)implementationName; + +/** Returns QP scaling settings for encoder. The quality scaler adjusts the + * resolution in order to keep the QP from the encoded images within the given + * range. Returning nil from this function disables quality scaling. */ +- (nullable RTC_OBJC_TYPE(RTCVideoEncoderQpThresholds) *)scalingSettings; + +/** Resolutions should be aligned to this value. */ +@property(nonatomic, readonly) NSInteger resolutionAlignment; + +/** If enabled, resolution alignment is applied to all simulcast layers + simultaneously so that when scaled, all resolutions comply with + 'resolutionAlignment'. */ +@property(nonatomic, readonly) BOOL applyAlignmentToAllSimulcastLayers; + +/** If YES, the receiver is expected to resample/scale the source texture to the + expected output size. */ +@property(nonatomic, readonly) BOOL supportsNativeHandle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderAV1.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderAV1.h new file mode 100644 index 00000000..4c97563b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderAV1.h @@ -0,0 +1,32 @@ +/* + * Copyright 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderAV1) : NSObject + +/* This returns a AV1 encoder that can be returned from a RTCVideoEncoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoEncoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (nonnull id)av1Encoder; + +/* Returns list of scalability modes supported by the encoder that can be + * created with `av1Encoder` method above. + */ ++ (nonnull NSArray*)supportedScalabilityModes; + ++ (bool)isSupported; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderFactory.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderFactory.h new file mode 100644 index 00000000..85745458 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderFactory.h @@ -0,0 +1,78 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** RTCVideoEncoderFactory is an Objective-C version of + webrtc::VideoEncoderFactory::VideoEncoderSelector. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoEncoderSelector) + + - (void)registerCurrentEncoderInfo + : (RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info; +- (nullable RTC_OBJC_TYPE(RTCVideoCodecInfo) *)encoderForBitrate: + (NSInteger)bitrate; +- (nullable RTC_OBJC_TYPE(RTCVideoCodecInfo) *)encoderForBrokenEncoder; + +@optional +- (nullable RTC_OBJC_TYPE(RTCVideoCodecInfo) *)encoderForResolutionChangeBySize: + (CGSize)size; + +@end + +/** RTCVideoEncoderCodecSupport is an Objective-C version of + * webrtc::VideoEncoderFactory::CodecSupport. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderCodecSupport) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithSupported:(bool)isSupported; +- (instancetype)initWithSupported:(bool)isSupported + isPowerEfficient:(bool)isPowerEfficient + NS_DESIGNATED_INITIALIZER; + +@property(nonatomic, readonly) bool isSupported; +@property(nonatomic, readonly) bool isPowerEfficient; + +@end + +/** RTCVideoEncoderFactory is an Objective-C version of + * webrtc::VideoEncoderFactory. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoEncoderFactory) + + - (nullable id)createEncoder + : (RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info; +- (NSArray *) + supportedCodecs; // TODO(andersc): "supportedFormats" instead? + +@optional +- (NSArray *)implementations; +- (nullable id)encoderSelector; +/* TODO: b/299588022 - move to non-optional section when implemented by all + * derived classes. */ +- (RTC_OBJC_TYPE(RTCVideoEncoderCodecSupport) *) + queryCodecSupport:(RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info + scalabilityMode:(nullable NSString *)scalabilityMode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderFactoryH264.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderFactoryH264.h new file mode 100644 index 00000000..ac9bac8a --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderFactoryH264.h @@ -0,0 +1,18 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderFactoryH264) : NSObject +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderH264.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderH264.h new file mode 100644 index 00000000..37ff4e4b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderH264.h @@ -0,0 +1,22 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderH264) : NSObject + +- (instancetype)initWithCodecInfo:(RTC_OBJC_TYPE(RTCVideoCodecInfo) *)codecInfo; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderQpThresholds.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderQpThresholds.h new file mode 100644 index 00000000..818c2728 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderQpThresholds.h @@ -0,0 +1,29 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** QP thresholds for encoder. Corresponds to + * webrtc::VideoEncoder::QpThresholds. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderQpThresholds) : NSObject + +- (instancetype)initWithThresholdsLow:(NSInteger)low high:(NSInteger)high; + +@property(nonatomic, readonly) NSInteger low; +@property(nonatomic, readonly) NSInteger high; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderSettings.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderSettings.h new file mode 100644 index 00000000..efbdb5ee --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderSettings.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, RTCVideoCodecMode) { + RTCVideoCodecModeRealtimeVideo, + RTCVideoCodecModeScreensharing, +}; + +/** Settings for encoder. Corresponds to webrtc::VideoCodec. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderSettings) : NSObject + +@property(nonatomic, strong) NSString *name; + +@property(nonatomic, assign) unsigned short width; +@property(nonatomic, assign) unsigned short height; + +@property(nonatomic, assign) unsigned int startBitrate; // kilobits/sec. +@property(nonatomic, assign) unsigned int maxBitrate; +@property(nonatomic, assign) unsigned int minBitrate; + +@property(nonatomic, assign) uint32_t maxFramerate; + +@property(nonatomic, assign) unsigned int qpMax; +@property(nonatomic, assign) RTCVideoCodecMode mode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderVP8.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderVP8.h new file mode 100644 index 00000000..789ae28b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderVP8.h @@ -0,0 +1,30 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderVP8) : NSObject + +/* This returns a VP8 encoder that can be returned from a RTCVideoEncoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoEncoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (nonnull id)vp8Encoder; + +/* Returns list of scalability modes supported by the encoder that can be + * created with `vp8Encoder` method above. + */ ++ (nonnull NSArray*)supportedScalabilityModes; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderVP9.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderVP9.h new file mode 100644 index 00000000..86c3ddf5 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoEncoderVP9.h @@ -0,0 +1,32 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderVP9) : NSObject + +/* This returns a VP9 encoder that can be returned from a RTCVideoEncoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoEncoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (nullable id)vp9Encoder; + +/* Returns list of scalability modes supported by the encoder that can be + * created with `vp9Encoder` method above. + */ ++ (nonnull NSArray*)supportedScalabilityModes; + ++ (bool)isSupported; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoFrame.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoFrame.h new file mode 100644 index 00000000..c9f7cfc9 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoFrame.h @@ -0,0 +1,64 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, RTCVideoRotation) { + RTCVideoRotation_0 = 0, + RTCVideoRotation_90 = 90, + RTCVideoRotation_180 = 180, + RTCVideoRotation_270 = 270, +}; + +@protocol RTC_OBJC_TYPE +(RTCVideoFrameBuffer); + +// RTCVideoFrame is an ObjectiveC version of webrtc::VideoFrame. +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoFrame) : NSObject + +/** Width without rotation applied. */ +@property(nonatomic, readonly) int width; + +/** Height without rotation applied. */ +@property(nonatomic, readonly) int height; +@property(nonatomic, readonly) RTCVideoRotation rotation; + +/** Timestamp in nanoseconds. */ +@property(nonatomic, readonly) int64_t timeStampNs; + +/** Timestamp 90 kHz. */ +@property(nonatomic, assign) int32_t timeStamp; + +@property(nonatomic, readonly) id buffer; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)new NS_UNAVAILABLE; + +/** Initialize an RTCVideoFrame from a frame buffer, rotation, and timestamp. + */ +- (instancetype)initWithBuffer: + (id)frameBuffer + rotation:(RTCVideoRotation)rotation + timeStampNs:(int64_t)timeStampNs; + +/** Return a frame that is guaranteed to be I420, i.e. it is possible to access + * the YUV data on it. + */ +- (RTC_OBJC_TYPE(RTCVideoFrame) *)newI420VideoFrame; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoFrameBuffer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoFrameBuffer.h new file mode 100644 index 00000000..7efbd2b7 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoFrameBuffer.h @@ -0,0 +1,40 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol RTC_OBJC_TYPE +(RTCI420Buffer); + +// RTCVideoFrameBuffer is an ObjectiveC version of webrtc::VideoFrameBuffer. +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoFrameBuffer) + + @property(nonatomic, readonly) int width; +@property(nonatomic, readonly) int height; + +- (id)toI420; + +@optional +- (id)cropAndScaleWith:(int)offsetX + offsetY:(int)offsetY + cropWidth:(int)cropWidth + cropHeight:(int)cropHeight + scaleWidth:(int)scaleWidth + scaleHeight:(int)scaleHeight; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoRenderer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoRenderer.h new file mode 100644 index 00000000..11828446 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoRenderer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#if TARGET_OS_IPHONE +#import +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCVideoFrame); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoRenderer) + + /** The size of the frame. */ + - (void)setSize : (CGSize)size; + +/** The frame to be displayed. */ +- (void)renderFrame:(nullable RTC_OBJC_TYPE(RTCVideoFrame) *)frame; + +@end + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoViewDelegate) + + - (void)videoView + : (id)videoView didChangeVideoSize + : (CGSize)size; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoSource.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoSource.h new file mode 100644 index 00000000..24285275 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoSource.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT + +@interface RTC_OBJC_TYPE (RTCVideoSource) : RTC_OBJC_TYPE(RTCMediaSource) + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Calling this function will cause frames to be scaled down to the + * requested resolution. Also, frames will be cropped to match the + * requested aspect ratio, and frames will be dropped to match the + * requested fps. The requested aspect ratio is orientation agnostic and + * will be adjusted to maintain the input orientation, so it doesn't + * matter if e.g. 1280x720 or 720x1280 is requested. + */ +- (void)adaptOutputFormatToWidth:(int)width height:(int)height fps:(int)fps; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoTrack.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoTrack.h new file mode 100644 index 00000000..18368168 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoTrack.h @@ -0,0 +1,38 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol RTC_OBJC_TYPE +(RTCVideoRenderer); +@class RTC_OBJC_TYPE(RTCPeerConnectionFactory); +@class RTC_OBJC_TYPE(RTCVideoSource); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoTrack) : RTC_OBJC_TYPE(RTCMediaStreamTrack) + +/** The video source for this video track. */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCVideoSource) *source; + +- (instancetype)init NS_UNAVAILABLE; + +/** Register a renderer that will render all frames received on this track. */ +- (void)addRenderer:(id)renderer; + +/** Deregister a renderer. */ +- (void)removeRenderer:(id)renderer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoViewShading.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoViewShading.h new file mode 100644 index 00000000..afba0154 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCVideoViewShading.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * RTCVideoViewShading provides a way for apps to customize the OpenGL(ES + * shaders used in rendering for the RTCEAGLVideoView/RTCNSGLVideoView. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoViewShading) + + /** Callback for I420 frames. Each plane is given as a texture. */ + - (void)applyShadingForFrameWithWidth : (int)width height + : (int)height rotation : (RTCVideoRotation)rotation yPlane + : (GLuint)yPlane uPlane : (GLuint)uPlane vPlane : (GLuint)vPlane; + +/** Callback for NV12 frames. Each plane is given as a texture. */ +- (void)applyShadingForFrameWithWidth:(int)width + height:(int)height + rotation:(RTCVideoRotation)rotation + yPlane:(GLuint)yPlane + uvPlane:(GLuint)uvPlane; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCYUVPlanarBuffer.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCYUVPlanarBuffer.h new file mode 100644 index 00000000..20a154c7 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/RTCYUVPlanarBuffer.h @@ -0,0 +1,46 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Protocol for RTCVideoFrameBuffers containing YUV planar data. */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCYUVPlanarBuffer) + + @property(nonatomic, readonly) int chromaWidth; +@property(nonatomic, readonly) int chromaHeight; +@property(nonatomic, readonly) const uint8_t *dataY; +@property(nonatomic, readonly) const uint8_t *dataU; +@property(nonatomic, readonly) const uint8_t *dataV; +@property(nonatomic, readonly) int strideY; +@property(nonatomic, readonly) int strideU; +@property(nonatomic, readonly) int strideV; + +- (instancetype)initWithWidth:(int)width + height:(int)height + dataY:(const uint8_t *)dataY + dataU:(const uint8_t *)dataU + dataV:(const uint8_t *)dataV; +- (instancetype)initWithWidth:(int)width height:(int)height; +- (instancetype)initWithWidth:(int)width + height:(int)height + strideY:(int)strideY + strideU:(int)strideU + strideV:(int)strideV; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/UIDevice+RTCDevice.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/UIDevice+RTCDevice.h new file mode 100644 index 00000000..4d04f38f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/UIDevice+RTCDevice.h @@ -0,0 +1,17 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +@interface UIDevice (RTCDevice) + ++ (NSString *)machineName; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/WebRTC.h b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/WebRTC.h new file mode 100644 index 00000000..1f88aebd --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Headers/WebRTC.h @@ -0,0 +1,102 @@ +/* + * Copyright 2026 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Info.plist b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Info.plist new file mode 100644 index 00000000..be9d9452 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Info.plist @@ -0,0 +1,59 @@ + + + + + BuildMachineOSBuild + 24G517 + CFBundleDevelopmentRegion + en + CFBundleExecutable + WebRTC + CFBundleIdentifier + org.webrtc.WebRTC + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + WebRTC + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + iPhoneOS + + CFBundleVersion + 1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 23C53 + DTPlatformName + iphoneos + DTPlatformVersion + 26.2 + DTSDKBuild + 23C53 + DTSDKName + iphoneos26.2 + DTXcode + 2620 + DTXcodeBuild + 17C52 + MinimumOSVersion + 12.0 + NSCameraUsageDescription + SimDeck uses WebRTC to receive simulator streams. This bundled WebRTC framework includes optional camera capture support. + NSMicrophoneUsageDescription + SimDeck uses WebRTC to receive simulator streams. This bundled WebRTC framework includes optional microphone capture support. + NSPrincipalClass + + UIDeviceFamily + + 1 + 2 + + + diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Modules/module.modulemap b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Modules/module.modulemap new file mode 100644 index 00000000..cd485a4e --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module WebRTC { + umbrella header "WebRTC.h" + + export * + module * { export * } +} diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/PrivacyInfo.xcprivacy b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..a1f67253 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,32 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + 8FFB.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + diff --git a/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/WebRTC b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/WebRTC new file mode 100755 index 00000000..5e3f5cee Binary files /dev/null and b/ios/Vendor/WebRTC.xcframework/ios-arm64/WebRTC.framework/WebRTC differ diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioDevice.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioDevice.h new file mode 100644 index 00000000..c28076ca --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioDevice.h @@ -0,0 +1,335 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef OSStatus (^RTC_OBJC_TYPE(RTCAudioDeviceGetPlayoutDataBlock))( + AudioUnitRenderActionFlags *_Nonnull actionFlags, + const AudioTimeStamp *_Nonnull timestamp, + NSInteger inputBusNumber, + UInt32 frameCount, + AudioBufferList *_Nonnull outputData); + +typedef OSStatus (^RTC_OBJC_TYPE(RTCAudioDeviceRenderRecordedDataBlock))( + AudioUnitRenderActionFlags *_Nonnull actionFlags, + const AudioTimeStamp *_Nonnull timestamp, + NSInteger inputBusNumber, + UInt32 frameCount, + AudioBufferList *_Nonnull inputData, + void *_Nullable renderContext); + +typedef OSStatus (^RTC_OBJC_TYPE(RTCAudioDeviceDeliverRecordedDataBlock))( + AudioUnitRenderActionFlags *_Nonnull actionFlags, + const AudioTimeStamp *_Nonnull timestamp, + NSInteger inputBusNumber, + UInt32 frameCount, + const AudioBufferList *_Nullable inputData, + void *_Nullable renderContext, + NS_NOESCAPE RTC_OBJC_TYPE( + RTCAudioDeviceRenderRecordedDataBlock) _Nullable renderBlock); + +/** + * Delegate object provided by native ADM during RTCAudioDevice initialization. + * Provides blocks to poll playback audio samples from native ADM and to feed + * recorded audio samples into native ADM. + */ +RTC_OBJC_EXPORT @protocol RTC_OBJC_TYPE +(RTCAudioDeviceDelegate) + /** + * Implementation of RTCAudioSource should call this block to feed recorded + * PCM (16-bit integer) into native ADM. Stereo data is expected to be + * interleaved starting with the left channel. Either `inputData` with + * pre-filled audio data must be provided during block call or `renderBlock` + * must be provided which must fill provided audio buffer with recorded + * samples. + * + * NOTE: Implementation of RTCAudioDevice is expected to call the block on + * the same thread until `notifyAudioInterrupted` is called. When + * `notifyAudioInterrupted` is called implementation can call the block on a + * different thread. + */ + @property(readonly, nonnull) + RTC_OBJC_TYPE(RTCAudioDeviceDeliverRecordedDataBlock) + deliverRecordedData; + +/** + * Provides input sample rate preference as it preferred by native ADM. + */ +@property(readonly) double preferredInputSampleRate; + +/** + * Provides input IO buffer duration preference as it preferred by native ADM. + */ +@property(readonly) NSTimeInterval preferredInputIOBufferDuration; + +/** + * Provides output sample rate preference as it preferred by native ADM. + */ +@property(readonly) double preferredOutputSampleRate; + +/** + * Provides output IO buffer duration preference as it preferred by native ADM. + */ +@property(readonly) NSTimeInterval preferredOutputIOBufferDuration; + +/** + * Implementation of RTCAudioDevice should call this block to request PCM + * (16-bit integer) from native ADM to play. Stereo data is interleaved starting + * with the left channel. + * + * NOTE: Implementation of RTCAudioDevice is expected to invoke of this block on + * the same thread until `notifyAudioInterrupted` is called. When + * `notifyAudioInterrupted` is called implementation can call the block from a + * different thread. + */ +@property(readonly, nonnull) RTC_OBJC_TYPE(RTCAudioDeviceGetPlayoutDataBlock) + getPlayoutData; + +/** + * Notifies native ADM that some of the audio input parameters of RTCAudioDevice + * like samle rate and/or IO buffer duration and/or IO latency had possibly + * changed. Native ADM will adjust its audio input buffer to match current + * parameters of audio device. + * + * NOTE: Must be called within block executed via `dispatchAsync` or + * `dispatchSync`. + */ +- (void)notifyAudioInputParametersChange; + +/** + * Notifies native ADM that some of the audio output parameters of + * RTCAudioDevice like samle rate and/or IO buffer duration and/or IO latency + * had possibly changed. Native ADM will adjust its audio output buffer to match + * current parameters of audio device. + * + * NOTE: Must be called within block executed via `dispatchAsync` or + * `dispatchSync`. + */ +- (void)notifyAudioOutputParametersChange; + +/** + * Notifies native ADM that audio input is interrupted and further audio playout + * and recording might happen on a different thread. + * + * NOTE: Must be called within block executed via `dispatchAsync` or + * `dispatchSync`. + */ +- (void)notifyAudioInputInterrupted; + +/** + * Notifies native ADM that audio output is interrupted and further audio + * playout and recording might happen on a different thread. + * + * NOTE: Must be called within block executed via `dispatchAsync` or + * `dispatchSync`. + */ +- (void)notifyAudioOutputInterrupted; + +/** + * Asynchronously execute block of code within the context of + * thread which owns native ADM. + * + * NOTE: Intended to be used to invoke `notifyAudioInputParametersChange`, + * `notifyAudioOutputParametersChange`, `notifyAudioInputInterrupted`, + * `notifyAudioOutputInterrupted` on native ADM thread. + * Also could be used by `RTCAudioDevice` implementation to tie + * mutations of underlying audio objects (AVAudioEngine, AudioUnit, etc) + * to the native ADM thread. Could be useful to handle events like audio route + * change, which could lead to audio parameters change. + */ +- (void)dispatchAsync:(dispatch_block_t)block; + +/** + * Synchronously execute block of code within the context of + * thread which owns native ADM. Allows reentrancy. + * + * NOTE: Intended to be used to invoke `notifyAudioInputParametersChange`, + * `notifyAudioOutputParametersChange`, `notifyAudioInputInterrupted`, + * `notifyAudioOutputInterrupted` on native ADM thread and make sure + * aforementioned is completed before `dispatchSync` returns. Could be useful + * when implementation of `RTCAudioDevice` tie mutation to underlying audio + * objects (AVAudioEngine, AudioUnit, etc) to own thread to satisfy requirement + * that native ADM audio parameters must be kept in sync with current audio + * parameters before audio is actually played or recorded. + */ +- (void)dispatchSync:(dispatch_block_t)block; + +@end + +/** + * Protocol to abstract platform specific ways to implement playback and + * recording. + * + * NOTE: All the members of protocol are called by native ADM from the same + * thread between calls to `initializeWithDelegate` and `terminate`. NOTE: + * Implementation is fully responsible for configuring application's + * AVAudioSession. An example implementation of RTCAudioDevice: + * https://github.com/mstyura/RTCAudioDevice + * TODO(yura.yaroshevich): Implement custom RTCAudioDevice for AppRTCMobile demo + * app. + */ +RTC_OBJC_EXPORT @protocol RTC_OBJC_TYPE +(RTCAudioDevice) + + /** + * Indicates current sample rate of audio recording. Changes to this + * property must be notified back to native ADM via + * `-[RTCAudioDeviceDelegate notifyAudioParametersChange]`. + */ + @property(readonly) double deviceInputSampleRate; + +/** + * Indicates current size of record buffer. Changes to this property + * must be notified back to native ADM via `-[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) NSTimeInterval inputIOBufferDuration; + +/** + * Indicates current number of recorded audio channels. Changes to this property + * must be notified back to native ADM via `-[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) NSInteger inputNumberOfChannels; + +/** + * Indicates current input latency + */ +@property(readonly) NSTimeInterval inputLatency; + +/** + * Indicates current sample rate of audio playback. Changes to this property + * must be notified back to native ADM via `-[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) double deviceOutputSampleRate; + +/** + * Indicates current size of playback buffer. Changes to this property + * must be notified back to native ADM via `-[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) NSTimeInterval outputIOBufferDuration; + +/** + * Indicates current number of playback audio channels. Changes to this property + * must be notified back to WebRTC via `[RTCAudioDeviceDelegate + * notifyAudioParametersChange]`. + */ +@property(readonly) NSInteger outputNumberOfChannels; + +/** + * Indicates current output latency + */ +@property(readonly) NSTimeInterval outputLatency; + +/** + * Indicates if invocation of `initializeWithDelegate` required before usage of + * RTCAudioDevice. YES indicates that `initializeWithDelegate` was called + * earlier without subsequent call to `terminate`. NO indicates that either + * `initializeWithDelegate` not called or `terminate` called. + */ +@property(readonly) BOOL isInitialized; + +/** + * Initializes RTCAudioDevice with RTCAudioDeviceDelegate. + * Implementation must return YES if RTCAudioDevice initialized successfully and + * NO otherwise. + */ +- (BOOL)initializeWithDelegate: + (id)delegate; + +/** + * De-initializes RTCAudioDevice. Implementation should forget about `delegate` + * provided in `initializeWithDelegate`. + */ +- (BOOL)terminateDevice; + +/** + * Property to indicate if `initializePlayout` call required before invocation + * of `startPlayout`. YES indicates that `initializePlayout` was successfully + * invoked earlier or not necessary, NO indicates that `initializePlayout` + * invocation required. + */ +@property(readonly) BOOL isPlayoutInitialized; + +/** + * Prepares RTCAudioDevice to play audio. + * Called by native ADM before invocation of `startPlayout`. + * Implementation is expected to return YES in case of successful playout + * initialization and NO otherwise. + */ +- (BOOL)initializePlayout; + +/** + * Property to indicate if RTCAudioDevice should be playing according to + * earlier calls of `startPlayout` and `stopPlayout`. + */ +@property(readonly) BOOL isPlaying; + +/** + * Method is called when native ADM wants to play audio. + * Implementation is expected to return YES if playback start request + * successfully handled and NO otherwise. + */ +- (BOOL)startPlayout; + +/** + * Method is called when native ADM no longer needs to play audio. + * Implementation is expected to return YES if playback stop request + * successfully handled and NO otherwise. + */ +- (BOOL)stopPlayout; + +/** + * Property to indicate if `initializeRecording` call required before usage of + * `startRecording`. YES indicates that `initializeRecording` was successfully + * invoked earlier or not necessary, NO indicates that `initializeRecording` + * invocation required. + */ +@property(readonly) BOOL isRecordingInitialized; + +/** + * Prepares RTCAudioDevice to record audio. + * Called by native ADM before invocation of `startRecording`. + * Implementation may use this method to prepare resources required to record + * audio. Implementation is expected to return YES in case of successful record + * initialization and NO otherwise. + */ +- (BOOL)initializeRecording; + +/** + * Property to indicate if RTCAudioDevice should record audio according to + * earlier calls to `startRecording` and `stopRecording`. + */ +@property(readonly) BOOL isRecording; + +/** + * Method is called when native ADM wants to record audio. + * Implementation is expected to return YES if recording start request + * successfully handled and NO otherwise. + */ +- (BOOL)startRecording; + +/** + * Method is called when native ADM no longer needs to record audio. + * Implementation is expected to return YES if recording stop request + * successfully handled and NO otherwise. + */ +- (BOOL)stopRecording; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSession.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSession.h new file mode 100644 index 00000000..08ecabfa --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSession.h @@ -0,0 +1,289 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const kRTCAudioSessionErrorDomain; +/** Method that requires lock was called without lock. */ +extern NSInteger const kRTCAudioSessionErrorLockRequired; +/** Unknown configuration error occurred. */ +extern NSInteger const kRTCAudioSessionErrorConfiguration; + +@class RTC_OBJC_TYPE(RTCAudioSession); +@class RTC_OBJC_TYPE(RTCAudioSessionConfiguration); + +// Surfaces AVAudioSession events. WebRTC will listen directly for notifications +// from AVAudioSession and handle them before calling these delegate methods, +// at which point applications can perform additional processing if required. +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCAudioSessionDelegate) + + @optional +/** Called on a system notification thread when AVAudioSession starts an + * interruption event. + */ +- (void)audioSessionDidBeginInterruption: + (RTC_OBJC_TYPE(RTCAudioSession) *)session; + +/** Called on a system notification thread when AVAudioSession ends an + * interruption event. + */ +- (void)audioSessionDidEndInterruption:(RTC_OBJC_TYPE(RTCAudioSession) *)session + shouldResumeSession:(BOOL)shouldResumeSession; + +/** Called on a system notification thread when AVAudioSession changes the + * route. + */ +- (void)audioSessionDidChangeRoute:(RTC_OBJC_TYPE(RTCAudioSession) *)session + reason:(AVAudioSessionRouteChangeReason)reason + previousRoute: + (AVAudioSessionRouteDescription *)previousRoute; + +/** Called on a system notification thread when AVAudioSession media server + * terminates. + */ +- (void)audioSessionMediaServerTerminated: + (RTC_OBJC_TYPE(RTCAudioSession) *)session; + +/** Called on a system notification thread when AVAudioSession media server + * restarts. + */ +- (void)audioSessionMediaServerReset:(RTC_OBJC_TYPE(RTCAudioSession) *)session; + +// TODO(tkchin): Maybe handle SilenceSecondaryAudioHintNotification. + +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)session + didChangeCanPlayOrRecord:(BOOL)canPlayOrRecord; + +/** Called on a WebRTC thread when the audio device is notified to begin + * playback or recording. + */ +- (void)audioSessionDidStartPlayOrRecord: + (RTC_OBJC_TYPE(RTCAudioSession) *)session; + +/** Called on a WebRTC thread when the audio device is notified to stop + * playback or recording. + */ +- (void)audioSessionDidStopPlayOrRecord: + (RTC_OBJC_TYPE(RTCAudioSession) *)session; + +/** Called when the AVAudioSession output volume value changes. */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + didChangeOutputVolume:(float)outputVolume; + +/** Called when the audio device detects a playout glitch. The argument is the + * number of glitches detected so far in the current audio playout session. + */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + didDetectPlayoutGlitch:(int64_t)totalNumberOfGlitches; + +/** Called when the audio session is about to change the active state. + */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + willSetActive:(BOOL)active; + +/** Called after the audio session sucessfully changed the active state. + */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + didSetActive:(BOOL)active; + +/** Called after the audio session failed to change the active state. + */ +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + failedToSetActive:(BOOL)active + error:(NSError *)error; + +- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession + audioUnitStartFailedWithError:(NSError *)error; + +@end + +/** This is a protocol used to inform RTCAudioSession when the audio session + * activation state has changed outside of RTCAudioSession. The current known + * use case of this is when CallKit activates the audio session for the + * application + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCAudioSessionActivationDelegate) + + /** Called when the audio session is activated outside of the app by iOS. */ + - (void)audioSessionDidActivate : (AVAudioSession *)session; + +/** Called when the audio session is deactivated outside of the app by iOS. */ +- (void)audioSessionDidDeactivate:(AVAudioSession *)session; + +@end + +/** Proxy class for AVAudioSession that adds a locking mechanism similar to + * AVCaptureDevice. This is used to that interleaving configurations between + * WebRTC and the application layer are avoided. + * + * RTCAudioSession also coordinates activation so that the audio session is + * activated only once. See `setActive:error:`. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCAudioSession) : NSObject + +/** Convenience property to access the AVAudioSession singleton. Callers should + * not call setters on AVAudioSession directly, but other method invocations + * are fine. + */ +@property(nonatomic, readonly) AVAudioSession *session; + +/** Our best guess at whether the session is active based on results of calls to + * AVAudioSession. + */ +@property(nonatomic, readonly) BOOL isActive; + +/** If YES, WebRTC will not initialize the audio unit automatically when an + * audio track is ready for playout or recording. Instead, applications should + * call setIsAudioEnabled. If NO, WebRTC will initialize the audio unit + * as soon as an audio track is ready for playout or recording. + */ +@property(nonatomic, assign) BOOL useManualAudio; + +/** This property is only effective if useManualAudio is YES. + * Represents permission for WebRTC to initialize the VoIP audio unit. + * When set to NO, if the VoIP audio unit used by WebRTC is active, it will be + * stopped and uninitialized. This will stop incoming and outgoing audio. + * When set to YES, WebRTC will initialize and start the audio unit when it is + * needed (e.g. due to establishing an audio connection). + * This property was introduced to work around an issue where if an AVPlayer is + * playing audio while the VoIP audio unit is initialized, its audio would be + * either cut off completely or played at a reduced volume. By preventing + * the audio unit from being initialized until after the audio has completed, + * we are able to prevent the abrupt cutoff. + */ +@property(nonatomic, assign) BOOL isAudioEnabled; + +// Proxy properties. +@property(readonly) NSString *category; +@property(readonly) AVAudioSessionCategoryOptions categoryOptions; +@property(readonly) NSString *mode; +@property(readonly) BOOL secondaryAudioShouldBeSilencedHint; +@property(readonly) AVAudioSessionRouteDescription *currentRoute; +@property(readonly) NSInteger maximumInputNumberOfChannels; +@property(readonly) NSInteger maximumOutputNumberOfChannels; +@property(readonly) float inputGain; +@property(readonly) BOOL inputGainSettable; +@property(readonly) BOOL inputAvailable; +@property(readonly, nullable) + NSArray *inputDataSources; +@property(readonly, nullable) + AVAudioSessionDataSourceDescription *inputDataSource; +@property(readonly, nullable) + NSArray *outputDataSources; +@property(readonly, nullable) + AVAudioSessionDataSourceDescription *outputDataSource; +@property(readonly) double sampleRate; +@property(readonly) double preferredSampleRate; +@property(readonly) NSInteger inputNumberOfChannels; +@property(readonly) NSInteger outputNumberOfChannels; +@property(readonly) float outputVolume; +@property(readonly) NSTimeInterval inputLatency; +@property(readonly) NSTimeInterval outputLatency; +@property(readonly) NSTimeInterval IOBufferDuration; +@property(readonly) NSTimeInterval preferredIOBufferDuration; + +/** + When YES, calls to -setConfiguration:error: and -setConfiguration:active:error: + ignore errors in configuring the audio session's "preferred" attributes (e.g. + preferredInputNumberOfChannels). Typically, configurations to preferred + attributes are optimizations, and ignoring this type of configuration error + allows code flow to continue along the happy path when these optimization are + not available. The default value of this property is NO. + */ +@property(nonatomic) BOOL ignoresPreferredAttributeConfigurationErrors; + +/** Default constructor. */ ++ (instancetype)sharedInstance; +- (instancetype)init NS_UNAVAILABLE; + +/** Adds a delegate, which is held weakly. */ +- (void)addDelegate:(id)delegate; +/** Removes an added delegate. */ +- (void)removeDelegate:(id)delegate; + +/** Request exclusive access to the audio session for configuration. This call + * will block if the lock is held by another object. + */ +- (void)lockForConfiguration; +/** Relinquishes exclusive access to the audio session. */ +- (void)unlockForConfiguration; + +/** If `active`, activates the audio session if it isn't already active. + * Successful calls must be balanced with a setActive:NO when activation is no + * longer required. If not `active`, deactivates the audio session if one is + * active and this is the last balanced call. When deactivating, the + * AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation option is passed to + * AVAudioSession. + */ +- (BOOL)setActive:(BOOL)active error:(NSError **)outError; + +// The following methods are proxies for the associated methods on +// AVAudioSession. `lockForConfiguration` must be called before using them +// otherwise they will fail with kRTCAudioSessionErrorLockRequired. + +- (BOOL)setCategory:(AVAudioSessionCategory)category + mode:(AVAudioSessionMode)mode + options:(AVAudioSessionCategoryOptions)options + error:(NSError **)outError; +- (BOOL)setCategory:(AVAudioSessionCategory)category + withOptions:(AVAudioSessionCategoryOptions)options + error:(NSError **)outError; +- (BOOL)setMode:(AVAudioSessionMode)mode error:(NSError **)outError; +- (BOOL)setInputGain:(float)gain error:(NSError **)outError; +- (BOOL)setPreferredSampleRate:(double)sampleRate error:(NSError **)outError; +- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration + error:(NSError **)outError; +- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count + error:(NSError **)outError; +- (BOOL)setPreferredOutputNumberOfChannels:(NSInteger)count + error:(NSError **)outError; +- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride + error:(NSError **)outError; +- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort + error:(NSError **)outError; +- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource + error:(NSError **)outError; +- (BOOL)setOutputDataSource:(AVAudioSessionDataSourceDescription *)dataSource + error:(NSError **)outError; +@end + +@interface RTC_OBJC_TYPE (RTCAudioSession) +(Configuration) + + /** Applies the configuration to the current session. Attempts to set all + * properties even if previous ones fail. Only the last error will be + * returned. + * `lockForConfiguration` must be called first. + */ + - (BOOL)setConfiguration + : (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration error + : (NSError **)outError; + +/** Convenience method that calls both setConfiguration and setActive. + * `lockForConfiguration` must be called first. + */ +- (BOOL)setConfiguration: + (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration + active:(BOOL)active + error:(NSError **)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSessionConfiguration.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSessionConfiguration.h new file mode 100644 index 00000000..b937f160 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSessionConfiguration.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_EXTERN const int kRTCAudioSessionPreferredNumberOfChannels; +RTC_EXTERN const double kRTCAudioSessionHighPerformanceSampleRate; +RTC_EXTERN const double kRTCAudioSessionHighPerformanceIOBufferDuration; + +// Struct to hold configuration values. +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCAudioSessionConfiguration) : NSObject + +@property(nonatomic, strong) NSString *category; +@property(nonatomic, assign) AVAudioSessionCategoryOptions categoryOptions; +@property(nonatomic, strong) NSString *mode; +@property(nonatomic, assign) double sampleRate; +@property(nonatomic, assign) NSTimeInterval ioBufferDuration; +@property(nonatomic, assign) NSInteger inputNumberOfChannels; +@property(nonatomic, assign) NSInteger outputNumberOfChannels; + +/** Initializes configuration to defaults. */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** Returns the current configuration of the audio session. */ ++ (instancetype)currentConfiguration; +/** Returns the configuration that WebRTC needs. */ ++ (instancetype)webRTCConfiguration; +/** Provide a way to override the default configuration. */ ++ (void)setWebRTCConfiguration: + (RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSource.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSource.h new file mode 100644 index 00000000..784864a7 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioSource.h @@ -0,0 +1,32 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCAudioSource) : RTC_OBJC_TYPE(RTCMediaSource) + +- (instancetype)init NS_UNAVAILABLE; + +// Sets the volume for the RTCMediaSource. `volume` is a gain value in the range +// [0, 10]. +// Temporary fix to be able to modify volume of remote audio tracks. +// TODO(kthelgason): Property stays here temporarily until a proper volume-api +// is available on the surface exposed by webrtc. +@property(nonatomic, assign) double volume; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioTrack.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioTrack.h new file mode 100644 index 00000000..3c6d1dcf --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCAudioTrack.h @@ -0,0 +1,28 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCAudioSource); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCAudioTrack) : RTC_OBJC_TYPE(RTCMediaStreamTrack) + +- (instancetype)init NS_UNAVAILABLE; + +/** The audio source for this audio track. */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCAudioSource) * source; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCVPixelBuffer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCVPixelBuffer.h new file mode 100644 index 00000000..dd112b4e --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCVPixelBuffer.h @@ -0,0 +1,52 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** RTCVideoFrameBuffer containing a CVPixelBufferRef */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCVPixelBuffer) : NSObject + +@property(nonatomic, readonly) CVPixelBufferRef pixelBuffer; +@property(nonatomic, readonly) int cropX; +@property(nonatomic, readonly) int cropY; +@property(nonatomic, readonly) int cropWidth; +@property(nonatomic, readonly) int cropHeight; + ++ (NSSet *)supportedPixelFormats; + +- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer; +- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer + adaptedWidth:(int)adaptedWidth + adaptedHeight:(int)adaptedHeight + cropWidth:(int)cropWidth + cropHeight:(int)cropHeight + cropX:(int)cropX + cropY:(int)cropY; + +- (BOOL)requiresCropping; +- (BOOL)requiresScalingToWidth:(int)width height:(int)height; +- (int)bufferSizeForCroppingAndScalingToWidth:(int)width height:(int)height; + +/** The minimum size of the `tmpBuffer` must be the number of bytes returned + * from the bufferSizeForCroppingAndScalingToWidth:height: method. If that size + * is 0, the `tmpBuffer` may be nil. + */ +- (BOOL)cropAndScaleTo:(CVPixelBufferRef)outputPixelBuffer + withTempBuffer:(nullable uint8_t *)tmpBuffer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCallbackLogger.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCallbackLogger.h new file mode 100644 index 00000000..7e2745b6 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCallbackLogger.h @@ -0,0 +1,41 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^RTCCallbackLoggerMessageHandler)(NSString *message); +typedef void (^RTCCallbackLoggerMessageAndSeverityHandler)( + NSString *message, RTCLoggingSeverity severity); + +// This class intercepts WebRTC logs and forwards them to a registered block. +// This class is not threadsafe. +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCallbackLogger) : NSObject + +// The severity level to capture. The default is kRTCLoggingSeverityInfo. +@property(nonatomic, assign) RTCLoggingSeverity severity; + +// The callback handler will be called on the same thread that does the +// logging, so if the logging callback can be slow it may be a good idea +// to implement dispatching to some other queue. +- (void)start:(nullable RTCCallbackLoggerMessageHandler)handler; +- (void)startWithMessageAndSeverityHandler: + (nullable RTCCallbackLoggerMessageAndSeverityHandler)handler; + +- (void)stop; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCameraPreviewView.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCameraPreviewView.h new file mode 100644 index 00000000..710f2e79 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCameraPreviewView.h @@ -0,0 +1,30 @@ +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +@class AVCaptureSession; + +/** RTCCameraPreviewView is a view that renders local video from an + * AVCaptureSession. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCameraPreviewView) : UIView + +/** The capture session being rendered in the view. Capture session + * is assigned to AVCaptureVideoPreviewLayer async in the same + * queue that the AVCaptureSession is started/stopped. + */ +@property(nonatomic, strong) AVCaptureSession* captureSession; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCameraVideoCapturer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCameraVideoCapturer.h new file mode 100644 index 00000000..de69c5d2 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCameraVideoCapturer.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// Camera capture that implements RTCVideoCapturer. Delivers frames to a +// RTCVideoCapturerDelegate (usually RTCVideoSource). +NS_EXTENSION_UNAVAILABLE_IOS("Camera not available in app extensions.") +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCameraVideoCapturer) : RTC_OBJC_TYPE(RTCVideoCapturer) + +// Capture session that is used for capturing. Valid from initialization to dealloc. +@property(readonly, nonatomic) AVCaptureSession *captureSession; + +// Returns list of available capture devices that support video capture. ++ (NSArray *)captureDevices; +// Returns list of formats that are supported by this class for this device. ++ (NSArray *)supportedFormatsForDevice: + (AVCaptureDevice *)device; + +// Returns the most efficient supported output pixel format for this capturer. +- (FourCharCode)preferredOutputPixelFormat; + +// Starts the capture session asynchronously and notifies callback on +// completion. The device will capture video in the format given in the `format` +// parameter. If the pixel format in `format` is supported by the WebRTC +// pipeline, the same pixel format will be used for the output. Otherwise, the +// format returned by `preferredOutputPixelFormat` will be used. +- (void)startCaptureWithDevice:(AVCaptureDevice *)device + format:(AVCaptureDeviceFormat *)format + fps:(NSInteger)fps + completionHandler: + (nullable void (^)(NSError *_Nullable))completionHandler; +// Stops the capture session asynchronously and notifies callback on completion. +- (void)stopCaptureWithCompletionHandler: + (nullable void (^)(void))completionHandler; + +// Starts the capture session asynchronously. +- (void)startCaptureWithDevice:(AVCaptureDevice *)device + format:(AVCaptureDeviceFormat *)format + fps:(NSInteger)fps; +// Stops the capture session asynchronously. +- (void)stopCapture; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCertificate.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCertificate.h new file mode 100644 index 00000000..e300febb --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCertificate.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCertificate) : NSObject + +/** Private key in PEM. */ +@property(nonatomic, readonly, copy) NSString *private_key; + +/** Public key in an x509 cert encoded in PEM. */ +@property(nonatomic, readonly, copy) NSString *certificate; + +/** + * Initialize an RTCCertificate with PEM strings for private_key and + * certificate. + */ +- (instancetype)initWithPrivateKey:(NSString *)private_key + certificate:(NSString *)certificate + NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +/** Generate a new certificate for 're' use. + * + * Optional dictionary of parameters. Defaults to KeyType ECDSA if none are + * provided. + * - name: "ECDSA" or "RSASSA-PKCS1-v1_5" + */ ++ (nullable RTC_OBJC_TYPE(RTCCertificate) *)generateCertificateWithParams: + (NSDictionary *)params; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCodecSpecificInfo.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCodecSpecificInfo.h new file mode 100644 index 00000000..39f7c183 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCodecSpecificInfo.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Implement this protocol to pass codec specific info from the encoder. + * Corresponds to webrtc::CodecSpecificInfo. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCCodecSpecificInfo) @end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCodecSpecificInfoH264.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCodecSpecificInfoH264.h new file mode 100644 index 00000000..b6f34a54 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCodecSpecificInfoH264.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +/** Class for H264 specific config. */ +typedef NS_ENUM(NSUInteger, RTCH264PacketizationMode) { + RTCH264PacketizationModeNonInterleaved = + 0, // Mode 1 - STAP-A, FU-A is allowed + RTCH264PacketizationModeSingleNalUnit // Mode 0 - only single NALU allowed +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCodecSpecificInfoH264) : NSObject + +@property(nonatomic, assign) RTCH264PacketizationMode packetizationMode; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCConfiguration.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCConfiguration.h new file mode 100644 index 00000000..21f12fcd --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCConfiguration.h @@ -0,0 +1,268 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +@class RTC_OBJC_TYPE(RTCIceServer); + +/** + * Represents the ice transport policy. This exposes the same states in C++, + * which include one more state than what exists in the W3C spec. + */ +typedef NS_ENUM(NSInteger, RTCIceTransportPolicy) { + RTCIceTransportPolicyNone, + RTCIceTransportPolicyRelay, + RTCIceTransportPolicyNoHost, + RTCIceTransportPolicyAll +}; + +/** Represents the bundle policy. */ +typedef NS_ENUM(NSInteger, RTCBundlePolicy) { + RTCBundlePolicyBalanced, + RTCBundlePolicyMaxCompat, + RTCBundlePolicyMaxBundle +}; + +/** Represents the rtcp mux policy. */ +typedef NS_ENUM(NSInteger, RTCRtcpMuxPolicy) { + RTCRtcpMuxPolicyNegotiate, + RTCRtcpMuxPolicyRequire +}; + +/** Represents the tcp candidate policy. */ +typedef NS_ENUM(NSInteger, RTCTcpCandidatePolicy) { + RTCTcpCandidatePolicyEnabled, + RTCTcpCandidatePolicyDisabled +}; + +/** Represents the candidate network policy. */ +typedef NS_ENUM(NSInteger, RTCCandidateNetworkPolicy) { + RTCCandidateNetworkPolicyAll, + RTCCandidateNetworkPolicyLowCost +}; + +/** Represents the continual gathering policy. */ +typedef NS_ENUM(NSInteger, RTCContinualGatheringPolicy) { + RTCContinualGatheringPolicyGatherOnce, + RTCContinualGatheringPolicyGatherContinually +}; + +/** Represents the encryption key type. */ +typedef NS_ENUM(NSInteger, RTCEncryptionKeyType) { + RTCEncryptionKeyTypeRSA, + RTCEncryptionKeyTypeECDSA, +}; + +/** Represents the chosen SDP semantics for the RTCPeerConnection. */ +typedef NS_ENUM(NSInteger, RTCSdpSemantics) { + // TODO(https://crbug.com/webrtc/13528): Remove support for Plan B. + RTCSdpSemanticsPlanB, + RTCSdpSemanticsUnifiedPlan, +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCConfiguration) : NSObject + +/** If true, allows DSCP codes to be set on outgoing packets, configured using + * networkPriority field of RTCRtpEncodingParameters. Defaults to false. + */ +@property(nonatomic, assign) BOOL enableDscp; + +/** An array of Ice Servers available to be used by ICE. */ +@property(nonatomic, copy) NSArray *iceServers; + +/** An RTCCertificate for 're' use. */ +@property(nonatomic, nullable) RTC_OBJC_TYPE(RTCCertificate) * certificate; + +/** Which candidates the ICE agent is allowed to use. The W3C calls it + * `iceTransportPolicy`, while in C++ it is called `type`. */ +@property(nonatomic, assign) RTCIceTransportPolicy iceTransportPolicy; + +/** The media-bundling policy to use when gathering ICE candidates. */ +@property(nonatomic, assign) RTCBundlePolicy bundlePolicy; + +/** The rtcp-mux policy to use when gathering ICE candidates. */ +@property(nonatomic, assign) RTCRtcpMuxPolicy rtcpMuxPolicy; +@property(nonatomic, assign) RTCTcpCandidatePolicy tcpCandidatePolicy; +@property(nonatomic, assign) RTCCandidateNetworkPolicy candidateNetworkPolicy; +@property(nonatomic, assign) + RTCContinualGatheringPolicy continualGatheringPolicy; + +/** If set to YES, don't gather IPv6 ICE candidates on Wi-Fi. + * Only intended to be used on specific devices. Certain phones disable IPv6 + * when the screen is turned off and it would be better to just disable the + * IPv6 ICE candidates on Wi-Fi in those cases. + * Default is NO. + */ +@property(nonatomic, assign) BOOL disableIPV6OnWiFi; + +/** By default, the PeerConnection will use a limited number of IPv6 network + * interfaces, in order to avoid too many ICE candidate pairs being created + * and delaying ICE completion. + * + * Can be set to INT_MAX to effectively disable the limit. + */ +@property(nonatomic, assign) int maxIPv6Networks; + +/** Exclude link-local network interfaces + * from considertaion for gathering ICE candidates. + * Defaults to NO. + */ +@property(nonatomic, assign) BOOL disableLinkLocalNetworks; + +@property(nonatomic, assign) int audioJitterBufferMaxPackets; +@property(nonatomic, assign) BOOL audioJitterBufferFastAccelerate; +@property(nonatomic, assign) int iceConnectionReceivingTimeout; +@property(nonatomic, assign) int iceBackupCandidatePairPingInterval; + +/** Key type used to generate SSL identity. Default is ECDSA. */ +@property(nonatomic, assign) RTCEncryptionKeyType keyType; + +/** ICE candidate pool size as defined in JSEP. Default is 0. */ +@property(nonatomic, assign) int iceCandidatePoolSize; + +/** Prune turn ports on the same network to the same turn server. + * Default is NO. + */ +@property(nonatomic, assign) BOOL shouldPruneTurnPorts; + +/** If set to YES, this means the ICE transport should presume TURN-to-TURN + * candidate pairs will succeed, even before a binding response is received. + */ +@property(nonatomic, assign) BOOL shouldPresumeWritableWhenFullyRelayed; + +/* This flag is only effective when `continualGatheringPolicy` is + * RTCContinualGatheringPolicyGatherContinually. + * + * If YES, after the ICE transport type is changed such that new types of + * ICE candidates are allowed by the new transport type, e.g. from + * RTCIceTransportPolicyRelay to RTCIceTransportPolicyAll, candidates that + * have been gathered by the ICE transport but not matching the previous + * transport type and as a result not observed by PeerConnectionDelegateAdapter, + * will be surfaced to the delegate. + */ +@property(nonatomic, assign) + BOOL shouldSurfaceIceCandidatesOnIceTransportTypeChanged; + +/** If set to non-nil, controls the minimal interval between consecutive ICE + * check packets. + */ +@property(nonatomic, copy, nullable) NSNumber *iceCheckMinInterval; + +/** + * Configure the SDP semantics used by this PeerConnection. By default, this + * is RTCSdpSemanticsUnifiedPlan which is compliant to the WebRTC 1.0 + * specification. It is possible to overrwite this to the deprecated + * RTCSdpSemanticsPlanB SDP format, but note that RTCSdpSemanticsPlanB will be + * deleted at some future date, see https://crbug.com/webrtc/13528. + * + * RTCSdpSemanticsUnifiedPlan will cause RTCPeerConnection to create offers and + * answers with multiple m= sections where each m= section maps to one + * RTCRtpSender and one RTCRtpReceiver (an RTCRtpTransceiver), either both audio + * or both video. This will also cause RTCPeerConnection to ignore all but the + * first a=ssrc lines that form a Plan B stream. + * + * RTCSdpSemanticsPlanB will cause RTCPeerConnection to create offers and + * answers with at most one audio and one video m= section with multiple + * RTCRtpSenders and RTCRtpReceivers specified as multiple a=ssrc lines within + * the section. This will also cause RTCPeerConnection to ignore all but the + * first m= section of the same media type. + */ +@property(nonatomic, assign) RTCSdpSemantics sdpSemantics; + +/** Actively reset the SRTP parameters when the DTLS transports underneath are + * changed after offer/answer negotiation. This is only intended to be a + * workaround for crbug.com/835958 + */ +@property(nonatomic, assign) BOOL activeResetSrtpParams; + +/** + * Defines advanced optional cryptographic settings related to SRTP and + * frame encryption for native WebRTC. Setting this will overwrite any + * options set through the PeerConnectionFactory (which is deprecated). + */ +@property(nonatomic, nullable) RTC_OBJC_TYPE(RTCCryptoOptions) * cryptoOptions; + +/** + * An optional string that will be attached to the TURN_ALLOCATE_REQUEST which + * which can be used to correlate client logs with backend logs. + */ +@property(nonatomic, nullable, copy) NSString *turnLoggingId; + +/** + * Time interval between audio RTCP reports. + */ +@property(nonatomic, assign) int rtcpAudioReportIntervalMs; + +/** + * Time interval between video RTCP reports. + */ +@property(nonatomic, assign) int rtcpVideoReportIntervalMs; + +/** + * Allow implicit rollback of local description when remote description + * conflicts with local description. + * See: https://w3c.github.io/webrtc-pc/#dom-peerconnection-setremotedescription + */ +@property(nonatomic, assign) BOOL enableImplicitRollback; + +/** + * Control if "a=extmap-allow-mixed" is included in the offer. + * See: https://www.chromestatus.com/feature/6269234631933952 + */ +@property(nonatomic, assign) BOOL offerExtmapAllowMixed; + +/** + * Defines the interval applied to ALL candidate pairs + * when ICE is strongly connected, and it overrides the + * default value of this interval in the ICE implementation; + */ +@property(nonatomic, copy, nullable) + NSNumber *iceCheckIntervalStrongConnectivity; + +/** + * Defines the counterpart for ALL pairs when ICE is + * weakly connected, and it overrides the default value of + * this interval in the ICE implementation + */ +@property(nonatomic, copy, nullable) NSNumber *iceCheckIntervalWeakConnectivity; + +/** + * The min time period for which a candidate pair must wait for response to + * connectivity checks before it becomes unwritable. This parameter + * overrides the default value in the ICE implementation if set. + */ +@property(nonatomic, copy, nullable) NSNumber *iceUnwritableTimeout; + +/** + * The min number of connectivity checks that a candidate pair must sent + * without receiving response before it becomes unwritable. This parameter + * overrides the default value in the ICE implementation if set. + */ +@property(nonatomic, copy, nullable) NSNumber *iceUnwritableMinChecks; + +/** + * The min time period for which a candidate pair must wait for response to + * connectivity checks it becomes inactive. This parameter overrides the + * default value in the ICE implementation if set. + */ +@property(nonatomic, copy, nullable) NSNumber *iceInactiveTimeout; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCryptoOptions.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCryptoOptions.h new file mode 100644 index 00000000..a4c85d78 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCCryptoOptions.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Objective-C bindings for webrtc::CryptoOptions. This API had to be flattened + * as Objective-C doesn't support nested structures. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCCryptoOptions) : NSObject + +/** + * Enable GCM crypto suites from RFC 7714 for SRTP. GCM will only be used + * if both sides enable it + */ +@property(nonatomic, assign) BOOL srtpEnableGcmCryptoSuites; +/** + * If set to true, the (potentially insecure) crypto cipher + * kSrtpAes128CmSha1_32 will be included in the list of supported ciphers + * during negotiation. It will only be used if both peers support it and no + * other ciphers get preferred. + */ +@property(nonatomic, assign) BOOL srtpEnableAes128Sha1_32CryptoCipher; +/** + * If set to true, encrypted RTP header extensions as defined in RFC 6904 + * will be negotiated. They will only be used if both peers support them. + */ +@property(nonatomic, assign) BOOL srtpEnableEncryptedRtpHeaderExtensions; + +/** + * If set all RtpSenders must have an FrameEncryptor attached to them before + * they are allowed to send packets. All RtpReceivers must have a + * FrameDecryptor attached to them before they are able to receive packets. + */ +@property(nonatomic, assign) BOOL sframeRequireFrameEncryption; + +/** + * Initializes CryptoOptions with all possible options set explicitly. This + * is done when converting from a native RTCConfiguration.crypto_options. + */ +- (instancetype) + initWithSrtpEnableGcmCryptoSuites:(BOOL)srtpEnableGcmCryptoSuites + srtpEnableAes128Sha1_32CryptoCipher: + (BOOL)srtpEnableAes128Sha1_32CryptoCipher + srtpEnableEncryptedRtpHeaderExtensions: + (BOOL)srtpEnableEncryptedRtpHeaderExtensions + sframeRequireFrameEncryption:(BOOL)sframeRequireFrameEncryption + NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDataChannel.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDataChannel.h new file mode 100644 index 00000000..c5c2c9a1 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDataChannel.h @@ -0,0 +1,134 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDataBuffer) : NSObject + +/** NSData representation of the underlying buffer. */ +@property(nonatomic, readonly) NSData *data; + +/** Indicates whether `data` contains UTF-8 or binary data. */ +@property(nonatomic, readonly) BOOL isBinary; + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initialize an RTCDataBuffer from NSData. `isBinary` indicates whether `data` + * contains UTF-8 or binary data. + */ +- (instancetype)initWithData:(NSData *)data isBinary:(BOOL)isBinary; + +@end + +@class RTC_OBJC_TYPE(RTCDataChannel); +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCDataChannelDelegate) + + /** The data channel state changed. */ + - (void)dataChannelDidChangeState + : (RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel; + +/** The data channel successfully received a data buffer. */ +- (void)dataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel + didReceiveMessageWithBuffer:(RTC_OBJC_TYPE(RTCDataBuffer) *)buffer; + +@optional +/** The data channel's `bufferedAmount` changed. */ +- (void)dataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel + didChangeBufferedAmount:(uint64_t)amount; + +@end + +/** Represents the state of the data channel. */ +typedef NS_ENUM(NSInteger, RTCDataChannelState) { + RTCDataChannelStateConnecting, + RTCDataChannelStateOpen, + RTCDataChannelStateClosing, + RTCDataChannelStateClosed, +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDataChannel) : NSObject + +/** + * A label that can be used to distinguish this data channel from other data + * channel objects. + */ +@property(nonatomic, readonly) NSString *label; + +/** Whether the data channel can send messages in unreliable mode. */ +@property(nonatomic, readonly) BOOL isReliable DEPRECATED_ATTRIBUTE; + +/** Returns whether this data channel is ordered or not. */ +@property(nonatomic, readonly) BOOL isOrdered; + +/** Deprecated. Use maxPacketLifeTime. */ +@property(nonatomic, readonly) + NSUInteger maxRetransmitTime DEPRECATED_ATTRIBUTE; + +/** + * The length of the time window (in milliseconds) during which transmissions + * and retransmissions may occur in unreliable mode. + */ +@property(nonatomic, readonly) uint16_t maxPacketLifeTime; + +/** + * The maximum number of retransmissions that are attempted in unreliable mode. + */ +@property(nonatomic, readonly) uint16_t maxRetransmits; + +/** + * The name of the sub-protocol used with this data channel, if any. Otherwise + * this returns an empty string. + */ +@property(nonatomic, readonly) NSString *protocol; + +/** + * Returns whether this data channel was negotiated by the application or not. + */ +@property(nonatomic, readonly) BOOL isNegotiated; + +/** Deprecated. Use channelId. */ +@property(nonatomic, readonly) NSInteger streamId DEPRECATED_ATTRIBUTE; + +/** The identifier for this data channel. */ +@property(nonatomic, readonly) int channelId; + +/** The state of the data channel. */ +@property(nonatomic, readonly) RTCDataChannelState readyState; + +/** + * The number of bytes of application data that have been queued using + * `sendData:` but that have not yet been transmitted to the network. + */ +@property(nonatomic, readonly) uint64_t bufferedAmount; + +/** The delegate for this data channel. */ +@property(nonatomic, weak) id delegate; + +- (instancetype)init NS_UNAVAILABLE; + +/** Closes the data channel. */ +- (void)close; + +/** Attempt to send `data` on this data channel's underlying data transport. */ +- (BOOL)sendData:(RTC_OBJC_TYPE(RTCDataBuffer) *)data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDataChannelConfiguration.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDataChannelConfiguration.h new file mode 100644 index 00000000..b1d8d770 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDataChannelConfiguration.h @@ -0,0 +1,52 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDataChannelConfiguration) : NSObject + +/** Set to YES if ordered delivery is required. */ +@property(nonatomic, assign) BOOL isOrdered; + +/** Deprecated. Use maxPacketLifeTime. */ +@property(nonatomic, assign) NSInteger maxRetransmitTimeMs DEPRECATED_ATTRIBUTE; + +/** + * Max period in milliseconds in which retransmissions will be sent. After this + * time, no more retransmissions will be sent. -1 if unset. + */ +@property(nonatomic, assign) int maxPacketLifeTime; + +/** The max number of retransmissions. -1 if unset. */ +@property(nonatomic, assign) int maxRetransmits; + +/** Set to YES if the channel has been externally negotiated and we do not send + * an in-band signalling in the form of an "open" message. + */ +@property(nonatomic, assign) BOOL isNegotiated; + +/** Deprecated. Use channelId. */ +@property(nonatomic, assign) int streamId DEPRECATED_ATTRIBUTE; + +/** The id of the data channel. */ +@property(nonatomic, assign) int channelId; + +/** Set by the application and opaque to the WebRTC implementation. */ +@property(nonatomic) NSString* protocol; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDefaultVideoDecoderFactory.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDefaultVideoDecoderFactory.h new file mode 100644 index 00000000..88b1d9c8 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDefaultVideoDecoderFactory.h @@ -0,0 +1,26 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This decoder factory include support for all codecs bundled with WebRTC. If + * using custom codecs, create custom implementations of RTCVideoEncoderFactory + * and RTCVideoDecoderFactory. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDefaultVideoDecoderFactory) : NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDefaultVideoEncoderFactory.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDefaultVideoEncoderFactory.h new file mode 100644 index 00000000..6defc80c --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDefaultVideoEncoderFactory.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This encoder factory include support for all codecs bundled with WebRTC. If + * using custom codecs, create custom implementations of RTCVideoEncoderFactory + * and RTCVideoDecoderFactory. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDefaultVideoEncoderFactory) : NSObject + +@property(nonatomic, retain) RTC_OBJC_TYPE(RTCVideoCodecInfo) *preferredCodec; + ++ (NSArray *)supportedCodecs; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDispatcher.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDispatcher.h new file mode 100644 index 00000000..bc44b478 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDispatcher.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSInteger, RTCDispatcherQueueType) { + // Main dispatcher queue. + RTCDispatcherTypeMain, + // Used for starting/stopping AVCaptureSession, and assigning + // capture session to AVCaptureVideoPreviewLayer. + RTCDispatcherTypeCaptureSession, + // Used for operations on AVAudioSession. + RTCDispatcherTypeAudioSession, + // Used for operations on NWPathMonitor. + RTCDispatcherTypeNetworkMonitor, +}; + +/** Dispatcher that asynchronously dispatches blocks to a specific + * shared dispatch queue. + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCDispatcher) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** Dispatch the block asynchronously on the queue for dispatchType. + * @param dispatchType The queue type to dispatch on. + * @param block The block to dispatch asynchronously. + */ ++ (void)dispatchAsyncOnType:(RTCDispatcherQueueType)dispatchType + block:(dispatch_block_t)block; + +/** Returns YES if run on queue for the dispatchType otherwise NO. + * Useful for asserting that a method is run on a correct queue. + */ ++ (BOOL)isOnQueueForType:(RTCDispatcherQueueType)dispatchType; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDtmfSender.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDtmfSender.h new file mode 100644 index 00000000..33d98c57 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCDtmfSender.h @@ -0,0 +1,73 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCDtmfSender) + + /** + * Returns true if this RTCDtmfSender is capable of sending DTMF. Otherwise + * returns false. To be able to send DTMF, the associated RTCRtpSender must + * be able to send packets, and a "telephone-event" codec must be + * negotiated. + */ + @property(nonatomic, readonly) BOOL canInsertDtmf; + +/** + * Queues a task that sends the DTMF tones. The tones parameter is treated + * as a series of characters. The characters 0 through 9, A through D, #, and * + * generate the associated DTMF tones. The characters a to d are equivalent + * to A to D. The character ',' indicates a delay of 2 seconds before + * processing the next character in the tones parameter. + * + * Unrecognized characters are ignored. + * + * @param duration The parameter indicates the duration to use for each + * character passed in the tones parameter. The duration cannot be more + * than 6000 or less than 70 ms. + * + * @param interToneGap The parameter indicates the gap between tones. + * This parameter must be at least 50 ms but should be as short as + * possible. + * + * If InsertDtmf is called on the same object while an existing task for this + * object to generate DTMF is still running, the previous task is canceled. + * Returns true on success and false on failure. + */ +- (BOOL)insertDtmf:(nonnull NSString *)tones + duration:(NSTimeInterval)duration + interToneGap:(NSTimeInterval)interToneGap; + +/** The tones remaining to be played out */ +- (nonnull NSString *)remainingTones; + +/** + * The current tone duration value. This value will be the value last set via + * the insertDtmf method, or the default value of 100 ms if insertDtmf was never + * called. + */ +- (NSTimeInterval)duration; + +/** + * The current value of the between-tone gap. This value will be the value last + * set via the insertDtmf() method, or the default value of 50 ms if + * insertDtmf() was never called. + */ +- (NSTimeInterval)interToneGap; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCEAGLVideoView.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCEAGLVideoView.h new file mode 100644 index 00000000..75cf9aed --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCEAGLVideoView.h @@ -0,0 +1,45 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCEAGLVideoView); + +/** + * RTCEAGLVideoView is an RTCVideoRenderer which renders video frames + * in its bounds using OpenGLES 2.0 or OpenGLES 3.0. + */ +NS_EXTENSION_UNAVAILABLE_IOS("Rendering not available in app extensions.") +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCEAGLVideoView) : UIView + +@property(nonatomic, weak) id delegate; + +- (instancetype)initWithFrame:(CGRect)frame + shader:(id)shader + NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithCoder:(NSCoder *)aDecoder + shader:(id)shader + NS_DESIGNATED_INITIALIZER; + +/** @abstract Wrapped RTCVideoRotation, or nil. + */ +@property(nonatomic, nullable) NSValue *rotationOverride; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCEncodedImage.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCEncodedImage.h new file mode 100644 index 00000000..97f29ed3 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCEncodedImage.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Represents an encoded frame's type. */ +typedef NS_ENUM(NSUInteger, RTCFrameType) { + RTCFrameTypeEmptyFrame = 0, + RTCFrameTypeAudioFrameSpeech = 1, + RTCFrameTypeAudioFrameCN = 2, + RTCFrameTypeVideoFrameKey = 3, + RTCFrameTypeVideoFrameDelta = 4, +}; + +typedef NS_ENUM(NSUInteger, RTCVideoContentType) { + RTCVideoContentTypeUnspecified, + RTCVideoContentTypeScreenshare, +}; + +/** Represents an encoded frame. Corresponds to webrtc::EncodedImage. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCEncodedImage) : NSObject + +@property(nonatomic, strong) NSData *buffer; +@property(nonatomic, assign) int32_t encodedWidth; +@property(nonatomic, assign) int32_t encodedHeight; +@property(nonatomic, assign) uint32_t timeStamp; +@property(nonatomic, assign) int64_t captureTimeMs; +@property(nonatomic, assign) int64_t ntpTimeMs; +@property(nonatomic, assign) uint8_t flags; +@property(nonatomic, assign) int64_t encodeStartMs; +@property(nonatomic, assign) int64_t encodeFinishMs; +@property(nonatomic, assign) RTCFrameType frameType; +@property(nonatomic, assign) RTCVideoRotation rotation; +@property(nonatomic, strong) NSNumber *qp; +@property(nonatomic, assign) RTCVideoContentType contentType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFieldTrials.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFieldTrials.h new file mode 100644 index 00000000..fa27322f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFieldTrials.h @@ -0,0 +1,34 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +/** The only valid value for the following if set is kRTCFieldTrialEnabledValue. + */ +RTC_EXTERN NSString *const kRTCFieldTrialAudioForceABWENoTWCCKey; +RTC_EXTERN NSString *const kRTCFieldTrialFlexFec03AdvertisedKey; +RTC_EXTERN NSString *const kRTCFieldTrialFlexFec03Key; +RTC_EXTERN NSString *const kRTCFieldTrialH264HighProfileKey; +RTC_EXTERN NSString *const kRTCFieldTrialMinimizeResamplingOnMobileKey; +RTC_EXTERN NSString *const kRTCFieldTrialUseNWPathMonitor; + +/** The valid value for field trials above. */ +RTC_EXTERN NSString *const kRTCFieldTrialEnabledValue; + +/** Initialize field trials using a dictionary mapping field trial keys to their + * values. See above for valid keys and values. Must be called before any other + * call into WebRTC. See: webrtc/system_wrappers/include/field_trial.h + */ +// TODO: bugs.webrtc.org/42220378 - Delete after January 1, 2026. +RTC_OBJC_DEPRECATED("Pass field trials when building PeerConnectionFactory") +RTC_EXTERN void RTCInitFieldTrialDictionary( + NSDictionary *fieldTrials); diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFileLogger.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFileLogger.h new file mode 100644 index 00000000..551a895f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFileLogger.h @@ -0,0 +1,76 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSUInteger, RTCFileLoggerSeverity) { + RTCFileLoggerSeverityVerbose, + RTCFileLoggerSeverityInfo, + RTCFileLoggerSeverityWarning, + RTCFileLoggerSeverityError +}; + +typedef NS_ENUM(NSUInteger, RTCFileLoggerRotationType) { + RTCFileLoggerTypeCall, + RTCFileLoggerTypeApp, +}; + +NS_ASSUME_NONNULL_BEGIN + +// This class intercepts WebRTC logs and saves them to a file. The file size +// will not exceed the given maximum bytesize. When the maximum bytesize is +// reached, logs are rotated according to the rotationType specified. +// For kRTCFileLoggerTypeCall, logs from the beginning and the end +// are preserved while the middle section is overwritten instead. +// For kRTCFileLoggerTypeApp, the oldest log is overwritten. +// This class is not threadsafe. +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCFileLogger) : NSObject + +// The severity level to capture. The default is kRTCFileLoggerSeverityInfo. +@property(nonatomic, assign) RTCFileLoggerSeverity severity; + +// The rotation type for this file logger. The default is +// kRTCFileLoggerTypeCall. +@property(nonatomic, readonly) RTCFileLoggerRotationType rotationType; + +// Disables buffering disk writes. Should be set before `start`. Buffering +// is enabled by default for performance. +@property(nonatomic, assign) BOOL shouldDisableBuffering; + +// Default constructor provides default settings for dir path, file size and +// rotation type. +- (instancetype)init; + +// Create file logger with default rotation type. +- (instancetype)initWithDirPath:(NSString *)dirPath + maxFileSize:(NSUInteger)maxFileSize; + +- (instancetype)initWithDirPath:(NSString *)dirPath + maxFileSize:(NSUInteger)maxFileSize + rotationType:(RTCFileLoggerRotationType)rotationType + NS_DESIGNATED_INITIALIZER; + +// Starts writing WebRTC logs to disk if not already started. Overwrites any +// existing file(s). +- (void)start; + +// Stops writing WebRTC logs to disk. This method is also called on dealloc. +- (void)stop; + +// Returns the current contents of the logs, or nil if start has been called +// without a stop. +- (nullable NSData *)logData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFileVideoCapturer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFileVideoCapturer.h new file mode 100644 index 00000000..38f65f81 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCFileVideoCapturer.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Error passing block. + */ +typedef void (^RTCFileVideoCapturerErrorBlock)(NSError *error); + +/** + * Captures buffers from bundled video file. + * + * See @c RTCVideoCapturer for more info on capturers. + */ +RTC_OBJC_EXPORT + +NS_CLASS_AVAILABLE_IOS(10) +@interface RTC_OBJC_TYPE (RTCFileVideoCapturer) : RTC_OBJC_TYPE(RTCVideoCapturer) + +/** + * Starts asynchronous capture of frames from video file. + * + * Capturing is not started if error occurs. Underlying error will be + * relayed in the errorBlock if one is provided. + * Successfully captured video frames will be passed to the delegate. + * + * @param nameOfFile The name of the bundled video file to be read. + * @errorBlock block to be executed upon error. + */ +- (void)startCapturingFromFileNamed:(NSString *)nameOfFile + onError:(__nullable RTCFileVideoCapturerErrorBlock)errorBlock; + +/** + * Immediately stops capture. + */ +- (void)stopCapture; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCH264ProfileLevelId.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCH264ProfileLevelId.h new file mode 100644 index 00000000..67bcae16 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCH264ProfileLevelId.h @@ -0,0 +1,61 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +RTC_EXTERN NSString *const kRTCVideoCodecH264Name; +RTC_EXTERN NSString *const kRTCLevel31ConstrainedHigh; +RTC_EXTERN NSString *const kRTCLevel31ConstrainedBaseline; +RTC_EXTERN NSString *const kRTCMaxSupportedH264ProfileLevelConstrainedHigh; +RTC_EXTERN NSString *const kRTCMaxSupportedH264ProfileLevelConstrainedBaseline; + +/** H264 Profiles and levels. */ +typedef NS_ENUM(NSUInteger, RTCH264Profile) { + RTCH264ProfileConstrainedBaseline, + RTCH264ProfileBaseline, + RTCH264ProfileMain, + RTCH264ProfileConstrainedHigh, + RTCH264ProfileHigh, +}; + +typedef NS_ENUM(NSUInteger, RTCH264Level) { + RTCH264Level1_b = 0, + RTCH264Level1 = 10, + RTCH264Level1_1 = 11, + RTCH264Level1_2 = 12, + RTCH264Level1_3 = 13, + RTCH264Level2 = 20, + RTCH264Level2_1 = 21, + RTCH264Level2_2 = 22, + RTCH264Level3 = 30, + RTCH264Level3_1 = 31, + RTCH264Level3_2 = 32, + RTCH264Level4 = 40, + RTCH264Level4_1 = 41, + RTCH264Level4_2 = 42, + RTCH264Level5 = 50, + RTCH264Level5_1 = 51, + RTCH264Level5_2 = 52 +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCH264ProfileLevelId) : NSObject + +@property(nonatomic, readonly) RTCH264Profile profile; +@property(nonatomic, readonly) RTCH264Level level; +@property(nonatomic, readonly) NSString *hexString; + +- (instancetype)initWithHexString:(NSString *)hexString; +- (instancetype)initWithProfile:(RTCH264Profile)profile + level:(RTCH264Level)level; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCI420Buffer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCI420Buffer.h new file mode 100644 index 00000000..54c32408 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCI420Buffer.h @@ -0,0 +1,22 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Protocol for RTCYUVPlanarBuffers containing I420 data */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCI420Buffer) @end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceCandidate.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceCandidate.h new file mode 100644 index 00000000..23b4fece --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceCandidate.h @@ -0,0 +1,50 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCIceCandidate) : NSObject + +/** + * If present, the identifier of the "media stream identification" for the media + * component this candidate is associated with. + */ +@property(nonatomic, readonly, nullable) NSString *sdpMid; + +/** + * The index (starting at zero) of the media description this candidate is + * associated with in the SDP. + */ +@property(nonatomic, readonly) int sdpMLineIndex; + +/** The SDP string for this candidate. */ +@property(nonatomic, readonly) NSString *sdp; + +/** The URL of the ICE server which this candidate is gathered from. */ +@property(nonatomic, readonly, nullable) NSString *serverUrl; + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initialize an RTCIceCandidate from SDP. + */ +- (instancetype)initWithSdp:(NSString *)sdp + sdpMLineIndex:(int)sdpMLineIndex + sdpMid:(nullable NSString *)sdpMid + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceCandidateErrorEvent.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceCandidateErrorEvent.h new file mode 100644 index 00000000..fb8e853f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceCandidateErrorEvent.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCIceCandidateErrorEvent) : NSObject + +/** The local IP address used to communicate with the STUN or TURN server. */ +@property(nonatomic, readonly) NSString *address; + +/** The port used to communicate with the STUN or TURN server. */ +@property(nonatomic, readonly) int port; + +/** The STUN or TURN URL that identifies the STUN or TURN server for which the + * failure occurred. */ +@property(nonatomic, readonly) NSString *url; + +/** The numeric STUN error code returned by the STUN or TURN server. If no host + * candidate can reach the server, errorCode will be set to the value 701 which + * is outside the STUN error code range. This error is only fired once per + * server URL while in the RTCIceGatheringState of "gathering". */ +@property(nonatomic, readonly) int errorCode; + +/** The STUN reason text returned by the STUN or TURN server. If the server + * could not be reached, errorText will be set to an implementation-specific + * value providing details about the error. */ +@property(nonatomic, readonly) NSString *errorText; + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceServer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceServer.h new file mode 100644 index 00000000..6f6c7eaa --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCIceServer.h @@ -0,0 +1,115 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSUInteger, RTCTlsCertPolicy) { + RTCTlsCertPolicySecure, + RTCTlsCertPolicyInsecureNoCheck +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCIceServer) : NSObject + +/** URI(s) for this server represented as NSStrings. */ +@property(nonatomic, readonly) NSArray *urlStrings; + +/** Username to use if this RTCIceServer object is a TURN server. */ +@property(nonatomic, readonly, nullable) NSString *username; + +/** Credential to use if this RTCIceServer object is a TURN server. */ +@property(nonatomic, readonly, nullable) NSString *credential; + +/** + * TLS certificate policy to use if this RTCIceServer object is a TURN server. + */ +@property(nonatomic, readonly) RTCTlsCertPolicy tlsCertPolicy; + +/** + If the URIs in `urls` only contain IP addresses, this field can be used + to indicate the hostname, which may be necessary for TLS (using the SNI + extension). If `urls` itself contains the hostname, this isn't necessary. + */ +@property(nonatomic, readonly, nullable) NSString *hostname; + +/** List of protocols to be used in the TLS ALPN extension. */ +@property(nonatomic, readonly) NSArray *tlsAlpnProtocols; + +/** + List elliptic curves to be used in the TLS elliptic curves extension. + Only curve names supported by OpenSSL should be used (eg. "P-256","X25519"). + */ +@property(nonatomic, readonly) NSArray *tlsEllipticCurves; + +- (nonnull instancetype)init NS_UNAVAILABLE; + +/** Convenience initializer for a server with no authentication (e.g. STUN). */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, and credentialType. + */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, and TLS cert policy. + */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential + tlsCertPolicy:(RTCTlsCertPolicy)tlsCertPolicy; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, TLS cert policy and hostname. + */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential + tlsCertPolicy:(RTCTlsCertPolicy)tlsCertPolicy + hostname:(nullable NSString *)hostname; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, TLS cert policy, hostname and ALPN protocols. + */ +- (instancetype)initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential + tlsCertPolicy:(RTCTlsCertPolicy)tlsCertPolicy + hostname:(nullable NSString *)hostname + tlsAlpnProtocols:(NSArray *)tlsAlpnProtocols; + +/** + * Initialize an RTCIceServer with its associated URLs, optional username, + * optional credential, TLS cert policy, hostname, ALPN protocols and + * elliptic curves. + */ +- (instancetype) + initWithURLStrings:(NSArray *)urlStrings + username:(nullable NSString *)username + credential:(nullable NSString *)credential + tlsCertPolicy:(RTCTlsCertPolicy)tlsCertPolicy + hostname:(nullable NSString *)hostname + tlsAlpnProtocols:(nullable NSArray *)tlsAlpnProtocols + tlsEllipticCurves:(nullable NSArray *)tlsEllipticCurves + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCLegacyStatsReport.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCLegacyStatsReport.h new file mode 100644 index 00000000..c9ce8e38 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCLegacyStatsReport.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** This does not currently conform to the spec. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCLegacyStatsReport) : NSObject + +/** Time since 1970-01-01T00:00:00Z in milliseconds. */ +@property(nonatomic, readonly) CFTimeInterval timestamp; + +/** The type of stats held by this object. */ +@property(nonatomic, readonly) NSString *type; + +/** The identifier for this object. */ +@property(nonatomic, readonly) NSString *reportId; + +/** A dictionary holding the actual stats. */ +@property(nonatomic, readonly) NSDictionary *values; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCLogging.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCLogging.h new file mode 100644 index 00000000..4cc29010 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCLogging.h @@ -0,0 +1,70 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +// Subset of webrtc::LoggingSeverity. +typedef NS_ENUM(NSInteger, RTCLoggingSeverity) { + RTCLoggingSeverityVerbose, + RTCLoggingSeverityInfo, + RTCLoggingSeverityWarning, + RTCLoggingSeverityError, + RTCLoggingSeverityNone, +}; + +// Wrapper for C++ RTC_LOG(sev) macros. +// Logs the log string to the webrtc logstream for the given severity. +RTC_EXTERN void RTCLogEx(RTCLoggingSeverity severity, NSString* log_string); + +// Wrapper for webrtc::LogMessage::LogToDebug. +// Sets the minimum severity to be logged to console. +RTC_EXTERN void RTCSetMinDebugLogLevel(RTCLoggingSeverity severity); + +// Returns the filename with the path prefix removed. +RTC_EXTERN NSString* RTCFileName(const char* filePath); + +// Some convenience macros. + +#define RTCLogString(format, ...) \ + [NSString stringWithFormat:@"(%@:%d %s): " format, \ + RTCFileName(__FILE__), \ + __LINE__, \ + __FUNCTION__, \ + ##__VA_ARGS__] + +#define RTCLogFormat(severity, format, ...) \ + do { \ + NSString* log_string = RTCLogString(format, ##__VA_ARGS__); \ + RTCLogEx(severity, log_string); \ + } while (false) + +#define RTCLogVerbose(format, ...) \ + RTCLogFormat(RTCLoggingSeverityVerbose, format, ##__VA_ARGS__) + +#define RTCLogInfo(format, ...) \ + RTCLogFormat(RTCLoggingSeverityInfo, format, ##__VA_ARGS__) + +#define RTCLogWarning(format, ...) \ + RTCLogFormat(RTCLoggingSeverityWarning, format, ##__VA_ARGS__) + +#define RTCLogError(format, ...) \ + RTCLogFormat(RTCLoggingSeverityError, format, ##__VA_ARGS__) + +#if !defined(NDEBUG) +#define RTCLogDebug(format, ...) RTCLogInfo(format, ##__VA_ARGS__) +#else +#define RTCLogDebug(format, ...) \ + do { \ + } while (false) +#endif + +#define RTCLog(format, ...) RTCLogInfo(format, ##__VA_ARGS__) diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMTLVideoView.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMTLVideoView.h new file mode 100644 index 00000000..f93ab591 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMTLVideoView.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * RTCMTLVideoView is thin wrapper around MTKView. + * + * It has id property that renders video frames in the view's + * bounds using Metal. + */ +NS_CLASS_AVAILABLE_IOS(9) + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMTLVideoView) : UIView + +@property(nonatomic, weak) id delegate; + +@property(nonatomic) UIViewContentMode videoContentMode; + +/** @abstract Enables/disables rendering. + */ +@property(nonatomic, getter=isEnabled) BOOL enabled; + +/** @abstract Wrapped RTCVideoRotation, or nil. + */ +@property(nonatomic, nullable) NSValue* rotationOverride; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMacros.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMacros.h new file mode 100644 index 00000000..cb943b4b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMacros.h @@ -0,0 +1,67 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SDK_OBJC_BASE_RTCMACROS_H_ +#define SDK_OBJC_BASE_RTCMACROS_H_ + +#ifdef WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT + +#if defined(WEBRTC_LIBRARY_IMPL) +#define RTC_OBJC_EXPORT __attribute__((visibility("default"))) +#endif + +#endif // WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT + +#ifndef RTC_OBJC_EXPORT +#define RTC_OBJC_EXPORT +#endif + +// Macro used to mark a function as deprecated. +#define RTC_OBJC_DEPRECATED(msg) __attribute__((deprecated(msg))) + +// Internal macros used to correctly concatenate symbols. +#define RTC_SYMBOL_CONCAT_HELPER(a, b) a##b +#define RTC_SYMBOL_CONCAT(a, b) RTC_SYMBOL_CONCAT_HELPER(a, b) + +// RTC_OBJC_TYPE_PREFIX +// +// Macro used to prepend a prefix to the API types that are exported with +// RTC_OBJC_EXPORT. +// +// Clients can patch the definition of this macro locally and build +// WebRTC.framework with their own prefix in case symbol clashing is a +// problem. +// +// This macro must be defined uniformily across all the translation units. +#ifndef RTC_OBJC_TYPE_PREFIX +#define RTC_OBJC_TYPE_PREFIX +#endif + +// RCT_OBJC_TYPE +// +// Macro used internally to declare API types. Declaring an API type without +// using this macro will not include the declared type in the set of types +// that will be affected by the configurable RTC_OBJC_TYPE_PREFIX. +#define RTC_OBJC_TYPE(type_name) \ + RTC_SYMBOL_CONCAT(RTC_OBJC_TYPE_PREFIX, type_name) + +#if defined(__cplusplus) +#define RTC_EXTERN extern "C" RTC_OBJC_EXPORT +#else +#define RTC_EXTERN extern RTC_OBJC_EXPORT +#endif + +#ifdef __OBJC__ +#define RTC_FWD_DECL_OBJC_CLASS(classname) @class classname +#else +#define RTC_FWD_DECL_OBJC_CLASS(classname) typedef struct objc_object classname +#endif + +#endif // SDK_OBJC_BASE_RTCMACROS_H_ diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaConstraints.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaConstraints.h new file mode 100644 index 00000000..8d002b97 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaConstraints.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Constraint keys for media sources. */ +/** The value for this key should be a base64 encoded string containing + * the data from the serialized configuration proto. + */ +RTC_EXTERN NSString *const kRTCMediaConstraintsAudioNetworkAdaptorConfig; + +/** Constraint keys for generating offers and answers. */ +RTC_EXTERN NSString *const kRTCMediaConstraintsIceRestart; +RTC_EXTERN NSString *const kRTCMediaConstraintsOfferToReceiveAudio; +RTC_EXTERN NSString *const kRTCMediaConstraintsOfferToReceiveVideo; +RTC_EXTERN NSString *const kRTCMediaConstraintsVoiceActivityDetection; + +/** Constraint values for Boolean parameters. */ +RTC_EXTERN NSString *const kRTCMediaConstraintsValueTrue; +RTC_EXTERN NSString *const kRTCMediaConstraintsValueFalse; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMediaConstraints) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** Initialize with mandatory and/or optional constraints. */ +- (instancetype)initWithMandatoryConstraints: + (nullable NSDictionary *)mandatory + optionalConstraints: + (nullable NSDictionary *) + optional NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaSource.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaSource.h new file mode 100644 index 00000000..51ceb605 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaSource.h @@ -0,0 +1,34 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSInteger, RTCSourceState) { + RTCSourceStateInitializing, + RTCSourceStateLive, + RTCSourceStateEnded, + RTCSourceStateMuted, +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMediaSource) : NSObject + +/** The current state of the RTCMediaSource. */ +@property(nonatomic, readonly) RTCSourceState state; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaStream.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaStream.h new file mode 100644 index 00000000..ce3eec5d --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaStream.h @@ -0,0 +1,50 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCAudioTrack); +@class RTC_OBJC_TYPE(RTCPeerConnectionFactory); +@class RTC_OBJC_TYPE(RTCVideoTrack); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMediaStream) : NSObject + +/** The audio tracks in this stream. */ +@property(nonatomic, strong, readonly) NSArray *audioTracks; + +/** The video tracks in this stream. */ +@property(nonatomic, strong, readonly) + NSArray *videoTracks; + +/** An identifier for this media stream. */ +@property(nonatomic, readonly) NSString *streamId; + +- (instancetype)init NS_UNAVAILABLE; + +/** Adds the given audio track to this media stream. */ +- (void)addAudioTrack:(RTC_OBJC_TYPE(RTCAudioTrack) *)audioTrack; + +/** Adds the given video track to this media stream. */ +- (void)addVideoTrack:(RTC_OBJC_TYPE(RTCVideoTrack) *)videoTrack; + +/** Removes the given audio track to this media stream. */ +- (void)removeAudioTrack:(RTC_OBJC_TYPE(RTCAudioTrack) *)audioTrack; + +/** Removes the given video track to this media stream. */ +- (void)removeVideoTrack:(RTC_OBJC_TYPE(RTCVideoTrack) *)videoTrack; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaStreamTrack.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaStreamTrack.h new file mode 100644 index 00000000..52658794 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMediaStreamTrack.h @@ -0,0 +1,50 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +/** + * Represents the state of the track. This exposes the same states in C++. + */ +typedef NS_ENUM(NSInteger, RTCMediaStreamTrackState) { + RTCMediaStreamTrackStateLive, + RTCMediaStreamTrackStateEnded +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_EXTERN NSString *const kRTCMediaStreamTrackKindAudio; +RTC_EXTERN NSString *const kRTCMediaStreamTrackKindVideo; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMediaStreamTrack) : NSObject + +/** + * The kind of track. For example, "audio" if this track represents an audio + * track and "video" if this track represents a video track. + */ +@property(nonatomic, readonly) NSString *kind; + +/** An identifier string. */ +@property(nonatomic, readonly) NSString *trackId; + +/** The enabled state of the track. */ +@property(nonatomic, assign) BOOL isEnabled; + +/** The state of the track. */ +@property(nonatomic, readonly) RTCMediaStreamTrackState readyState; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMetrics.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMetrics.h new file mode 100644 index 00000000..fffb451a --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMetrics.h @@ -0,0 +1,24 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +/** + * Enables gathering of metrics (which can be fetched with + * RTCGetAndResetMetrics). Must be called before any other call into WebRTC. + */ +RTC_EXTERN void RTCEnableMetrics(void); + +/** Gets and clears native histograms. */ +RTC_EXTERN NSArray* + RTCGetAndResetMetrics(void); diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMetricsSampleInfo.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMetricsSampleInfo.h new file mode 100644 index 00000000..18afdc0b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMetricsSampleInfo.h @@ -0,0 +1,48 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMetricsSampleInfo) : NSObject + +/** + * Example of RTCMetricsSampleInfo: + * name: "WebRTC.Video.InputFramesPerSecond" + * min: 1 + * max: 100 + * bucketCount: 50 + * samples: [29]:2 [30]:1 + */ + +/** The name of the histogram. */ +@property(nonatomic, readonly) NSString *name; + +/** The minimum bucket value. */ +@property(nonatomic, readonly) int min; + +/** The maximum bucket value. */ +@property(nonatomic, readonly) int max; + +/** The number of buckets. */ +@property(nonatomic, readonly) int bucketCount; + +/** A dictionary holding the samples . */ +@property(nonatomic, readonly) NSDictionary *samples; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMutableI420Buffer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMutableI420Buffer.h new file mode 100644 index 00000000..7685234f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMutableI420Buffer.h @@ -0,0 +1,24 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Extension of the I420 buffer with mutable data access */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCMutableI420Buffer) @end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMutableYUVPlanarBuffer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMutableYUVPlanarBuffer.h new file mode 100644 index 00000000..feb7417b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCMutableYUVPlanarBuffer.h @@ -0,0 +1,28 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Extension of the YUV planar data buffer with mutable data access */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCMutableYUVPlanarBuffer) + + @property(nonatomic, readonly) uint8_t *mutableDataY; +@property(nonatomic, readonly) uint8_t *mutableDataU; +@property(nonatomic, readonly) uint8_t *mutableDataV; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNativeI420Buffer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNativeI420Buffer.h new file mode 100644 index 00000000..c5a0ddf7 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNativeI420Buffer.h @@ -0,0 +1,23 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** RTCI420Buffer implements the RTCI420Buffer protocol */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCI420Buffer) : NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNativeMutableI420Buffer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNativeMutableI420Buffer.h new file mode 100644 index 00000000..b300731a --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNativeMutableI420Buffer.h @@ -0,0 +1,24 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Mutable version of RTCI420Buffer */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCMutableI420Buffer) : RTC_OBJC_TYPE(RTCI420Buffer) +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNetworkMonitor.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNetworkMonitor.h new file mode 100644 index 00000000..21d22f54 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCNetworkMonitor.h @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Listens for NWPathMonitor updates and forwards the results to a C++ + * observer. + */ +@interface RTCNetworkMonitor : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnection.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnection.h new file mode 100644 index 00000000..7ddb5ba4 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnection.h @@ -0,0 +1,422 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +@class RTC_OBJC_TYPE(RTCConfiguration); +@class RTC_OBJC_TYPE(RTCDataChannel); +@class RTC_OBJC_TYPE(RTCDataChannelConfiguration); +@class RTC_OBJC_TYPE(RTCIceCandidate); +@class RTC_OBJC_TYPE(RTCIceCandidateErrorEvent); +@class RTC_OBJC_TYPE(RTCMediaConstraints); +@class RTC_OBJC_TYPE(RTCMediaStream); +@class RTC_OBJC_TYPE(RTCMediaStreamTrack); +@class RTC_OBJC_TYPE(RTCPeerConnectionFactory); +@class RTC_OBJC_TYPE(RTCRtpReceiver); +@class RTC_OBJC_TYPE(RTCRtpSender); +@class RTC_OBJC_TYPE(RTCRtpTransceiver); +@class RTC_OBJC_TYPE(RTCRtpTransceiverInit); +@class RTC_OBJC_TYPE(RTCSessionDescription); +@class RTC_OBJC_TYPE(RTCStatisticsReport); +@class RTC_OBJC_TYPE(RTCLegacyStatsReport); + +typedef NS_ENUM(NSInteger, RTCRtpMediaType); + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const kRTCPeerConnectionErrorDomain; +extern int const kRTCSessionDescriptionErrorCode; + +/** Represents the signaling state of the peer connection. */ +typedef NS_ENUM(NSInteger, RTCSignalingState) { + RTCSignalingStateStable, + RTCSignalingStateHaveLocalOffer, + RTCSignalingStateHaveLocalPrAnswer, + RTCSignalingStateHaveRemoteOffer, + RTCSignalingStateHaveRemotePrAnswer, + // Not an actual state, represents the total number of states. + RTCSignalingStateClosed, +}; + +/** Represents the ice connection state of the peer connection. */ +typedef NS_ENUM(NSInteger, RTCIceConnectionState) { + RTCIceConnectionStateNew, + RTCIceConnectionStateChecking, + RTCIceConnectionStateConnected, + RTCIceConnectionStateCompleted, + RTCIceConnectionStateFailed, + RTCIceConnectionStateDisconnected, + RTCIceConnectionStateClosed, + RTCIceConnectionStateCount, +}; + +/** Represents the combined ice+dtls connection state of the peer connection. */ +typedef NS_ENUM(NSInteger, RTCPeerConnectionState) { + RTCPeerConnectionStateNew, + RTCPeerConnectionStateConnecting, + RTCPeerConnectionStateConnected, + RTCPeerConnectionStateDisconnected, + RTCPeerConnectionStateFailed, + RTCPeerConnectionStateClosed, +}; + +/** Represents the ice gathering state of the peer connection. */ +typedef NS_ENUM(NSInteger, RTCIceGatheringState) { + RTCIceGatheringStateNew, + RTCIceGatheringStateGathering, + RTCIceGatheringStateComplete, +}; + +/** Represents the stats output level. */ +typedef NS_ENUM(NSInteger, RTCStatsOutputLevel) { + RTCStatsOutputLevelStandard, + RTCStatsOutputLevelDebug, +}; + +typedef void (^RTCCreateSessionDescriptionCompletionHandler)( + RTC_OBJC_TYPE(RTCSessionDescription) *_Nullable sdp, + NSError *_Nullable error); + +typedef void (^RTCSetSessionDescriptionCompletionHandler)( + NSError *_Nullable error); + +@class RTC_OBJC_TYPE(RTCPeerConnection); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCPeerConnectionDelegate) + + /** Called when the SignalingState changed. */ + - (void)peerConnection + : (RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection didChangeSignalingState + : (RTCSignalingState)stateChanged; + +/** Called when media is received on a new stream from remote peer. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didAddStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream; + +/** Called when a remote peer closes a stream. + * This is not called when RTCSdpSemanticsUnifiedPlan is specified. + */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didRemoveStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream; + +/** Called when negotiation is needed, for example ICE has restarted. */ +- (void)peerConnectionShouldNegotiate: + (RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection; + +/** Called any time the IceConnectionState changes. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeIceConnectionState:(RTCIceConnectionState)newState; + +/** Called any time the IceGatheringState changes. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeIceGatheringState:(RTCIceGatheringState)newState; + +/** New ice candidate has been found. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didGenerateIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate; + +/** Called when a group of local Ice candidates have been removed. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didRemoveIceCandidates: + (NSArray *)candidates; + +/** New data channel has been opened. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didOpenDataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel; + +/** Called when signaling indicates a transceiver will be receiving media from + * the remote endpoint. + * This is only called with RTCSdpSemanticsUnifiedPlan specified. + */ +@optional +/** Called any time the IceConnectionState changes following standardized + * transition. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeStandardizedIceConnectionState:(RTCIceConnectionState)newState; + +/** Called any time the PeerConnectionState changes. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeConnectionState:(RTCPeerConnectionState)newState; + +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didStartReceivingOnTransceiver: + (RTC_OBJC_TYPE(RTCRtpTransceiver) *)transceiver; + +/** Called when a receiver and its track are created. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didAddReceiver:(RTC_OBJC_TYPE(RTCRtpReceiver) *)rtpReceiver + streams:(NSArray *)mediaStreams; + +/** Called when the receiver and its track are removed. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didRemoveReceiver:(RTC_OBJC_TYPE(RTCRtpReceiver) *)rtpReceiver; + +/** Called when the selected ICE candidate pair is changed. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didChangeLocalCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)local + remoteCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)remote + lastReceivedMs:(int)lastDataReceivedMs + changeReason:(NSString *)reason; + +/** Called when gathering of an ICE candidate failed. */ +- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection + didFailToGatherIceCandidate: + (RTC_OBJC_TYPE(RTCIceCandidateErrorEvent) *)event; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCPeerConnection) : NSObject + +/** The object that will be notifed about events such as state changes and + * streams being added or removed. + */ +@property(nonatomic, weak, nullable) id delegate; +/** This property is not available with RTCSdpSemanticsUnifiedPlan. Please use + * `senders` instead. + */ +@property(nonatomic, readonly) + NSArray *localStreams; +@property(nonatomic, readonly, nullable) RTC_OBJC_TYPE(RTCSessionDescription) * + localDescription; +@property(nonatomic, readonly, nullable) RTC_OBJC_TYPE(RTCSessionDescription) * + remoteDescription; +@property(nonatomic, readonly) RTCSignalingState signalingState; +@property(nonatomic, readonly) RTCIceConnectionState iceConnectionState; +@property(nonatomic, readonly) RTCPeerConnectionState connectionState; +@property(nonatomic, readonly) RTCIceGatheringState iceGatheringState; +@property(nonatomic, readonly, copy) RTC_OBJC_TYPE(RTCConfiguration) * + configuration; + +/** Gets all RTCRtpSenders associated with this peer connection. + * Note: reading this property returns different instances of RTCRtpSender. + * Use isEqual: instead of == to compare RTCRtpSender instances. + */ +@property(nonatomic, readonly) NSArray *senders; + +/** Gets all RTCRtpReceivers associated with this peer connection. + * Note: reading this property returns different instances of RTCRtpReceiver. + * Use isEqual: instead of == to compare RTCRtpReceiver instances. + */ +@property(nonatomic, readonly) + NSArray *receivers; + +/** Gets all RTCRtpTransceivers associated with this peer connection. + * Note: reading this property returns different instances of + * RTCRtpTransceiver. Use isEqual: instead of == to compare + * RTCRtpTransceiver instances. This is only available with + * RTCSdpSemanticsUnifiedPlan specified. + */ +@property(nonatomic, readonly) + NSArray *transceivers; + +- (instancetype)init NS_UNAVAILABLE; + +/** Sets the PeerConnection's global configuration to `configuration`. + * Any changes to STUN/TURN servers or ICE candidate policy will affect the + * next gathering phase, and cause the next call to createOffer to generate + * new ICE credentials. Note that the BUNDLE and RTCP-multiplexing policies + * cannot be changed with this method. + */ +- (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCConfiguration) *)configuration; + +/** Terminate all media and close the transport. */ +- (void)close; + +/** Provide a remote candidate to the ICE Agent. */ +- (void)addIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate + DEPRECATED_MSG_ATTRIBUTE( + "Please use addIceCandidate:completionHandler: instead"); + +/** Provide a remote candidate to the ICE Agent. */ +- (void)addIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate + completionHandler:(void (^)(NSError *_Nullable error))completionHandler; + +/** Remove a group of remote candidates from the ICE Agent. */ +- (void)removeIceCandidates: + (NSArray *)candidates; + +/** Add a new media stream to be sent on this peer connection. + * This method is not supported with RTCSdpSemanticsUnifiedPlan. Please use + * addTrack instead. + */ +- (void)addStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream; + +/** Remove the given media stream from this peer connection. + * This method is not supported with RTCSdpSemanticsUnifiedPlan. Please use + * removeTrack instead. + */ +- (void)removeStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream; + +/** Add a new media stream track to be sent on this peer connection, and return + * the newly created RTCRtpSender. The RTCRtpSender will be + * associated with the streams specified in the `streamIds` list. + * + * Errors: If an error occurs, returns nil. An error can occur if: + * - A sender already exists for the track. + * - The peer connection is closed. + */ +- (nullable RTC_OBJC_TYPE(RTCRtpSender) *) + addTrack:(RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track + streamIds:(NSArray *)streamIds; + +/** With PlanB semantics, removes an RTCRtpSender from this peer connection. + * + * With UnifiedPlan semantics, sets sender's track to null and removes the + * send component from the associated RTCRtpTransceiver's direction. + * + * Returns YES on success. + */ +- (BOOL)removeTrack:(RTC_OBJC_TYPE(RTCRtpSender) *)sender; + +/** addTransceiver creates a new RTCRtpTransceiver and adds it to the set of + * transceivers. Adding a transceiver will cause future calls to CreateOffer + * to add a media description for the corresponding transceiver. + * + * The initial value of `mid` in the returned transceiver is nil. Setting a + * new session description may change it to a non-nil value. + * + * https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver + * + * Optionally, an RtpTransceiverInit structure can be specified to configure + * the transceiver from construction. If not specified, the transceiver will + * default to having a direction of kSendRecv and not be part of any streams. + * + * These methods are only available when Unified Plan is enabled (see + * RTCConfiguration). + */ + +/** Adds a transceiver with a sender set to transmit the given track. The kind + * of the transceiver (and sender/receiver) will be derived from the kind of + * the track. + */ +- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *)addTransceiverWithTrack: + (RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track; +- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *) + addTransceiverWithTrack:(RTC_OBJC_TYPE(RTCMediaStreamTrack) *)track + init:(RTC_OBJC_TYPE(RTCRtpTransceiverInit) *)init; + +/** Adds a transceiver with the given kind. Can either be RTCRtpMediaTypeAudio + * or RTCRtpMediaTypeVideo. + */ +- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *)addTransceiverOfType: + (RTCRtpMediaType)mediaType; +- (nullable RTC_OBJC_TYPE(RTCRtpTransceiver) *) + addTransceiverOfType:(RTCRtpMediaType)mediaType + init:(RTC_OBJC_TYPE(RTCRtpTransceiverInit) *)init; + +/** Tells the PeerConnection that ICE should be restarted. This triggers a need + * for negotiation and subsequent offerForConstraints:completionHandler call + * will act as if RTCOfferAnswerOptions::ice_restart is true. + */ +- (void)restartIce; + +/** Generate an SDP offer. */ +- (void)offerForConstraints:(RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints + completionHandler: + (RTCCreateSessionDescriptionCompletionHandler)completionHandler; + +/** Generate an SDP answer. */ +- (void)answerForConstraints:(RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints + completionHandler: + (RTCCreateSessionDescriptionCompletionHandler)completionHandler; + +/** Apply the supplied RTCSessionDescription as the local description. */ +- (void)setLocalDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp + completionHandler: + (RTCSetSessionDescriptionCompletionHandler)completionHandler; + +/** Creates an offer or answer (depending on current signaling state) and sets + * it as the local session description. */ +- (void)setLocalDescriptionWithCompletionHandler: + (RTCSetSessionDescriptionCompletionHandler)completionHandler; + +/** Apply the supplied RTCSessionDescription as the remote description. */ +- (void)setRemoteDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp + completionHandler: + (RTCSetSessionDescriptionCompletionHandler)completionHandler; + +/** Limits the bandwidth allocated for all RTP streams sent by this + * PeerConnection. Nil parameters will be unchanged. Setting + * `currentBitrateBps` will force the available bitrate estimate to the given + * value. Returns YES if the parameters were successfully updated. + */ +- (BOOL)setBweMinBitrateBps:(nullable NSNumber *)minBitrateBps + currentBitrateBps:(nullable NSNumber *)currentBitrateBps + maxBitrateBps:(nullable NSNumber *)maxBitrateBps; + +/** Start or stop recording an Rtc EventLog. */ +- (BOOL)startRtcEventLogWithFilePath:(NSString *)filePath + maxSizeInBytes:(int64_t)maxSizeInBytes; +- (void)stopRtcEventLog; + +@end + +@interface RTC_OBJC_TYPE (RTCPeerConnection) +(Media) + + /** Create an RTCRtpSender with the specified kind and media stream ID. + * See RTCMediaStreamTrack.h for available kinds. + * This method is not supported with RTCSdpSemanticsUnifiedPlan. Please use + * addTransceiver instead. + */ + - (RTC_OBJC_TYPE(RTCRtpSender) *)senderWithKind : (NSString *)kind streamId + : (NSString *)streamId; + +@end + +@interface RTC_OBJC_TYPE (RTCPeerConnection) +(DataChannel) + + /** Create a new data channel with the given label and configuration. */ + - (nullable RTC_OBJC_TYPE(RTCDataChannel) *)dataChannelForLabel + : (NSString *)label configuration + : (RTC_OBJC_TYPE(RTCDataChannelConfiguration) *)configuration; + +@end + +typedef void (^RTCStatisticsCompletionHandler)( + RTC_OBJC_TYPE(RTCStatisticsReport) *); + +@interface RTC_OBJC_TYPE (RTCPeerConnection) +(Stats) + + /** Gather stats for the given RTCMediaStreamTrack. If `mediaStreamTrack` is + * nil statistics are gathered for all tracks. + */ + - (void)statsForTrack : (nullable RTC_OBJC_TYPE(RTCMediaStreamTrack) *) + mediaStreamTrack statsOutputLevel + : (RTCStatsOutputLevel)statsOutputLevel completionHandler + : (nullable void (^)(NSArray *stats)) + completionHandler; + +/** Gather statistic through the v2 statistics API. */ +- (void)statisticsWithCompletionHandler: + (RTCStatisticsCompletionHandler)completionHandler; + +/** Spec-compliant getStats() performing the stats selection algorithm with the + * sender. + */ +- (void)statisticsForSender:(RTC_OBJC_TYPE(RTCRtpSender) *)sender + completionHandler:(RTCStatisticsCompletionHandler)completionHandler; + +/** Spec-compliant getStats() performing the stats selection algorithm with the + * receiver. + */ +- (void)statisticsForReceiver:(RTC_OBJC_TYPE(RTCRtpReceiver) *)receiver + completionHandler:(RTCStatisticsCompletionHandler)completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnectionFactory.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnectionFactory.h new file mode 100644 index 00000000..7396beab --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnectionFactory.h @@ -0,0 +1,147 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCRtpCapabilities); +@class RTC_OBJC_TYPE(RTCAudioSource); +@class RTC_OBJC_TYPE(RTCAudioTrack); +@class RTC_OBJC_TYPE(RTCConfiguration); +@class RTC_OBJC_TYPE(RTCMediaConstraints); +@class RTC_OBJC_TYPE(RTCMediaStream); +@class RTC_OBJC_TYPE(RTCPeerConnection); +@class RTC_OBJC_TYPE(RTCVideoSource); +@class RTC_OBJC_TYPE(RTCVideoTrack); +@class RTC_OBJC_TYPE(RTCPeerConnectionFactoryOptions); +@protocol RTC_OBJC_TYPE +(RTCPeerConnectionDelegate); +@protocol RTC_OBJC_TYPE +(RTCVideoDecoderFactory); +@protocol RTC_OBJC_TYPE +(RTCVideoEncoderFactory); +@protocol RTC_OBJC_TYPE +(RTCSSLCertificateVerifier); +@protocol RTC_OBJC_TYPE +(RTCAudioDevice); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCPeerConnectionFactory) : NSObject + +/* Initialize object with default H264 video encoder/decoder factories and default ADM */ +- (instancetype)init; + +/* Initialize object with injectable video encoder/decoder factories and default + * ADM */ +- (instancetype) + initWithEncoderFactory: + (nullable id)encoderFactory + decoderFactory:(nullable id) + decoderFactory; + +/* Initialize object with injectable video encoder/decoder factories and + * injectable ADM */ +- (instancetype) + initWithEncoderFactory: + (nullable id)encoderFactory + decoderFactory:(nullable id) + decoderFactory + audioDevice: + (nullable id)audioDevice; + +/** + * Valid kind values are kRTCMediaStreamTrackKindAudio and + * kRTCMediaStreamTrackKindVideo. + */ +- (RTC_OBJC_TYPE(RTCRtpCapabilities) *)rtpSenderCapabilitiesForKind: + (NSString *)kind; + +/** + * Valid kind values are kRTCMediaStreamTrackKindAudio and + * kRTCMediaStreamTrackKindVideo. + */ +- (RTC_OBJC_TYPE(RTCRtpCapabilities) *)rtpReceiverCapabilitiesForKind: + (NSString *)kind; + +/** Initialize an RTCAudioSource with constraints. */ +- (RTC_OBJC_TYPE(RTCAudioSource) *)audioSourceWithConstraints: + (nullable RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints; + +/** Initialize an RTCAudioTrack with an id. Convenience ctor to use an audio + * source with no constraints. + */ +- (RTC_OBJC_TYPE(RTCAudioTrack) *)audioTrackWithTrackId:(NSString *)trackId; + +/** Initialize an RTCAudioTrack with a source and an id. */ +- (RTC_OBJC_TYPE(RTCAudioTrack) *)audioTrackWithSource: + (RTC_OBJC_TYPE(RTCAudioSource) *)source + trackId:(NSString *)trackId; + +/** Initialize a generic RTCVideoSource. The RTCVideoSource should be + * passed to a RTCVideoCapturer implementation, e.g. + * RTCCameraVideoCapturer, in order to produce frames. + */ +- (RTC_OBJC_TYPE(RTCVideoSource) *)videoSource; + +/** Initialize a generic RTCVideoSource with he posibility of marking + * it as usable for screen sharing. The RTCVideoSource should be + * passed to a RTCVideoCapturer implementation, e.g. + * RTCCameraVideoCapturer, in order to produce frames. + */ +- (RTC_OBJC_TYPE(RTCVideoSource) *)videoSourceForScreenCast:(BOOL)forScreenCast; + +/** Initialize an RTCVideoTrack with a source and an id. */ +- (RTC_OBJC_TYPE(RTCVideoTrack) *)videoTrackWithSource: + (RTC_OBJC_TYPE(RTCVideoSource) *)source + trackId:(NSString *)trackId; + +/** Initialize an RTCMediaStream with an id. */ +- (RTC_OBJC_TYPE(RTCMediaStream) *)mediaStreamWithStreamId:(NSString *)streamId; + +/** Initialize an RTCPeerConnection with a configuration, constraints, and + * delegate. + */ +- (nullable RTC_OBJC_TYPE(RTCPeerConnection) *) + peerConnectionWithConfiguration: + (RTC_OBJC_TYPE(RTCConfiguration) *)configuration + constraints: + (RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints + delegate:(nullable id)delegate; + +- (nullable RTC_OBJC_TYPE(RTCPeerConnection) *) + peerConnectionWithConfiguration: + (RTC_OBJC_TYPE(RTCConfiguration) *)configuration + constraints: + (RTC_OBJC_TYPE(RTCMediaConstraints) *)constraints + certificateVerifier: + (id) + certificateVerifier + delegate:(nullable id)delegate; + +/** Set the options to be used for subsequently created RTCPeerConnections */ +- (void)setOptions: + (nonnull RTC_OBJC_TYPE(RTCPeerConnectionFactoryOptions) *)options; + +/** Start an AecDump recording. This API call will likely change in the future. + */ +- (BOOL)startAecDumpWithFilePath:(NSString *)filePath + maxSizeInBytes:(int64_t)maxSizeInBytes; + +/* Stop an active AecDump recording */ +- (void)stopAecDump; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnectionFactoryOptions.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnectionFactoryOptions.h new file mode 100644 index 00000000..1c7a10d1 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCPeerConnectionFactoryOptions.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCPeerConnectionFactoryOptions) : NSObject + +@property(nonatomic, assign) BOOL disableEncryption; + +@property(nonatomic, assign) BOOL disableNetworkMonitor; + +@property(nonatomic, assign) BOOL ignoreLoopbackNetworkAdapter; + +@property(nonatomic, assign) BOOL ignoreVPNNetworkAdapter; + +@property(nonatomic, assign) BOOL ignoreCellularNetworkAdapter; + +@property(nonatomic, assign) BOOL ignoreWiFiNetworkAdapter; + +@property(nonatomic, assign) BOOL ignoreEthernetNetworkAdapter; + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtcpParameters.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtcpParameters.h new file mode 100644 index 00000000..8449500f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtcpParameters.h @@ -0,0 +1,30 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtcpParameters) : NSObject + +/** The Canonical Name used by RTCP. */ +@property(nonatomic, readonly, copy) NSString *cname; + +/** Whether reduced size RTCP is configured or compound RTCP. */ +@property(nonatomic, assign) BOOL isReducedSize; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCapabilities.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCapabilities.h new file mode 100644 index 00000000..c44d0b86 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCapabilities.h @@ -0,0 +1,31 @@ +/* + * Copyright 2024 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCRtpCodecCapability); +@class RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpCapabilities) : NSObject + +@property(nonatomic, copy) NSArray *codecs; +@property(nonatomic, copy) + NSArray *headerExtensions; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCodecCapability.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCodecCapability.h new file mode 100644 index 00000000..ee953e13 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCodecCapability.h @@ -0,0 +1,58 @@ +/* + * Copyright 2024 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpCodecCapability) : NSObject + +/** The preferred RTP payload type. */ +@property(nonatomic, readonly, nullable) NSNumber *preferredPayloadType; + +/** + * The codec MIME subtype. Valid types are listed in: + * http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-2 + * + * Several supported types are represented by the constants above. + */ +@property(nonatomic, readonly) NSString *name; + +/** + * The media type of this codec. Equivalent to MIME top-level type. + * + * Valid values are kRTCMediaStreamTrackKindAudio and + * kRTCMediaStreamTrackKindVideo. + */ +@property(nonatomic, readonly) NSString *kind; + +/** The codec clock rate expressed in Hertz. */ +@property(nonatomic, readonly, nullable) NSNumber *clockRate; + +/** + * The number of audio channels (mono=1, stereo=2). + * Set to null for video codecs. + **/ +@property(nonatomic, readonly, nullable) NSNumber *numChannels; + +/** The "format specific parameters" field from the "a=fmtp" line in the SDP */ +@property(nonatomic, readonly) NSDictionary *parameters; + +/** The MIME type of the codec. */ +@property(nonatomic, readonly) NSString *mimeType; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCodecParameters.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCodecParameters.h new file mode 100644 index 00000000..9f96220c --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpCodecParameters.h @@ -0,0 +1,72 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_EXTERN const NSString *const kRTCRtxCodecName; +RTC_EXTERN const NSString *const kRTCRedCodecName; +RTC_EXTERN const NSString *const kRTCUlpfecCodecName; +RTC_EXTERN const NSString *const kRTCFlexfecCodecName; +RTC_EXTERN const NSString *const kRTCOpusCodecName; +RTC_EXTERN const NSString *const kRTCIsacCodecName; +RTC_EXTERN const NSString *const kRTCL16CodecName; +RTC_EXTERN const NSString *const kRTCG722CodecName; +RTC_EXTERN const NSString *const kRTCPcmuCodecName; +RTC_EXTERN const NSString *const kRTCPcmaCodecName; +RTC_EXTERN const NSString *const kRTCDtmfCodecName; +RTC_EXTERN const NSString *const kRTCComfortNoiseCodecName; +RTC_EXTERN const NSString *const kRTCVp8CodecName; +RTC_EXTERN const NSString *const kRTCVp9CodecName; +RTC_EXTERN const NSString *const kRTCH264CodecName; + +/** Defined in https://www.w3.org/TR/webrtc/#idl-def-rtcrtpcodecparameters */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpCodecParameters) : NSObject + +/** The RTP payload type. */ +@property(nonatomic, assign) int payloadType; + +/** + * The codec MIME subtype. Valid types are listed in: + * http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-2 + * + * Several supported types are represented by the constants above. + */ +@property(nonatomic, readonly, nonnull) NSString *name; + +/** + * The media type of this codec. Equivalent to MIME top-level type. + * + * Valid values are kRTCMediaStreamTrackKindAudio and + * kRTCMediaStreamTrackKindVideo. + */ +@property(nonatomic, readonly, nonnull) NSString *kind; + +/** The codec clock rate expressed in Hertz. */ +@property(nonatomic, readonly, nullable) NSNumber *clockRate; + +/** + * The number of channels (mono=1, stereo=2). + * Set to null for video codecs. + **/ +@property(nonatomic, readonly, nullable) NSNumber *numChannels; + +/** The "format specific parameters" field from the "a=fmtp" line in the SDP */ +@property(nonatomic, readonly, nonnull) NSDictionary *parameters; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpEncodingParameters.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpEncodingParameters.h new file mode 100644 index 00000000..a4ad10a3 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpEncodingParameters.h @@ -0,0 +1,77 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Corresponds to webrtc::Priority. */ +typedef NS_ENUM(NSInteger, RTCPriority) { + RTCPriorityVeryLow, + RTCPriorityLow, + RTCPriorityMedium, + RTCPriorityHigh +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpEncodingParameters) : NSObject + +/** The idenfifier for the encoding layer. This is used in simulcast. */ +@property(nonatomic, copy, nullable) NSString *rid; + +/** Controls whether the encoding is currently transmitted. */ +@property(nonatomic, assign) BOOL isActive; + +/** The maximum bitrate to use for the encoding, or nil if there is no + * limit. + */ +@property(nonatomic, copy, nullable) NSNumber *maxBitrateBps; + +/** The minimum bitrate to use for the encoding, or nil if there is no + * limit. + */ +@property(nonatomic, copy, nullable) NSNumber *minBitrateBps; + +/** The maximum framerate to use for the encoding, or nil if there is no + * limit. + */ +@property(nonatomic, copy, nullable) NSNumber *maxFramerate; + +/** The requested number of temporal layers to use for the encoding, or nil + * if the default should be used. + */ +@property(nonatomic, copy, nullable) NSNumber *numTemporalLayers; + +/** Scale the width and height down by this factor for video. If nil, + * implementation default scaling factor will be used. + */ +@property(nonatomic, copy, nullable) NSNumber *scaleResolutionDownBy; + +/** The SSRC being used by this encoding. */ +@property(nonatomic, readonly, nullable) NSNumber *ssrc; + +/** The relative bitrate priority. */ +@property(nonatomic, assign) double bitratePriority; + +/** The relative DiffServ Code Point priority. */ +@property(nonatomic, assign) RTCPriority networkPriority; + +/** Allow dynamic frame length changes for audio: + https://w3c.github.io/webrtc-extensions/#dom-rtcrtpencodingparameters-adaptiveptime + */ +@property(nonatomic, assign) BOOL adaptiveAudioPacketTime; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpHeaderExtension.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpHeaderExtension.h new file mode 100644 index 00000000..d3f1fda8 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpHeaderExtension.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpHeaderExtension) : NSObject + +/** The URI of the RTP header extension, as defined in RFC5285. */ +@property(nonatomic, readonly, copy) NSString *uri; + +/** The value put in the RTP packet to identify the header extension. */ +@property(nonatomic, readonly) int id; + +/** Whether the header extension is encrypted or not. */ +@property(nonatomic, readonly, getter=isEncrypted) BOOL encrypted; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpHeaderExtensionCapability.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpHeaderExtensionCapability.h new file mode 100644 index 00000000..2fbcce36 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpHeaderExtensionCapability.h @@ -0,0 +1,39 @@ +/* + * Copyright 2024 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +typedef NS_ENUM(NSInteger, RTCRtpTransceiverDirection); + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpHeaderExtensionCapability) : NSObject + +/** The URI of the RTP header extension, as defined in RFC5285. */ +@property(nonatomic, readonly, copy) NSString *uri; + +/** The value put in the RTP packet to identify the header extension. */ +@property(nonatomic, readonly, nullable) NSNumber* preferredId; + +/** Whether the header extension is encrypted or not. */ +@property(nonatomic, readonly, getter=isPreferredEncrypted) + BOOL preferredEncrypted; + +/** Direction of the header extension. */ +@property(nonatomic) RTCRtpTransceiverDirection direction; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpParameters.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpParameters.h new file mode 100644 index 00000000..0e973643 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpParameters.h @@ -0,0 +1,65 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Corresponds to webrtc::DegradationPreference. */ +typedef NS_ENUM(NSInteger, RTCDegradationPreference) { + RTCDegradationPreferenceMaintainFramerateAndResolution, + // TODO(webrtc:450044904): Switch downstream projects to + // RTCDegradationPreferenceMaintainFramerateAndResolution and remove + // RTCDegradationPreferenceDisabled. + RTCDegradationPreferenceDisabled = + RTCDegradationPreferenceMaintainFramerateAndResolution, + RTCDegradationPreferenceMaintainFramerate, + RTCDegradationPreferenceMaintainResolution, + RTCDegradationPreferenceBalanced +}; + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpParameters) : NSObject + +/** A unique identifier for the last set of parameters applied. */ +@property(nonatomic, copy) NSString *transactionId; + +/** Parameters used for RTCP. */ +@property(nonatomic, readonly, copy) RTC_OBJC_TYPE(RTCRtcpParameters) * rtcp; + +/** An array containing parameters for RTP header extensions. */ +@property(nonatomic, readonly, copy) + NSArray *headerExtensions; + +/** The currently active encodings in the order of preference. */ +@property(nonatomic, copy) + NSArray *encodings; + +/** The negotiated set of send codecs in order of preference. */ +@property(nonatomic, copy) + NSArray *codecs; + +/** + * Degradation preference in case of CPU adaptation or constrained bandwidth. + * If nil, implementation default degradation preference will be used. + */ +@property(nonatomic, copy, nullable) NSNumber *degradationPreference; + +- (instancetype)init; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpReceiver.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpReceiver.h new file mode 100644 index 00000000..ad5a6c10 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpReceiver.h @@ -0,0 +1,105 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Represents the media type of the RtpReceiver. */ +typedef NS_ENUM(NSInteger, RTCRtpMediaType) { + RTCRtpMediaTypeAudio, + RTCRtpMediaTypeVideo, + RTCRtpMediaTypeData, + RTCRtpMediaTypeUnsupported, + RTCRtpMediaTypeAny, +}; + +@class RTC_OBJC_TYPE(RTCRtpReceiver); +@class RTC_OBJC_TYPE(RTCRtpSource); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpReceiverDelegate) + + /** Called when the first RTP packet is received. + * + * Note: Currently if there are multiple RtpReceivers of the same media + * type, they will all call OnFirstPacketReceived at once. + * + * For example, if we create three audio receivers, A/B/C, they will listen + * to the same signal from the underneath network layer. Whenever the first + * audio packet is received, the underneath signal will be fired. All the + * receivers A/B/C will be notified and the callback of the receiver's + * delegate will be called. + * + * The process is the same for video receivers. + */ + - (void)rtpReceiver : (RTC_OBJC_TYPE(RTCRtpReceiver) *) + rtpReceiver didReceiveFirstPacketForMediaType + : (RTCRtpMediaType)mediaType; +/** Called when the first RTP packet is received after a change in + * receptiveness. + */ +// TODO: crbug.com/40821064 - remove @optional. +@optional +- (void)rtpReceiver:(RTC_OBJC_TYPE(RTCRtpReceiver) *)rtpReceiver + didReceiveFirstPacketForMediaTypeAfterReceptiveChange: + (RTCRtpMediaType)mediaType; + +@end + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpReceiver) + + /** A unique identifier for this receiver. */ + @property(nonatomic, readonly) NSString *receiverId; + +/** The currently active RTCRtpParameters, as defined in + * https://www.w3.org/TR/webrtc/#idl-def-RTCRtpParameters. + * + * The WebRTC specification only defines RTCRtpParameters in terms of senders, + * but this API also applies them to receivers, similar to ORTC: + * http://ortc.org/wp-content/uploads/2016/03/ortc.html#rtcrtpparameters*. + */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCRtpParameters) * parameters; + +/** The RTCMediaStreamTrack associated with the receiver. + * Note: reading this property returns a new instance of + * RTCMediaStreamTrack. Use isEqual: instead of == to compare + * RTCMediaStreamTrack instances. + */ +@property(nonatomic, readonly, nullable) RTC_OBJC_TYPE(RTCMediaStreamTrack) * + track; + +/** +Returns an array that contains an object for each unique SSRC (synchronization +source) identifier and for each unique CSRC (contributing source) received by +the current RTCRtpReceiver in the last ten seconds. +*/ +@property(nonatomic, readonly) NSArray *sources; + +/** The delegate for this RtpReceiver. */ +@property(nonatomic, weak) id delegate; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpReceiver) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpSender.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpSender.h new file mode 100644 index 00000000..841a65e8 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpSender.h @@ -0,0 +1,55 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpSender) + + /** A unique identifier for this sender. */ + @property(nonatomic, readonly) NSString *senderId; + +/** The currently active RTCRtpParameters, as defined in + * https://www.w3.org/TR/webrtc/#idl-def-RTCRtpParameters. + */ +@property(nonatomic, copy) RTC_OBJC_TYPE(RTCRtpParameters) * parameters; + +/** The RTCMediaStreamTrack associated with the sender. + * Note: reading this property returns a new instance of + * RTCMediaStreamTrack. Use isEqual: instead of == to compare + * RTCMediaStreamTrack instances. + */ +@property(nonatomic, copy, nullable) RTC_OBJC_TYPE(RTCMediaStreamTrack) * track; + +/** IDs of streams associated with the RTP sender */ +@property(nonatomic, copy) NSArray *streamIds; + +/** The RTCDtmfSender accociated with the RTP sender. */ +@property(nonatomic, readonly, nullable) id + dtmfSender; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpSender) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpSource.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpSource.h new file mode 100644 index 00000000..a36a0bc1 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpSource.h @@ -0,0 +1,66 @@ +/* + * Copyright 2024 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Represents the source type of received media. */ +typedef NS_ENUM(NSInteger, RTCRtpSourceType) { + RTCRtpSourceTypeSSRC, + RTCRtpSourceTypeCSRC, +}; + +@class RTC_OBJC_TYPE(RTCRtpSource); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpSource) + + /** + A positive integer value specifying the CSRC identifier of the contributing + source or SSRC identifier of the synchronization source. This uniquely + identifies the source of the particular stream RTP packets. */ + @property(nonatomic, readonly) uint32_t sourceId; + +@property(nonatomic, readonly) RTCRtpSourceType sourceType; + +/** +A floating-point value between 0.0 and 1.0 specifying the audio level contained +in the last RTP packet played from the contributing source. +*/ +@property(nonatomic, readonly, nullable) NSNumber *audioLevel; + +/** +A timestamp indicating the most recent time at which a frame originating from +this source was delivered to the receiver's track +*/ +@property(nonatomic, readonly) CFTimeInterval timestampUs; + +/** +The RTP timestamp of the media. This source-generated timestamp indicates the +time at which the media in this packet, scheduled for play out at the time +indicated by timestamp, was initially sampled or generated. It may be useful for +sequencing and synchronization purposes. +*/ +@property(nonatomic, readonly) uint32_t rtpTimestamp; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpSource) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpTransceiver.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpTransceiver.h new file mode 100644 index 00000000..2c5084bb --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCRtpTransceiver.h @@ -0,0 +1,177 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const kRTCRtpTransceiverErrorDomain; + +/** https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiverdirection */ +typedef NS_ENUM(NSInteger, RTCRtpTransceiverDirection) { + RTCRtpTransceiverDirectionSendRecv, + RTCRtpTransceiverDirectionSendOnly, + RTCRtpTransceiverDirectionRecvOnly, + RTCRtpTransceiverDirectionInactive, + RTCRtpTransceiverDirectionStopped +}; + +/** Structure for initializing an RTCRtpTransceiver in a call to + * RTCPeerConnection.addTransceiver. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiverinit + */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpTransceiverInit) : NSObject + +/** Direction of the RTCRtpTransceiver. See RTCRtpTransceiver.direction. */ +@property(nonatomic) RTCRtpTransceiverDirection direction; + +/** The added RTCRtpTransceiver will be added to these streams. */ +@property(nonatomic) NSArray *streamIds; + +/** TODO(bugs.webrtc.org/7600): Not implemented. */ +@property(nonatomic) + NSArray *sendEncodings; + +@end + +@class RTC_OBJC_TYPE(RTCRtpTransceiver); +@class RTC_OBJC_TYPE(RTCRtpCodecCapability); +@class RTC_OBJC_TYPE(RTCRtpHeaderExtensionCapability); + +/** The RTCRtpTransceiver maps to the RTCRtpTransceiver defined by the + * WebRTC specification. A transceiver represents a combination of an + * RTCRtpSender and an RTCRtpReceiver that share a common mid. As defined in + * JSEP, an RTCRtpTransceiver is said to be associated with a media description + * if its mid property is non-nil; otherwise, it is said to be disassociated. + * JSEP: https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24 + * + * Note that RTCRtpTransceivers are only supported when using + * RTCPeerConnection with Unified Plan SDP. + * + * WebRTC specification for RTCRtpTransceiver, the JavaScript analog: + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCRtpTransceiver) + + /** Media type of the transceiver. The sender and receiver will also have + * this type. + */ + @property(nonatomic, readonly) RTCRtpMediaType mediaType; + +/** The mid attribute is the mid negotiated and present in the local and + * remote descriptions. Before negotiation is complete, the mid value may be + * nil. After rollbacks, the value may change from a non-nil value to nil. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-mid + */ +@property(nonatomic, readonly) NSString *mid; + +/** The sender attribute exposes the RTCRtpSender corresponding to the RTP + * media that may be sent with the transceiver's mid. The sender is always + * present, regardless of the direction of media. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-sender + */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCRtpSender) * sender; + +/** The receiver attribute exposes the RTCRtpReceiver corresponding to the RTP + * media that may be received with the transceiver's mid. The receiver is + * always present, regardless of the direction of media. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-receiver + */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCRtpReceiver) * receiver; + +/** The isStopped attribute indicates that the sender of this transceiver will + * no longer send, and that the receiver will no longer receive. It is true if + * either stop has been called or if setting the local or remote description + * has caused the RTCRtpTransceiver to be stopped. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stopped + */ +@property(nonatomic, readonly) BOOL isStopped; + +/** The direction attribute indicates the preferred direction of this + * transceiver, which will be used in calls to createOffer and createAnswer. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction + */ +@property(nonatomic, readonly) RTCRtpTransceiverDirection direction; + +/** It will contain all the RTP header extensions that are supported. + * The direction attribute for all extensions that are mandatory to use MUST be + * initialized to an appropriate value other than + * RTCRtpTransceiverDirectionStopped. The direction attribute for extensions + * that will not be offered by default in an initial offer MUST be initialized + * to RTCRtpTransceiverDirectionStopped. + */ +@property(nonatomic, readonly, copy) + NSArray + *headerExtensionsToNegotiate; +@property(nonatomic, readonly, copy) + NSArray + *negotiatedHeaderExtensions; + +/** The currentDirection attribute indicates the current direction negotiated + * for this transceiver. If this transceiver has never been represented in an + * offer/answer exchange, or if the transceiver is stopped, the value is not + * present and this method returns NO. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-currentdirection + */ +- (BOOL)currentDirection:(RTCRtpTransceiverDirection *)currentDirectionOut; + +/** The stop method irreversibly stops the RTCRtpTransceiver. The sender of + * this transceiver will no longer send, the receiver will no longer receive. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stop + */ +- (void)stopInternal; + +/** The setCodecPreferences method overrides the default codec preferences used + * by WebRTC for this transceiver. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-setcodecpreferences + */ +- (BOOL)setCodecPreferences: + (NSArray *_Nullable)codecs + error:(NSError **_Nullable)error; + +/** Deprecated version of [RTCRtpTransceiver setCodecPreferences:error:] */ +- (void)setCodecPreferences: + (NSArray *_Nullable)codecs + RTC_OBJC_DEPRECATED("Use setCodecPreferences:error: instead."); + +/** The setHeaderExtensionsToNegotiate method overrides the default header + * extensions used by WebRTC for this transceiver. + * https://w3c.github.io/webrtc-extensions/#ref-for-dom-rtcrtptransceiver-setheaderextensionstonegotiate + */ +- (BOOL)setHeaderExtensionsToNegotiate: + (NSArray *) + extensions + error:(NSError **)error; + +/** An update of directionality does not take effect immediately. Instead, + * future calls to createOffer and createAnswer mark the corresponding media + * descriptions as sendrecv, sendonly, recvonly, or inactive. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction + */ +- (void)setDirection:(RTCRtpTransceiverDirection)direction + error:(NSError **)error; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCRtpTransceiver) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSSLAdapter.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSSLAdapter.h new file mode 100644 index 00000000..a0da827c --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSSLAdapter.h @@ -0,0 +1,20 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +/** + * Initialize and clean up the SSL library. Failure is fatal. These call the + * corresponding functions in webrtc/rtc_base/ssladapter.h. + */ +RTC_EXTERN BOOL RTCInitializeSSL(void); +RTC_EXTERN BOOL RTCCleanupSSL(void); diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSSLCertificateVerifier.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSSLCertificateVerifier.h new file mode 100644 index 00000000..d5acafad --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSSLCertificateVerifier.h @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT @protocol RTC_OBJC_TYPE +(RTCSSLCertificateVerifier) + + /** The certificate to verify */ + - (BOOL)verify : (NSData *)derCertificate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSessionDescription.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSessionDescription.h new file mode 100644 index 00000000..e513c168 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCSessionDescription.h @@ -0,0 +1,49 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +/** + * Represents the session description type. This exposes the same types that are + * in C++, which doesn't include the rollback type that is in the W3C spec. + */ +typedef NS_ENUM(NSInteger, RTCSdpType) { + RTCSdpTypeOffer, + RTCSdpTypePrAnswer, + RTCSdpTypeAnswer, + RTCSdpTypeRollback, +}; + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCSessionDescription) : NSObject + +/** The type of session description. */ +@property(nonatomic, readonly) RTCSdpType type; + +/** The SDP string representation of this session description. */ +@property(nonatomic, readonly) NSString *sdp; + +- (instancetype)init NS_UNAVAILABLE; + +/** Initialize a session description with a type and SDP string. */ +- (instancetype)initWithType:(RTCSdpType)type + sdp:(NSString *)sdp NS_DESIGNATED_INITIALIZER; + ++ (NSString *)stringForType:(RTCSdpType)type; + ++ (RTCSdpType)typeForString:(NSString *)string; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCStatisticsReport.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCStatisticsReport.h new file mode 100644 index 00000000..785d9499 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCStatisticsReport.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +@class RTC_OBJC_TYPE(RTCStatistics); + +NS_ASSUME_NONNULL_BEGIN + +/** A statistics report. Encapsulates a number of RTCStatistics objects. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCStatisticsReport) : NSObject + +/** The timestamp of the report in microseconds since 1970-01-01T00:00:00Z. */ +@property(nonatomic, readonly) CFTimeInterval timestamp_us; + +/** RTCStatistics objects by id. */ +@property(nonatomic, readonly) + NSDictionary *statistics; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +/** A part of a report (a subreport) covering a certain area. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCStatistics) : NSObject + +/** The id of this subreport, e.g. "RTCMediaStreamTrack_receiver_2". */ +@property(nonatomic, readonly) NSString *id; + +/** The timestamp of the subreport in microseconds since 1970-01-01T00:00:00Z. + */ +@property(nonatomic, readonly) CFTimeInterval timestamp_us; + +/** The type of the subreport, e.g. "track", "codec". */ +@property(nonatomic, readonly) NSString *type; + +/** The keys and values of the subreport, e.g. "totalFramesDuration = 5.551". + The values are either NSNumbers or NSStrings or NSArrays encapsulating + NSNumbers or NSStrings, or NSDictionary of NSString keys to NSNumber values. + */ +@property(nonatomic, readonly) NSDictionary *values; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCTracing.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCTracing.h new file mode 100644 index 00000000..899a5700 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCTracing.h @@ -0,0 +1,21 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +RTC_EXTERN void RTCSetupInternalTracer(void); +/** Starts capture to specified file. Must be a valid writable path. + * Returns YES if capture starts. + */ +RTC_EXTERN BOOL RTCStartInternalCapture(NSString* filePath); +RTC_EXTERN void RTCStopInternalCapture(void); +RTC_EXTERN void RTCShutdownInternalTracer(void); diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCapturer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCapturer.h new file mode 100644 index 00000000..78b1befc --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCapturer.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCVideoCapturer); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoCapturerDelegate) - + (void)capturer + : (RTC_OBJC_TYPE(RTCVideoCapturer) *)capturer didCaptureVideoFrame + : (RTC_OBJC_TYPE(RTCVideoFrame) *)frame; +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoCapturer) : NSObject + +@property(nonatomic, weak) id delegate; + +- (instancetype)initWithDelegate: + (id)delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCodecConstants.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCodecConstants.h new file mode 100644 index 00000000..0277bfc4 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCodecConstants.h @@ -0,0 +1,17 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +RTC_EXTERN NSString* const kRTCVideoCodecVp8Name; +RTC_EXTERN NSString* const kRTCVideoCodecVp9Name; +RTC_EXTERN NSString* const kRTCVideoCodecAv1Name; diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCodecInfo.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCodecInfo.h new file mode 100644 index 00000000..0a2c2859 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoCodecInfo.h @@ -0,0 +1,43 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Holds information to identify a codec. Corresponds to + * webrtc::SdpVideoFormat. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoCodecInfo) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithName:(NSString *)name; + +- (instancetype)initWithName:(NSString *)name + parameters:(nullable NSDictionary *) + parameters; + +- (instancetype)initWithName:(NSString *)name + parameters:(NSDictionary *)parameters + scalabilityModes:(NSArray *)scalabilityModes + NS_DESIGNATED_INITIALIZER; + +- (BOOL)isEqualToCodecInfo:(RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info; + +@property(nonatomic, readonly) NSString *name; +@property(nonatomic, readonly) NSDictionary *parameters; +@property(nonatomic, readonly) NSArray *scalabilityModes; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoder.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoder.h new file mode 100644 index 00000000..e7bd6365 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoder.h @@ -0,0 +1,41 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Callback block for decoder. */ +typedef void (^RTCVideoDecoderCallback)(RTC_OBJC_TYPE(RTCVideoFrame) * frame); + +/** Protocol for decoder implementations. */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoDecoder) + + - (void)setCallback : (RTCVideoDecoderCallback)callback; +- (NSInteger)startDecodeWithNumberOfCores:(int)numberOfCores; +- (NSInteger)releaseDecoder; +// TODO(bugs.webrtc.org/15444): Remove obsolete missingFrames param. +- (NSInteger)decode:(RTC_OBJC_TYPE(RTCEncodedImage) *)encodedImage + missingFrames:(BOOL)missingFrames + codecSpecificInfo:(nullable id)info + renderTimeMs:(int64_t)renderTimeMs; +- (NSString *)implementationName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderAV1.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderAV1.h new file mode 100644 index 00000000..c6cb38c2 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderAV1.h @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderAV1) : NSObject + +/* This returns a AV1 decoder that can be returned from a RTCVideoDecoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoDecoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (id)av1Decoder; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderFactory.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderFactory.h new file mode 100644 index 00000000..5eb598a0 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderFactory.h @@ -0,0 +1,33 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** RTCVideoDecoderFactory is an Objective-C version of + * webrtc::VideoDecoderFactory. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoDecoderFactory) + + - (nullable id)createDecoder + : (RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info; +- (NSArray *) + supportedCodecs; // TODO(andersc): "supportedFormats" instead? + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderFactoryH264.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderFactoryH264.h new file mode 100644 index 00000000..d5ce05ed --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderFactoryH264.h @@ -0,0 +1,18 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderFactoryH264) : NSObject +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderH264.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderH264.h new file mode 100644 index 00000000..f1c84689 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderH264.h @@ -0,0 +1,18 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderH264) : NSObject +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderVP8.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderVP8.h new file mode 100644 index 00000000..9e3af0dc --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderVP8.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderVP8) : NSObject + +/* This returns a VP8 decoder that can be returned from a RTCVideoDecoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoDecoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (id)vp8Decoder; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderVP9.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderVP9.h new file mode 100644 index 00000000..6c76ebad --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoDecoderVP9.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoDecoderVP9) : NSObject + +/* This returns a VP9 decoder that can be returned from a RTCVideoDecoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoDecoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (id)vp9Decoder; + ++ (bool)isSupported; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoder.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoder.h new file mode 100644 index 00000000..1b0a16d9 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoder.h @@ -0,0 +1,62 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Callback block for encoder. */ +typedef BOOL (^RTCVideoEncoderCallback)( + RTC_OBJC_TYPE(RTCEncodedImage) * frame, + id info); + +/** Protocol for encoder implementations. */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoEncoder) + + - (void)setCallback : (nullable RTCVideoEncoderCallback)callback; +- (NSInteger)startEncodeWithSettings: + (RTC_OBJC_TYPE(RTCVideoEncoderSettings) *)settings + numberOfCores:(int)numberOfCores; +- (NSInteger)releaseEncoder; +- (NSInteger)encode:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame + codecSpecificInfo:(nullable id)info + frameTypes:(NSArray *)frameTypes; +- (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate; +- (NSString *)implementationName; + +/** Returns QP scaling settings for encoder. The quality scaler adjusts the + * resolution in order to keep the QP from the encoded images within the given + * range. Returning nil from this function disables quality scaling. */ +- (nullable RTC_OBJC_TYPE(RTCVideoEncoderQpThresholds) *)scalingSettings; + +/** Resolutions should be aligned to this value. */ +@property(nonatomic, readonly) NSInteger resolutionAlignment; + +/** If enabled, resolution alignment is applied to all simulcast layers + simultaneously so that when scaled, all resolutions comply with + 'resolutionAlignment'. */ +@property(nonatomic, readonly) BOOL applyAlignmentToAllSimulcastLayers; + +/** If YES, the receiver is expected to resample/scale the source texture to the + expected output size. */ +@property(nonatomic, readonly) BOOL supportsNativeHandle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderAV1.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderAV1.h new file mode 100644 index 00000000..4c97563b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderAV1.h @@ -0,0 +1,32 @@ +/* + * Copyright 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderAV1) : NSObject + +/* This returns a AV1 encoder that can be returned from a RTCVideoEncoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoEncoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (nonnull id)av1Encoder; + +/* Returns list of scalability modes supported by the encoder that can be + * created with `av1Encoder` method above. + */ ++ (nonnull NSArray*)supportedScalabilityModes; + ++ (bool)isSupported; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderFactory.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderFactory.h new file mode 100644 index 00000000..85745458 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderFactory.h @@ -0,0 +1,78 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** RTCVideoEncoderFactory is an Objective-C version of + webrtc::VideoEncoderFactory::VideoEncoderSelector. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoEncoderSelector) + + - (void)registerCurrentEncoderInfo + : (RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info; +- (nullable RTC_OBJC_TYPE(RTCVideoCodecInfo) *)encoderForBitrate: + (NSInteger)bitrate; +- (nullable RTC_OBJC_TYPE(RTCVideoCodecInfo) *)encoderForBrokenEncoder; + +@optional +- (nullable RTC_OBJC_TYPE(RTCVideoCodecInfo) *)encoderForResolutionChangeBySize: + (CGSize)size; + +@end + +/** RTCVideoEncoderCodecSupport is an Objective-C version of + * webrtc::VideoEncoderFactory::CodecSupport. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderCodecSupport) : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithSupported:(bool)isSupported; +- (instancetype)initWithSupported:(bool)isSupported + isPowerEfficient:(bool)isPowerEfficient + NS_DESIGNATED_INITIALIZER; + +@property(nonatomic, readonly) bool isSupported; +@property(nonatomic, readonly) bool isPowerEfficient; + +@end + +/** RTCVideoEncoderFactory is an Objective-C version of + * webrtc::VideoEncoderFactory. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoEncoderFactory) + + - (nullable id)createEncoder + : (RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info; +- (NSArray *) + supportedCodecs; // TODO(andersc): "supportedFormats" instead? + +@optional +- (NSArray *)implementations; +- (nullable id)encoderSelector; +/* TODO: b/299588022 - move to non-optional section when implemented by all + * derived classes. */ +- (RTC_OBJC_TYPE(RTCVideoEncoderCodecSupport) *) + queryCodecSupport:(RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info + scalabilityMode:(nullable NSString *)scalabilityMode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderFactoryH264.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderFactoryH264.h new file mode 100644 index 00000000..ac9bac8a --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderFactoryH264.h @@ -0,0 +1,18 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderFactoryH264) : NSObject +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderH264.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderH264.h new file mode 100644 index 00000000..37ff4e4b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderH264.h @@ -0,0 +1,22 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderH264) : NSObject + +- (instancetype)initWithCodecInfo:(RTC_OBJC_TYPE(RTCVideoCodecInfo) *)codecInfo; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderQpThresholds.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderQpThresholds.h new file mode 100644 index 00000000..818c2728 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderQpThresholds.h @@ -0,0 +1,29 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** QP thresholds for encoder. Corresponds to + * webrtc::VideoEncoder::QpThresholds. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderQpThresholds) : NSObject + +- (instancetype)initWithThresholdsLow:(NSInteger)low high:(NSInteger)high; + +@property(nonatomic, readonly) NSInteger low; +@property(nonatomic, readonly) NSInteger high; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderSettings.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderSettings.h new file mode 100644 index 00000000..efbdb5ee --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderSettings.h @@ -0,0 +1,42 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, RTCVideoCodecMode) { + RTCVideoCodecModeRealtimeVideo, + RTCVideoCodecModeScreensharing, +}; + +/** Settings for encoder. Corresponds to webrtc::VideoCodec. */ +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderSettings) : NSObject + +@property(nonatomic, strong) NSString *name; + +@property(nonatomic, assign) unsigned short width; +@property(nonatomic, assign) unsigned short height; + +@property(nonatomic, assign) unsigned int startBitrate; // kilobits/sec. +@property(nonatomic, assign) unsigned int maxBitrate; +@property(nonatomic, assign) unsigned int minBitrate; + +@property(nonatomic, assign) uint32_t maxFramerate; + +@property(nonatomic, assign) unsigned int qpMax; +@property(nonatomic, assign) RTCVideoCodecMode mode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderVP8.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderVP8.h new file mode 100644 index 00000000..789ae28b --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderVP8.h @@ -0,0 +1,30 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderVP8) : NSObject + +/* This returns a VP8 encoder that can be returned from a RTCVideoEncoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoEncoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (nonnull id)vp8Encoder; + +/* Returns list of scalability modes supported by the encoder that can be + * created with `vp8Encoder` method above. + */ ++ (nonnull NSArray*)supportedScalabilityModes; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderVP9.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderVP9.h new file mode 100644 index 00000000..86c3ddf5 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoEncoderVP9.h @@ -0,0 +1,32 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoEncoderVP9) : NSObject + +/* This returns a VP9 encoder that can be returned from a RTCVideoEncoderFactory injected into + * RTCPeerConnectionFactory. Even though it implements the RTCVideoEncoder protocol, it can not be + * used independently from the RTCPeerConnectionFactory. + */ ++ (nullable id)vp9Encoder; + +/* Returns list of scalability modes supported by the encoder that can be + * created with `vp9Encoder` method above. + */ ++ (nonnull NSArray*)supportedScalabilityModes; + ++ (bool)isSupported; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoFrame.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoFrame.h new file mode 100644 index 00000000..c9f7cfc9 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoFrame.h @@ -0,0 +1,64 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, RTCVideoRotation) { + RTCVideoRotation_0 = 0, + RTCVideoRotation_90 = 90, + RTCVideoRotation_180 = 180, + RTCVideoRotation_270 = 270, +}; + +@protocol RTC_OBJC_TYPE +(RTCVideoFrameBuffer); + +// RTCVideoFrame is an ObjectiveC version of webrtc::VideoFrame. +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoFrame) : NSObject + +/** Width without rotation applied. */ +@property(nonatomic, readonly) int width; + +/** Height without rotation applied. */ +@property(nonatomic, readonly) int height; +@property(nonatomic, readonly) RTCVideoRotation rotation; + +/** Timestamp in nanoseconds. */ +@property(nonatomic, readonly) int64_t timeStampNs; + +/** Timestamp 90 kHz. */ +@property(nonatomic, assign) int32_t timeStamp; + +@property(nonatomic, readonly) id buffer; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)new NS_UNAVAILABLE; + +/** Initialize an RTCVideoFrame from a frame buffer, rotation, and timestamp. + */ +- (instancetype)initWithBuffer: + (id)frameBuffer + rotation:(RTCVideoRotation)rotation + timeStampNs:(int64_t)timeStampNs; + +/** Return a frame that is guaranteed to be I420, i.e. it is possible to access + * the YUV data on it. + */ +- (RTC_OBJC_TYPE(RTCVideoFrame) *)newI420VideoFrame; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoFrameBuffer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoFrameBuffer.h new file mode 100644 index 00000000..7efbd2b7 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoFrameBuffer.h @@ -0,0 +1,40 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol RTC_OBJC_TYPE +(RTCI420Buffer); + +// RTCVideoFrameBuffer is an ObjectiveC version of webrtc::VideoFrameBuffer. +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoFrameBuffer) + + @property(nonatomic, readonly) int width; +@property(nonatomic, readonly) int height; + +- (id)toI420; + +@optional +- (id)cropAndScaleWith:(int)offsetX + offsetY:(int)offsetY + cropWidth:(int)cropWidth + cropHeight:(int)cropHeight + scaleWidth:(int)scaleWidth + scaleHeight:(int)scaleHeight; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoRenderer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoRenderer.h new file mode 100644 index 00000000..11828446 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoRenderer.h @@ -0,0 +1,44 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#if TARGET_OS_IPHONE +#import +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RTC_OBJC_TYPE(RTCVideoFrame); + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoRenderer) + + /** The size of the frame. */ + - (void)setSize : (CGSize)size; + +/** The frame to be displayed. */ +- (void)renderFrame:(nullable RTC_OBJC_TYPE(RTCVideoFrame) *)frame; + +@end + +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoViewDelegate) + + - (void)videoView + : (id)videoView didChangeVideoSize + : (CGSize)size; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoSource.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoSource.h new file mode 100644 index 00000000..24285275 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoSource.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_OBJC_EXPORT + +@interface RTC_OBJC_TYPE (RTCVideoSource) : RTC_OBJC_TYPE(RTCMediaSource) + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Calling this function will cause frames to be scaled down to the + * requested resolution. Also, frames will be cropped to match the + * requested aspect ratio, and frames will be dropped to match the + * requested fps. The requested aspect ratio is orientation agnostic and + * will be adjusted to maintain the input orientation, so it doesn't + * matter if e.g. 1280x720 or 720x1280 is requested. + */ +- (void)adaptOutputFormatToWidth:(int)width height:(int)height fps:(int)fps; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoTrack.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoTrack.h new file mode 100644 index 00000000..18368168 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoTrack.h @@ -0,0 +1,38 @@ +/* + * Copyright 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol RTC_OBJC_TYPE +(RTCVideoRenderer); +@class RTC_OBJC_TYPE(RTCPeerConnectionFactory); +@class RTC_OBJC_TYPE(RTCVideoSource); + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCVideoTrack) : RTC_OBJC_TYPE(RTCMediaStreamTrack) + +/** The video source for this video track. */ +@property(nonatomic, readonly) RTC_OBJC_TYPE(RTCVideoSource) *source; + +- (instancetype)init NS_UNAVAILABLE; + +/** Register a renderer that will render all frames received on this track. */ +- (void)addRenderer:(id)renderer; + +/** Deregister a renderer. */ +- (void)removeRenderer:(id)renderer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoViewShading.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoViewShading.h new file mode 100644 index 00000000..afba0154 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCVideoViewShading.h @@ -0,0 +1,39 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * RTCVideoViewShading provides a way for apps to customize the OpenGL(ES + * shaders used in rendering for the RTCEAGLVideoView/RTCNSGLVideoView. + */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCVideoViewShading) + + /** Callback for I420 frames. Each plane is given as a texture. */ + - (void)applyShadingForFrameWithWidth : (int)width height + : (int)height rotation : (RTCVideoRotation)rotation yPlane + : (GLuint)yPlane uPlane : (GLuint)uPlane vPlane : (GLuint)vPlane; + +/** Callback for NV12 frames. Each plane is given as a texture. */ +- (void)applyShadingForFrameWithWidth:(int)width + height:(int)height + rotation:(RTCVideoRotation)rotation + yPlane:(GLuint)yPlane + uvPlane:(GLuint)uvPlane; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCYUVPlanarBuffer.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCYUVPlanarBuffer.h new file mode 100644 index 00000000..20a154c7 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/RTCYUVPlanarBuffer.h @@ -0,0 +1,46 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Protocol for RTCVideoFrameBuffers containing YUV planar data. */ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCYUVPlanarBuffer) + + @property(nonatomic, readonly) int chromaWidth; +@property(nonatomic, readonly) int chromaHeight; +@property(nonatomic, readonly) const uint8_t *dataY; +@property(nonatomic, readonly) const uint8_t *dataU; +@property(nonatomic, readonly) const uint8_t *dataV; +@property(nonatomic, readonly) int strideY; +@property(nonatomic, readonly) int strideU; +@property(nonatomic, readonly) int strideV; + +- (instancetype)initWithWidth:(int)width + height:(int)height + dataY:(const uint8_t *)dataY + dataU:(const uint8_t *)dataU + dataV:(const uint8_t *)dataV; +- (instancetype)initWithWidth:(int)width height:(int)height; +- (instancetype)initWithWidth:(int)width + height:(int)height + strideY:(int)strideY + strideU:(int)strideU + strideV:(int)strideV; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/UIDevice+RTCDevice.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/UIDevice+RTCDevice.h new file mode 100644 index 00000000..4d04f38f --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/UIDevice+RTCDevice.h @@ -0,0 +1,17 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +@interface UIDevice (RTCDevice) + ++ (NSString *)machineName; + +@end diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/WebRTC.h b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/WebRTC.h new file mode 100644 index 00000000..1f88aebd --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Headers/WebRTC.h @@ -0,0 +1,102 @@ +/* + * Copyright 2026 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Info.plist b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Info.plist new file mode 100644 index 00000000..543b9904 Binary files /dev/null and b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Info.plist differ diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Modules/module.modulemap b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Modules/module.modulemap new file mode 100644 index 00000000..cd485a4e --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module WebRTC { + umbrella header "WebRTC.h" + + export * + module * { export * } +} diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/PrivacyInfo.xcprivacy b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..a1f67253 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,32 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + 8FFB.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/WebRTC b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/WebRTC new file mode 100755 index 00000000..d5abdcd9 Binary files /dev/null and b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/WebRTC differ diff --git a/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/_CodeSignature/CodeResources b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/_CodeSignature/CodeResources new file mode 100644 index 00000000..af754486 --- /dev/null +++ b/ios/Vendor/WebRTC.xcframework/ios-x86_64_arm64-simulator/WebRTC.framework/_CodeSignature/CodeResources @@ -0,0 +1,1147 @@ + + + + + files + + Headers/RTCAudioDevice.h + + SDa3ttYPaZ3HCtZvtj8X+kOty+k= + + Headers/RTCAudioSession.h + + illzrztfUK+sPLVYIXtnqYJiNrk= + + Headers/RTCAudioSessionConfiguration.h + + W9Ak2fpOkARa8Mg+sVKN/zVQMb4= + + Headers/RTCAudioSource.h + + kWRBXTR52cjOkiTLpJKLkRYvAss= + + Headers/RTCAudioTrack.h + + 1gbA/Gd5M9EQ65Q1kpFMqN5jWpw= + + Headers/RTCCVPixelBuffer.h + + TnrhRR/q6TN41/MzNipkBF8tJ5Q= + + Headers/RTCCallbackLogger.h + + FyfrWVStkrB3e6MMdUwk4yD+XVg= + + Headers/RTCCameraPreviewView.h + + e4Ib50IU5sxat0ql3rmnyyce7aE= + + Headers/RTCCameraVideoCapturer.h + + b02+7Ncw8ayAmVUV8/J/2HvpGvE= + + Headers/RTCCertificate.h + + 0RzXASkvZF+EC9+z5FITIt/DB+0= + + Headers/RTCCodecSpecificInfo.h + + JPJ/Y62lTfjlHQoLyZg6OnscU/4= + + Headers/RTCCodecSpecificInfoH264.h + + hlmIIY8pumCU3jglGYRuov0CbIw= + + Headers/RTCConfiguration.h + + oqYC846Gjyh1rjwa6EOS3n3UHBg= + + Headers/RTCCryptoOptions.h + + 6gbAx4yKazFMNZb/lrvmL9/YNU4= + + Headers/RTCDataChannel.h + + oc6Lxx/7Bo+P4M+Coq7fu0PdObo= + + Headers/RTCDataChannelConfiguration.h + + Xye9tDtWWH5oPrwy6xFVmD8G40c= + + Headers/RTCDefaultVideoDecoderFactory.h + + N5SFo16d7UEodXnLH56+/i10a/c= + + Headers/RTCDefaultVideoEncoderFactory.h + + hVhr8XCRzmci7UDqSs0nKmgvJ0A= + + Headers/RTCDispatcher.h + + jBJiaGEcN9Kfh0eDfhelrddZP/g= + + Headers/RTCDtmfSender.h + + A/b6SExEe8xAfSU/A+aIgg9RvAw= + + Headers/RTCEAGLVideoView.h + + ZEnBrQ/wbhnLgsxR2eUhDHL9MTY= + + Headers/RTCEncodedImage.h + + 8X1It1oml9x8wQF+CZoWXt9Gz0A= + + Headers/RTCFieldTrials.h + + vBVULzVIfPX4G3ARBLqYnr+pKX4= + + Headers/RTCFileLogger.h + + AOfx5kLiEqNnufBqQy876Z/WXtU= + + Headers/RTCFileVideoCapturer.h + + tO1Ar/XsssKuu3JayIDQsnz3Rlk= + + Headers/RTCH264ProfileLevelId.h + + GGBR3JogG7mE+SBnzoYOgluSqd8= + + Headers/RTCI420Buffer.h + + dar+dhBg1MF7Sq8VW510HwyZrDg= + + Headers/RTCIceCandidate.h + + wm+0GxYxzFbBo0YkdkXGKf0z2sE= + + Headers/RTCIceCandidateErrorEvent.h + + NoxWsYmI0xivzlYKaZpSNPgPaYg= + + Headers/RTCIceServer.h + + XocRgoxRrBESI9plS6MIH9FkzKc= + + Headers/RTCLegacyStatsReport.h + + DCgetEbRttoaiKv0AogakmFNbhQ= + + Headers/RTCLogging.h + + u1BgPF7wgHbNG9jH+6cgw+AI64E= + + Headers/RTCMTLVideoView.h + + pAGF5lC1ljzaEJ5ZlIQewjEN/mU= + + Headers/RTCMacros.h + + xqKtRda91R8Mt+ad4eowtsxq1pg= + + Headers/RTCMediaConstraints.h + + +anD7kUYF8M3mnWHnGwKHgZwrhw= + + Headers/RTCMediaSource.h + + OqvCvLy9KioApHmi/HRXnRZTMK0= + + Headers/RTCMediaStream.h + + ycer8f83EEvDc+knRoSxp1JOUsk= + + Headers/RTCMediaStreamTrack.h + + wxtsJmof7FenlF2B3DvDy0UKRTI= + + Headers/RTCMetrics.h + + ga8wm14NX0gS41HClMutj/4YI1Y= + + Headers/RTCMetricsSampleInfo.h + + J6CuW8ZLmanA19Rg5+ISgkhImdY= + + Headers/RTCMutableI420Buffer.h + + jfmojc9+uBnraNWrrWn69z2UHXU= + + Headers/RTCMutableYUVPlanarBuffer.h + + vBXNZWr98EFrPwuda858ZbuXcfc= + + Headers/RTCNativeI420Buffer.h + + 4iUCxoiX6rujtbIoLQznK1CzZmI= + + Headers/RTCNativeMutableI420Buffer.h + + t0PhIvjS5UaN9ZpjSnmoODG1j1U= + + Headers/RTCNetworkMonitor.h + + 9fPvkllipkpykCKvSRmtTEJTp1Y= + + Headers/RTCPeerConnection.h + + U/uK+jfZdeeYBJNHD6eWOyAQG0o= + + Headers/RTCPeerConnectionFactory.h + + d8/oPaXasPFGVMRt6AfrJuRg84s= + + Headers/RTCPeerConnectionFactoryOptions.h + + tF6GalMyZyMnJKhMUOcmTeISFPw= + + Headers/RTCRtcpParameters.h + + ormnt3mxO9lHmQ6MrX2BV8uDV40= + + Headers/RTCRtpCapabilities.h + + iFPwoUzcyW/ynx3eT3EgwJs4Bd0= + + Headers/RTCRtpCodecCapability.h + + zP46+y11OrnC/ewS6QHU0wo/zJ8= + + Headers/RTCRtpCodecParameters.h + + lVh5tb6fdXYSZLr2yA3AZp32kbY= + + Headers/RTCRtpEncodingParameters.h + + fIw8L4GbSyc2dJw55q+YXQJ3ORA= + + Headers/RTCRtpHeaderExtension.h + + 2X8iT7gZV8C6tuobk3hFs/TrZTQ= + + Headers/RTCRtpHeaderExtensionCapability.h + + 4Y/jEJJapO4DVVYK1r+WYfxSv/c= + + Headers/RTCRtpParameters.h + + V43KWrx0qA4Ci2TrFl5ckC3ALBg= + + Headers/RTCRtpReceiver.h + + ELFv11rpEcVQDotTtRqLCCb1XEM= + + Headers/RTCRtpSender.h + + iPVZCP3lsjS5IhPSZ7qVAGuvwio= + + Headers/RTCRtpSource.h + + ViLhe+maaCbZWTmi0xXko4rPuKE= + + Headers/RTCRtpTransceiver.h + + 5mCvV+92ayXqT9KdEg4zlz1eP1w= + + Headers/RTCSSLAdapter.h + + Js0r3rS8lrBPeCK3xqPWO6zCJsw= + + Headers/RTCSSLCertificateVerifier.h + + FUw6RnF4ubk7dzMio8M2fV2m8nQ= + + Headers/RTCSessionDescription.h + + 3tIwd30U4IzpWZufX/ix+PjsGJQ= + + Headers/RTCStatisticsReport.h + + q9SjHA62NoECuMaKnY/VgdmyEwY= + + Headers/RTCTracing.h + + CyvWa5YMI+Y7f3PSRsxVCRBnkFI= + + Headers/RTCVideoCapturer.h + + dLq+sQ96ZOVlpAquR3hVE1hoY4U= + + Headers/RTCVideoCodecConstants.h + + 6GtG2SMVGrU24808urXoBAA4NKs= + + Headers/RTCVideoCodecInfo.h + + SnTzGOImp+0HQ4TPNMiBdVbUQJE= + + Headers/RTCVideoDecoder.h + + pRFh/SYVyqUDcpNDWtrlCrLK/eU= + + Headers/RTCVideoDecoderAV1.h + + MUvqBwg+OgIe82/xXO4fNqmTHyI= + + Headers/RTCVideoDecoderFactory.h + + 4MD+m9s08u9P8M8kzGfYUcSV4yc= + + Headers/RTCVideoDecoderFactoryH264.h + + wmC87bRSHP6C9NwSD3xheQLoZeY= + + Headers/RTCVideoDecoderH264.h + + fjwRO4gnXWncajM70eSflV6S54w= + + Headers/RTCVideoDecoderVP8.h + + fTAuQ1zGKhgQf5WgxN79Su73/TY= + + Headers/RTCVideoDecoderVP9.h + + p46nJliqVYeL6W7kJUX/2F8fs1Q= + + Headers/RTCVideoEncoder.h + + rOApyBL0mJ5yyKiNKOubNObTPgQ= + + Headers/RTCVideoEncoderAV1.h + + OvYOg0CDBdvN4TMlo3KTmKjCkhw= + + Headers/RTCVideoEncoderFactory.h + + 2r71JvZlBwh5knhT6Zfr1plrChI= + + Headers/RTCVideoEncoderFactoryH264.h + + JzjYUizVdlvq0c7CHZDKD20F7GU= + + Headers/RTCVideoEncoderH264.h + + VnClhKvApCnhhbvGFBTB3bIQiWI= + + Headers/RTCVideoEncoderQpThresholds.h + + iAMxYHhUH9LZ1n1vaSVtBXL9UeY= + + Headers/RTCVideoEncoderSettings.h + + JJ/iOANj3nHmnpUM7/3sfB1MfuU= + + Headers/RTCVideoEncoderVP8.h + + atsZbBgo5cF3HVU2qMJ8zH1vCc4= + + Headers/RTCVideoEncoderVP9.h + + HXkBdajiFEoSiRetjOGCNzSyLtI= + + Headers/RTCVideoFrame.h + + ZSQQn4yLkSTDa7ntYDfQucraiyA= + + Headers/RTCVideoFrameBuffer.h + + Op33gd0R3wjuwA0NGylZiejRNYg= + + Headers/RTCVideoRenderer.h + + rKb9yg+l7ONaPnKnDrQ7JBTuC4A= + + Headers/RTCVideoSource.h + + 6uvT3M2TTtOk3Q+bMcHldFcedbo= + + Headers/RTCVideoTrack.h + + 5N6rMa77j33jRBGtALAL8UR7F78= + + Headers/RTCVideoViewShading.h + + sDdwYRdrjmdVK2+FdTdKe6dYdkc= + + Headers/RTCYUVPlanarBuffer.h + + Ixym0+dsM67/nkpoKOnN3+/E7j8= + + Headers/UIDevice+RTCDevice.h + + ZaWTrAwhL8JtYTsOW2E8GOI9uzM= + + Headers/WebRTC.h + + gvRHeJaRBqIL5GbJtMXUHsDE7pY= + + Info.plist + + HLSNT9imP7GBamgxg2qKC5QVpwk= + + Modules/module.modulemap + + 1xNYDUWHSIBqPL509v2gAYBnegU= + + PrivacyInfo.xcprivacy + + awkbSyTtHswWIztjoenYOFh5sXk= + + + files2 + + Headers/RTCAudioDevice.h + + hash2 + + i0vdYKs4wKCS2ksa1gZJRqd7D/6VwuEWoWQ8jYyj2D0= + + + Headers/RTCAudioSession.h + + hash2 + + e2STKXOGtnsMluXJlbTXzQB3+WsgDNOszDg9gLvJBTg= + + + Headers/RTCAudioSessionConfiguration.h + + hash2 + + U6jP5fRy3hnxRmxsleutBXF3iK3CFdt1hK2HlPwahI0= + + + Headers/RTCAudioSource.h + + hash2 + + pvd5Ijz2WJG9WWTlFqNA4yu6KFFHuDEWkd/KAIpyRFA= + + + Headers/RTCAudioTrack.h + + hash2 + + RDw2VcWZAnSPdymfMqtnSqm+pokooHZ+OMpzwa6gSc0= + + + Headers/RTCCVPixelBuffer.h + + hash2 + + v0xVE7+a0xpZ0Yluzy6eJyHSOc7qmlqyj7uWzO0ybEg= + + + Headers/RTCCallbackLogger.h + + hash2 + + L51/evkxcOmTv8eHLQsO0WdBjIa/ZMcI+iI10hyL1FE= + + + Headers/RTCCameraPreviewView.h + + hash2 + + v7vYmQN3PPitOis2VcKfdUCnIrZCmhErml/YLvmailo= + + + Headers/RTCCameraVideoCapturer.h + + hash2 + + c6TQLEpaUOT6qKoWbAw0saEO4jT9SKbb4Q9MyekpzvU= + + + Headers/RTCCertificate.h + + hash2 + + efu1XVj96oM+iAu6zZuxGN25qlRXNXMMWIhgbW8C3Mo= + + + Headers/RTCCodecSpecificInfo.h + + hash2 + + LAv8VFZujIM6C4ZBvlAF6k4mRsroY9D/CdQFLMZ5AQQ= + + + Headers/RTCCodecSpecificInfoH264.h + + hash2 + + Bf3/bcIxScIJbw/gT3sVK+jGHgjy6PCN72SyTeCteQ0= + + + Headers/RTCConfiguration.h + + hash2 + + 2ntgxcJTLTyeHcOlEVZ9xsMwQ3opY1jqv8EeFMovs14= + + + Headers/RTCCryptoOptions.h + + hash2 + + ZrfEBH/STCHu74gybJoFz3bY6Yc6tPQapRv1DfKvgv8= + + + Headers/RTCDataChannel.h + + hash2 + + PDXOYVzpe8sNjN19A09UtClfwhOCbNfw9HRMu9idLLA= + + + Headers/RTCDataChannelConfiguration.h + + hash2 + + b/tpKBZ/rCykRg4b925vYASPz14LIgQIL2PhOPyAAlE= + + + Headers/RTCDefaultVideoDecoderFactory.h + + hash2 + + IX6sXEYcHLKF0rikd/87VM+P50nbEIGovBUYpoWkZCg= + + + Headers/RTCDefaultVideoEncoderFactory.h + + hash2 + + 6V71PopZ/RoEKL3uQ0kCXsATFljFRgYOwGvID9oLZOQ= + + + Headers/RTCDispatcher.h + + hash2 + + 52khygoHBkycAJZNFrZ8ZGozwDqgBKVdGz65tJWZOVg= + + + Headers/RTCDtmfSender.h + + hash2 + + +ctnrLHvobmZt5/gN6YZco8x6jD+LFJe5Qq3RrjXW5c= + + + Headers/RTCEAGLVideoView.h + + hash2 + + k3dKHN3I9yAbqK8B9Tg2kda75hKhiOmomZGWMO8WDUA= + + + Headers/RTCEncodedImage.h + + hash2 + + rq9LtEiYzkDk/YHa3u70i+AXh7GJWdcTdU0RpHYDUWA= + + + Headers/RTCFieldTrials.h + + hash2 + + qDTzgTqXKDu3awI8Wcks1uM+vouUqVbfm9zb3WI/FeY= + + + Headers/RTCFileLogger.h + + hash2 + + iO9YLol2Igo1E9n4N+GU3HDpVsZoAZ0XK7YPTQZzPxo= + + + Headers/RTCFileVideoCapturer.h + + hash2 + + lCMtolLtUYtIIJsRWrQnyLK141fkMTSCJSSavHImfZ8= + + + Headers/RTCH264ProfileLevelId.h + + hash2 + + arLgp2vFNayn80CIDmaCbewkHD+EXlKMYiAo2spw6Hs= + + + Headers/RTCI420Buffer.h + + hash2 + + 2GjuoMif7cJmZim5h7JIUv6GA8ShMctCVy264K2YI4k= + + + Headers/RTCIceCandidate.h + + hash2 + + mRyv70AnAWA/4b2VceESbxyzrY9eKZKS1BXgAA7HJPw= + + + Headers/RTCIceCandidateErrorEvent.h + + hash2 + + omEpnEQQv9zySikonxi7cQbNaLriC5DD34qHpkx6jdI= + + + Headers/RTCIceServer.h + + hash2 + + lqwpUY11uW9xxMSYhDSWUQuPuyl/lmeSH/O9mCmdxCs= + + + Headers/RTCLegacyStatsReport.h + + hash2 + + 1OhK2zkdadDxIOGk475JAO+a5NhV/KPoJLwizZMib4A= + + + Headers/RTCLogging.h + + hash2 + + c28HCqbu6U/ZPgmosTsDps6yOVDFbx9CZ1iZrKs2thI= + + + Headers/RTCMTLVideoView.h + + hash2 + + Ldb+EpNvGN+1Exrj+Gg4zW41e3CVc2+GDf2H/lsukFY= + + + Headers/RTCMacros.h + + hash2 + + xaFjD0uwGI7l/B7v7xSa9zq2ZumBeCUtnTvOv5opEJQ= + + + Headers/RTCMediaConstraints.h + + hash2 + + j5P08gFtSNlH+rbdZAM90SrsIuqufzopf1x28Cyt4eE= + + + Headers/RTCMediaSource.h + + hash2 + + XyHrJ23zFY3zWmjQ5HXAVwi4sPP1uHg2YmlCGkP/RcU= + + + Headers/RTCMediaStream.h + + hash2 + + pb5NDOespECvBpprNzdazmrJ9aVeGL5ZbYXGqvOGX0s= + + + Headers/RTCMediaStreamTrack.h + + hash2 + + gvV+sNCUPljxwt597vGVY7hwXLoz0m7Tei0zETIzyCc= + + + Headers/RTCMetrics.h + + hash2 + + g0XBi23pe7xVpPbh2f88/37+GZEM9tl/kOD52astBwA= + + + Headers/RTCMetricsSampleInfo.h + + hash2 + + 9CQAch8vwpWCQ9f+Z87w8hdRlQHY/p3iZoNpHVlAnHw= + + + Headers/RTCMutableI420Buffer.h + + hash2 + + OazV1AH5QsDB9JkbZsw+ED8gu1g9mw53R5Gvgi3LO3w= + + + Headers/RTCMutableYUVPlanarBuffer.h + + hash2 + + UcTTcDoQ5onI+r4JnG5wkPbRhlhMUsqtK1k7n8kU01Q= + + + Headers/RTCNativeI420Buffer.h + + hash2 + + qHHk0DubKKLyRM+PRIRLfW1jL+rdzIZASraVUOIDQ20= + + + Headers/RTCNativeMutableI420Buffer.h + + hash2 + + oEAqhIo70mX+TYN/KtKfRJ765uo8EQwpHEHqijwQPDQ= + + + Headers/RTCNetworkMonitor.h + + hash2 + + g/PxiCGPuQFd60rYGrw0+ZShOY8+wRwuRKVfSAPM73c= + + + Headers/RTCPeerConnection.h + + hash2 + + mKqTUj7XAuBnb3a3KdhAi1XyG+atjCZhmHBLNacF52U= + + + Headers/RTCPeerConnectionFactory.h + + hash2 + + EVwuGNtRYN5LKgdVmCTHfu0W+oU3PUiwJXhrw7kFa9U= + + + Headers/RTCPeerConnectionFactoryOptions.h + + hash2 + + Xv7MAubEYMQg6U3La6BmP1lNwtQXrnSYk5Mde2j1XF4= + + + Headers/RTCRtcpParameters.h + + hash2 + + IKpyvBoFR/r/pwoZAm6rm5NG8I5WT9HtCt2nxkH9TD8= + + + Headers/RTCRtpCapabilities.h + + hash2 + + St8yejcJkm0Z3IdhmlWr56UB1+rJd9tJ7XApk00qVBw= + + + Headers/RTCRtpCodecCapability.h + + hash2 + + swn7VkKh7I5L/alKgsYdPvZhUGBvtbzfMNTjp0Ybgoc= + + + Headers/RTCRtpCodecParameters.h + + hash2 + + /p5QIlOzM9iRPU4gdo9jrSCKq6aL/nmfS3QMynD+Ri0= + + + Headers/RTCRtpEncodingParameters.h + + hash2 + + 3nl7l83Jt7YqhQy6ysoCGOyCdYVeDn1A7Z23/U6GBkI= + + + Headers/RTCRtpHeaderExtension.h + + hash2 + + FvTi4GGbq/bIwG/KXpFSNeJ7fXVKdEAP3SbfxywJE2k= + + + Headers/RTCRtpHeaderExtensionCapability.h + + hash2 + + olt8xG45rl7z4KDL+6dT76V9Xa8zKVEer8acWKYLdHU= + + + Headers/RTCRtpParameters.h + + hash2 + + kADFh4zvcNzyrc01XuG/ljT0SEibqyZsLN9LN6VV5Wo= + + + Headers/RTCRtpReceiver.h + + hash2 + + xVervVhWXLEHjY25IYkWVRYNOpTE8dL1C4vwbALC53A= + + + Headers/RTCRtpSender.h + + hash2 + + LcvzugDcvVRQ55pomY0IJRbrluNnB9ZKqw2l7rK5uyA= + + + Headers/RTCRtpSource.h + + hash2 + + 4hRzvC4+eDnOgQhTdXUCfV4smff3i6W4kN0d4l39YuU= + + + Headers/RTCRtpTransceiver.h + + hash2 + + gXw0sHI0ssXe2EPSdjp6PrQF+KQdWLmi9uaMoD/5KGY= + + + Headers/RTCSSLAdapter.h + + hash2 + + PD4XyT78MhAS2o0ukfT1A0M8hvpm28qELdLnsRVEhQE= + + + Headers/RTCSSLCertificateVerifier.h + + hash2 + + Z+nKcfNs8NrdpvZevMCMDmzCLFfRR7rMXgBFD0ZQn30= + + + Headers/RTCSessionDescription.h + + hash2 + + 4u2g31ckIBRMZvUXNJUEBxum4xtQB5bFe9yrLRFagSM= + + + Headers/RTCStatisticsReport.h + + hash2 + + MvAA9nC0ukI69Vvtn1nA59xZzpwb5UgjOhIlCTwbcGw= + + + Headers/RTCTracing.h + + hash2 + + +wTGF4uJLzFpqXUIgKOmWt9paAZTNGeUOpLgoVY5mKs= + + + Headers/RTCVideoCapturer.h + + hash2 + + Ty3ZTV4mZdi1UNa1sxURYhp3ApR8WRltYCROAT8UcmQ= + + + Headers/RTCVideoCodecConstants.h + + hash2 + + 3nvv8xfXbNQcRQK8NG1sEMGZTgIwpRSvL8Xj9UOXyd4= + + + Headers/RTCVideoCodecInfo.h + + hash2 + + QzoB/OYab/PGF9OT/NJ4emxbvE4lIvkj7tHds7rrSOk= + + + Headers/RTCVideoDecoder.h + + hash2 + + 4gqe2K8CsXu+WGJoGokYNXCjES/ynHgZixIbVVLuwjQ= + + + Headers/RTCVideoDecoderAV1.h + + hash2 + + y+62skJ3eVWoF7FcGVEF6sBqh2klm4RsSTZgBV1yee8= + + + Headers/RTCVideoDecoderFactory.h + + hash2 + + W+fTBdkDxeA9CLt6XSQpNZwmJGGvKTYIbwEMS2b6/YQ= + + + Headers/RTCVideoDecoderFactoryH264.h + + hash2 + + F0s/aE3e/6muWY9WgUGBcXukxsDBZshaP/z7huM7/U8= + + + Headers/RTCVideoDecoderH264.h + + hash2 + + QL8XJ1fLBNl16sSTjj7Qtl5kNVCqf3VEiJt8ayQMEOM= + + + Headers/RTCVideoDecoderVP8.h + + hash2 + + 5kma4+2VY9T+ERaN9CgMdfOLIFqObTlz0hoDN/DmKkU= + + + Headers/RTCVideoDecoderVP9.h + + hash2 + + hMsDqEgCLpTdTxb4VTb7EXZRgRJ6qOj/x57mFwZppF8= + + + Headers/RTCVideoEncoder.h + + hash2 + + MycT2kz1ZfC9qNBRRI313f2WAqLpAhS38JI2N1BDC7o= + + + Headers/RTCVideoEncoderAV1.h + + hash2 + + sZJLJVhv4oGRP1OPKKUC1twCvDJhiJunAxpdSrEVU98= + + + Headers/RTCVideoEncoderFactory.h + + hash2 + + Kex//yh1rNasFEi6K9u1MFH3MudD/+O1lxonvhXuiTI= + + + Headers/RTCVideoEncoderFactoryH264.h + + hash2 + + /SofeJbTcN5W92S+sI7qANltONBGl4XM9HOyRBy7mGw= + + + Headers/RTCVideoEncoderH264.h + + hash2 + + 9S2bLRsDQxAEV4kvuPVsVRwfHKI7Ied0qZRbhc2A7kA= + + + Headers/RTCVideoEncoderQpThresholds.h + + hash2 + + FoRGOGtKYOnOEqmDrFv+Aua1IICbqizUf4yQwF0XVtc= + + + Headers/RTCVideoEncoderSettings.h + + hash2 + + nO6HqW9ivexlzyaHxl4xBarsvA1n7AwxAwkIPX1LiFA= + + + Headers/RTCVideoEncoderVP8.h + + hash2 + + W3N6e+M8OWwO1rDp02NWAo3th8iEms5loKli35NPvTg= + + + Headers/RTCVideoEncoderVP9.h + + hash2 + + 1IVdqZMyZt7ZeSTQC7bi3+j3+LOpUxNepnKikS/GNkc= + + + Headers/RTCVideoFrame.h + + hash2 + + 8ekuVFgSoJsRlWSa1Gg2D32dx0/hU9l4gKpU5wzYI7c= + + + Headers/RTCVideoFrameBuffer.h + + hash2 + + TeCL8PDEwO2zlZIso/ZlketsXqMttxa/ABaiKUaDLko= + + + Headers/RTCVideoRenderer.h + + hash2 + + Ojj/mRFppqxJAmwFiT84A9SPzRL3dgUvAkmLAl9XzII= + + + Headers/RTCVideoSource.h + + hash2 + + 8lr+RU+yeqR7NwSDFapNn88A+wURVxX6xnjpaaAm+78= + + + Headers/RTCVideoTrack.h + + hash2 + + 9vwWtxa+vYbL4jip6QlaMMvJ7KYFBNM49ff87es585w= + + + Headers/RTCVideoViewShading.h + + hash2 + + VHRVcg+gW3U/qWlIDvBErAW+tmp59DMXJ614WWFfNRU= + + + Headers/RTCYUVPlanarBuffer.h + + hash2 + + uwTwFMg+MSnPLR4CNVtZZuOWbzYrqpdZOjenUIsck1A= + + + Headers/UIDevice+RTCDevice.h + + hash2 + + 7WQDmIM/w6GmOI6jWG7cHuNF/AJY6o+GAvKY+GRnV1w= + + + Headers/WebRTC.h + + hash2 + + 8WY6Z3re4VPMkDyRJONLP+jyARFRqiZ1G6pV5vcAYQ8= + + + Modules/module.modulemap + + hash2 + + PLZxtJCKd+1m+jcvXSm3k5+vapqC3+duCEzFT2qkhzk= + + + PrivacyInfo.xcprivacy + + hash2 + + 65M38t7lmS90risQ949SbT7sHajP3gKRMQgcCxhXlhQ= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/server/Cargo.lock b/server/Cargo.lock index 025b0970..341ce51a 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -360,12 +360,24 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -1157,6 +1169,18 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1366,6 +1390,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "nix" version = "0.26.4" @@ -1679,6 +1713,21 @@ dependencies = [ "syn", ] +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "qrcode" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" +dependencies = [ + "image", +] + [[package]] name = "quick-xml" version = "0.39.4" @@ -2117,6 +2166,7 @@ dependencies = [ "libc", "plist", "prost", + "qrcode", "roxmltree", "serde", "serde_json", diff --git a/server/Cargo.toml b/server/Cargo.toml index 8f90f63c..26701016 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -16,6 +16,7 @@ http = "1.1" libc = "0.2" plist = "1.7" prost = "0.13" +qrcode = "0.14" roxmltree = "0.20" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/server/src/api/routes.rs b/server/src/api/routes.rs index 36e43d33..6ac193c4 100644 --- a/server/src/api/routes.rs +++ b/server/src/api/routes.rs @@ -880,7 +880,11 @@ async fn pair_browser( if !auth::pairing_code_matches(&state.config, &payload.code) { return auth::unauthorized_response(&state.config, &headers); } - let mut response = Json(json_value!({ "ok": true })).into_response(); + let mut response = Json(json_value!({ + "ok": true, + "accessToken": state.config.access_token, + })) + .into_response(); auth::append_cors_headers(&state.config, &headers, response.headers_mut()); auth::append_access_cookie(response.headers_mut(), &state.config.access_token); response @@ -899,6 +903,8 @@ async fn health(State(state): State) -> Json { stream_quality_state_value(¤t_stream_quality_state(video_codec.clone())); json(json_value!({ "ok": true, + "serverId": crate::auth::server_identity(&state.config), + "advertiseHost": state.config.advertise_host, "httpPort": state.config.http_port, "timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO).as_secs_f64(), "videoCodec": video_codec, diff --git a/server/src/auth.rs b/server/src/auth.rs index ee97a519..1ef57246 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -28,6 +28,15 @@ pub fn generate_access_token() -> String { hex::encode(bytes) } +pub fn server_identity(config: &Config) -> String { + server_identity_for_token(&config.access_token) +} + +pub fn server_identity_for_token(token: &str) -> String { + let digest = hex::encode(Sha256::digest(token.as_bytes())); + digest[..16].to_owned() +} + pub fn generate_pairing_code() -> String { let token = generate_access_token(); let value = u32::from_str_radix(&token[..8], 16).unwrap_or_default() % 1_000_000; @@ -94,6 +103,10 @@ pub fn unauthorized_response(config: &Config, headers: &HeaderMap) -> Response { StatusCode::UNAUTHORIZED, Json(json!({ "error": "SimDeck API access token is required.", + "ok": false, + "serverId": server_identity(config), + "advertiseHost": config.advertise_host, + "httpPort": config.http_port, })), ) .into_response(); diff --git a/server/src/main.rs b/server/src/main.rs index 19b95851..486a18b8 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -28,6 +28,7 @@ use metrics::counters::Metrics; use native::bridge::{NativeBridge, NativeInputSession}; use native::ffi; use performance::PerformanceRegistry; +use qrcode::{render::unicode, QrCode}; use serde::{Deserialize, Serialize}; use serde_json::Value; use simulators::registry::SessionRegistry; @@ -40,12 +41,12 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket}; #[cfg(unix)] use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; -use std::process::{Command as ProcessCommand, Stdio}; +use std::process::{Child, Command as ProcessCommand, Stdio}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::mpsc; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime}; -use tracing::info; +use tracing::{info, warn}; const RECOVERABLE_RESTART_EXIT_CODE: i32 = 75; const SUPERVISED_DAEMON_METADATA_PID_ENV: &str = "SIMDECK_DAEMON_METADATA_PID"; @@ -98,6 +99,29 @@ enum Command { #[arg(long)] open: bool, }, + Pair { + #[arg( + long, + help = "Defaults to the existing service port, or the next available port near 4310" + )] + port: Option, + #[arg(long, default_value_t = IpAddr::V4(Ipv4Addr::UNSPECIFIED))] + bind: IpAddr, + #[arg(long)] + advertise_host: Option, + #[arg(long)] + client_root: Option, + #[arg(long, value_enum, default_value_t = VideoCodecMode::Auto)] + video_codec: VideoCodecMode, + #[arg(long)] + low_latency: bool, + #[arg(long, value_enum)] + stream_quality: Option, + #[arg(long, value_parser = clap::value_parser!(u32).range(15..=240))] + local_stream_fps: Option, + #[arg(long)] + json: bool, + }, Daemon { #[command(subcommand)] command: DaemonCommand, @@ -1278,6 +1302,198 @@ fn print_daemon_start_result(metadata: &DaemonMetadata, started: bool) -> anyhow })) } +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct PairingAddress { + kind: &'static str, + url: String, +} + +#[derive(Clone, Debug)] +struct PairingTarget { + target: &'static str, + service: Option, + project_root: Option, + pid: Option, + http_url: String, + port: u16, + advertise_host: Option, + server_id: Option, + pairing_code: String, +} + +impl PairingTarget { + fn from_service(result: service::ServiceInstallResult) -> anyhow::Result { + Ok(Self { + target: "service", + service: Some(result.service), + project_root: None, + pid: None, + http_url: http_url_for_host("127.0.0.1", result.port), + port: result.port, + advertise_host: result.advertise_host, + server_id: result + .access_token + .as_deref() + .map(auth::server_identity_for_token), + pairing_code: result + .pairing_code + .context("SimDeck service did not publish a pairing code")?, + }) + } +} + +fn print_pairing_result(target: &PairingTarget, started: bool, json: bool) -> anyhow::Result<()> { + let pairing_code = target.pairing_code.as_str(); + let addresses = pairing_addresses(target); + let primary_url = addresses + .iter() + .find(|address| address.kind != "local") + .or_else(|| addresses.first()) + .map(|address| address.url.as_str()) + .context("No SimDeck pairing address is available")?; + let pair_url = simdeck_pair_url( + primary_url, + pairing_code, + target.server_id.as_deref(), + &addresses, + ); + + if json { + println_json(&serde_json::json!({ + "ok": true, + "target": target.target, + "service": target.service, + "projectRoot": target.project_root, + "pid": target.pid, + "url": target.http_url, + "started": started, + "serverId": target.server_id, + "pairingCode": pairing_code, + "pairUrl": pair_url, + "addresses": addresses, + }))?; + return Ok(()); + } + + println!("🔐 SimDeck pairing"); + println!(); + for address in &addresses { + let label = match address.kind { + "local" => "Local:", + "lan" => "LAN:", + "tailscale" => "Tailscale:", + _ => "URL:", + }; + println!("{:>12} {}", label, address.url); + } + println!("{:>12} {}", "Pair:", format_pairing_code(pairing_code)); + println!(); + println!("Scan this with SimDeck for iOS:"); + println!("{}", render_qr_code(&pair_url)?); + println!("{:>12} {}", "Deep Link:", pair_url); + Ok(()) +} + +fn pairing_addresses(target: &PairingTarget) -> Vec { + let mut addresses = Vec::new(); + push_pairing_address( + &mut addresses, + "local", + http_url_for_host("127.0.0.1", target.port), + ); + + let advertise_host = target + .advertise_host + .as_deref() + .filter(|host| !host.trim().is_empty()); + if let Some(host) = advertise_host { + let kind = host + .parse::() + .ok() + .filter(|ip| is_tailscale_ip(*ip)) + .map(|_| "tailscale") + .unwrap_or("lan"); + if host != "127.0.0.1" && host != "localhost" { + push_pairing_address(&mut addresses, kind, http_url_for_host(host, target.port)); + } + } + + if let Some(lan_ip) = detect_lan_ip() { + push_pairing_address( + &mut addresses, + "lan", + http_url_for_host(&lan_ip.to_string(), target.port), + ); + } + + if let Some(tailscale_ip) = detect_tailscale_ip() { + push_pairing_address( + &mut addresses, + "tailscale", + http_url_for_host(&tailscale_ip.to_string(), target.port), + ); + } + + addresses +} + +fn push_pairing_address(addresses: &mut Vec, kind: &'static str, url: String) { + if addresses.iter().any(|address| address.url == url) { + return; + } + addresses.push(PairingAddress { kind, url }); +} + +fn simdeck_pair_url( + primary_url: &str, + pairing_code: &str, + server_id: Option<&str>, + addresses: &[PairingAddress], +) -> String { + let mut url = format!( + "simdeck://pair?u={}&c={}", + percent_encode(&pairing_address_value(primary_url)), + percent_encode(pairing_code) + ); + if let Some(server_id) = server_id.filter(|value| !value.is_empty()) { + url.push_str("&s="); + url.push_str(&percent_encode(server_id)); + } + for address in addresses + .iter() + .filter(|address| address.url != primary_url && address.kind != "local") + { + url.push_str("&a="); + url.push_str(&percent_encode(&pairing_address_value(&address.url))); + } + url +} + +fn pairing_address_value(url: &str) -> String { + let Ok(parsed) = url.parse::() else { + return url.to_owned(); + }; + let Some(authority) = parsed.authority() else { + return url.to_owned(); + }; + authority.as_str().to_owned() +} + +fn render_qr_code(value: &str) -> anyhow::Result { + let code = QrCode::new(value.as_bytes()).context("generate pairing QR code")?; + Ok(code + .render::() + .quiet_zone(true) + .dark_color(unicode::Dense1x2::Light) + .light_color(unicode::Dense1x2::Dark) + .build()) +} + +fn print_pair_progress(message: impl AsRef) { + eprintln!("simdeck pair: {}", message.as_ref()); +} + fn wait_for_daemon(metadata: &DaemonMetadata, timeout: Duration) -> anyhow::Result<()> { let deadline = Instant::now() + timeout; while Instant::now() < deadline { @@ -1292,6 +1508,21 @@ fn wait_for_daemon(metadata: &DaemonMetadata, timeout: Duration) -> anyhow::Resu ) } +fn wait_for_pairing_target(target: &PairingTarget, timeout: Duration) -> anyhow::Result<()> { + let deadline = Instant::now() + timeout; + while Instant::now() < deadline { + if http_get_json(&target.http_url, "/api/health").is_ok() { + return Ok(()); + } + std::thread::sleep(Duration::from_millis(50)); + } + anyhow::bail!( + "Timed out waiting for SimDeck {} at {}", + target.target, + target.http_url + ) +} + fn daemon_is_healthy(metadata: &DaemonMetadata) -> bool { http_get_json(&metadata.http_url, "/api/health").is_ok() } @@ -1432,7 +1663,8 @@ fn no_command_action_from_args() -> Option { fn is_known_command(value: &str) -> bool { matches!( value, - "ui" | "daemon" + "ui" | "pair" + | "daemon" | "service" | "core-simulator" | "simctl-service" @@ -1496,6 +1728,78 @@ fn restart_detached_daemon(options: DaemonLaunchOptions) -> anyhow::Result<()> { start_detached_daemon(options) } +struct PairGlobalServiceOptions { + port: Option, + bind: IpAddr, + advertise_host: Option, + client_root: Option, + video_codec: VideoCodecMode, + low_latency: bool, + stream_quality: Option, + local_stream_fps: Option, + json: bool, +} + +fn pair_global_service(options: PairGlobalServiceOptions) -> anyhow::Result<()> { + let PairGlobalServiceOptions { + port, + bind, + advertise_host, + client_root, + video_codec, + low_latency, + stream_quality, + local_stream_fps, + json, + } = options; + + if port.is_none() { + print_pair_progress("checking the installed service port"); + } + let port = match port { + Some(port) => port, + None => service::installed_port()?.unwrap_or(choose_daemon_port_for_bind(4310, bind)?), + }; + print_pair_progress(format!("using port {port}")); + + print_pair_progress("detecting LAN and Tailscale addresses"); + let advertise_host = advertise_host.or_else(|| { + detect_lan_ip() + .or_else(detect_tailscale_ip) + .map(|ip| ip.to_string()) + }); + if let Some(host) = advertise_host.as_deref() { + print_pair_progress(format!("advertising {host}:{port}")); + } else { + print_pair_progress("no LAN or Tailscale address detected; local pairing only"); + } + + print_pair_progress("starting the global SimDeck service"); + let result = service::pair(ServiceOptions { + port, + bind, + advertise_host, + client_root, + video_codec, + low_latency, + stream_quality_profile: local_stream_quality_profile(low_latency, stream_quality), + local_stream_fps, + access_token: None, + pairing_code: None, + })?; + print_pair_progress(format!( + "installed {}; logs: {}, {}", + result.service, + result.stdout_log.display(), + result.stderr_log.display() + )); + let target = PairingTarget::from_service(result)?; + print_pair_progress(format!("waiting for service health at {}", target.http_url)); + wait_for_pairing_target(&target, Duration::from_secs(15))?; + print_pair_progress("service is ready; rendering pairing QR"); + print_pairing_result(&target, true, json) +} + fn run_foreground_ui(selector: Option) -> anyhow::Result<()> { if let Some(metadata) = read_daemon_metadata().ok().flatten() { if daemon_is_healthy(&metadata) { @@ -1584,8 +1888,62 @@ fn detect_lan_ip() -> Option { None } +fn detect_tailscale_ip() -> Option { + detect_tailscale_ip_from_cli().or_else(detect_tailscale_ip_from_ifconfig) +} + +fn detect_tailscale_ip_from_cli() -> Option { + let output = ProcessCommand::new("tailscale") + .args(["ip", "-4"]) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output() + .ok()?; + if !output.status.success() { + return None; + } + let text = String::from_utf8_lossy(&output.stdout); + text.lines() + .filter_map(|line| line.trim().parse::().ok()) + .find(|ip| is_tailscale_ip(*ip)) +} + +fn detect_tailscale_ip_from_ifconfig() -> Option { + let output = ProcessCommand::new("ifconfig") + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output() + .ok()?; + if !output.status.success() { + return None; + } + let text = String::from_utf8_lossy(&output.stdout); + text.split_whitespace() + .filter_map(|part| part.parse::().ok()) + .find(|ip| is_tailscale_ip(*ip)) +} + +fn is_tailscale_ip(ip: IpAddr) -> bool { + match ip { + IpAddr::V4(ip) => { + let octets = ip.octets(); + octets[0] == 100 && (octets[1] & 0b1100_0000) == 0b0100_0000 + } + IpAddr::V6(_) => false, + } +} + +fn http_url_for_host(host: &str, port: u16) -> String { + let host = host.trim(); + if host.contains(':') && !host.starts_with('[') && !host.ends_with(']') { + format!("http://[{host}]:{port}") + } else { + format!("http://{host}:{port}") + } +} + fn ui_url(host: &str, port: u16, selector: Option<&str>) -> String { - let mut url = format!("http://{host}:{port}"); + let mut url = http_url_for_host(host, port); if let Some(selector) = selector.filter(|value| !value.trim().is_empty()) { url.push_str(&format!("/?device={}", percent_encode(selector.trim()))); } @@ -2002,6 +2360,27 @@ fn main() -> anyhow::Result<()> { print_daemon_start_result(&metadata, started)?; Ok(()) } + Command::Pair { + port, + bind, + advertise_host, + client_root, + video_codec, + low_latency, + stream_quality, + local_stream_fps, + json, + } => pair_global_service(PairGlobalServiceOptions { + port, + bind, + advertise_host, + client_root, + video_codec, + low_latency, + stream_quality, + local_stream_fps, + json, + }), Command::Daemon { command } => match command { DaemonCommand::Start { port, @@ -5916,6 +6295,7 @@ async fn serve( .with_context(|| format!("bind HTTP listener on {}", config.http_addr()))?; let health_heartbeat = Arc::new(AtomicU64::new(now_secs())); start_server_health_watchdog(config.http_addr(), health_heartbeat.clone()); + let _bonjour_advertisement = BonjourAdvertisement::start(&config); info!("HTTP listening on http://{}", config.http_addr()); info!("Serving client from {}", config.client_root.display()); @@ -5956,6 +6336,62 @@ async fn serve( Ok(()) } +struct BonjourAdvertisement { + child: Child, +} + +impl BonjourAdvertisement { + fn start(config: &Config) -> Option { + if config.bind_ip.is_loopback() { + return None; + } + let service_name = bonjour_service_name(&config.advertise_host); + let server_id = auth::server_identity(config); + match ProcessCommand::new("dns-sd") + .args([ + "-R", + &service_name, + "_simdeck._tcp.", + "local.", + &config.http_port.to_string(), + &format!("sid={server_id}"), + &format!("host={}", config.advertise_host), + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + { + Ok(child) => { + info!( + "Advertising Bonjour service '{}' on _simdeck._tcp. port {}", + service_name, config.http_port + ); + Some(Self { child }) + } + Err(error) => { + warn!("Unable to advertise Bonjour service with dns-sd: {error}"); + None + } + } + } +} + +impl Drop for BonjourAdvertisement { + fn drop(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + } +} + +fn bonjour_service_name(advertise_host: &str) -> String { + let host = advertise_host.trim(); + if host.is_empty() || host == "127.0.0.1" || host == "localhost" { + "SimDeck".to_owned() + } else { + format!("SimDeck {host}") + } +} + fn app_router(state: AppState, client_root: PathBuf, access_token: String) -> Router { router(state).fallback( move |axum::extract::ConnectInfo(address): axum::extract::ConnectInfo, @@ -6058,14 +6494,16 @@ fn default_client_root() -> anyhow::Result { #[cfg(test)] mod tests { use super::{ - batch_line_to_json_step, daemon_matches_launch_options, - normalize_accessibility_point_for_display, server_health_watchdog_should_restart, - service_post_error_is_retryable, studio_daemon_restart_args, Cli, Command, DaemonCommand, - DaemonLaunchOptions, DaemonMetadata, StreamQualityProfileArg, StudioExposeOptions, + batch_line_to_json_step, daemon_matches_launch_options, http_url_for_host, is_tailscale_ip, + normalize_accessibility_point_for_display, render_qr_code, + server_health_watchdog_should_restart, service_post_error_is_retryable, simdeck_pair_url, + studio_daemon_restart_args, Cli, Command, DaemonCommand, DaemonLaunchOptions, + DaemonMetadata, PairingAddress, StreamQualityProfileArg, StudioExposeOptions, VideoCodecMode, DEFAULT_LOCAL_STREAM_QUALITY_PROFILE, SERVER_HEALTH_WATCHDOG_FAILURE_THRESHOLD, SERVER_HEALTH_WATCHDOG_HTTP_FAILURE_THRESHOLD, }; use clap::Parser; + use std::net::{IpAddr, Ipv4Addr}; use std::path::PathBuf; fn daemon_metadata_for_test( @@ -6164,6 +6602,74 @@ mod tests { ); } + #[test] + fn pair_command_defaults_to_lan_bind() { + let cli = Cli::parse_from(["simdeck", "pair"]); + + let Command::Pair { bind, port, .. } = cli.command else { + panic!("expected pair command"); + }; + assert_eq!(port, None); + assert_eq!(bind, IpAddr::V4(Ipv4Addr::UNSPECIFIED)); + } + + #[test] + fn simdeck_pair_url_encodes_alternate_addresses() { + let addresses = vec![ + PairingAddress { + kind: "local", + url: "http://127.0.0.1:4310".to_owned(), + }, + PairingAddress { + kind: "lan", + url: "http://10.0.0.55:4310".to_owned(), + }, + PairingAddress { + kind: "tailscale", + url: "http://100.112.42.69:4310".to_owned(), + }, + ]; + + let url = simdeck_pair_url( + "http://10.0.0.55:4310", + "123456", + Some("server-1"), + &addresses, + ); + + assert!(url.starts_with("simdeck://pair?u=10.0.0.55%3A4310&c=123456&s=server-1")); + assert!(url.contains("a=100.112.42.69%3A4310")); + assert!(!url.contains("127.0.0.1")); + } + + #[test] + fn qr_renderer_uses_compact_metro_style_blocks() { + let qr = render_qr_code("simdeck://pair?url=http%3A%2F%2F10.0.0.55%3A4310&code=123456") + .expect("render QR"); + + assert!(qr.contains('█')); + assert!(qr.contains(' ')); + assert!(!qr.contains("\x1b[")); + assert!(qr.lines().count() < 40); + } + + #[test] + fn tailscale_ip_detection_matches_100_64_10() { + assert!(is_tailscale_ip("100.64.0.1".parse().unwrap())); + assert!(is_tailscale_ip("100.127.255.254".parse().unwrap())); + assert!(!is_tailscale_ip("100.128.0.1".parse().unwrap())); + assert!(!is_tailscale_ip("10.0.0.55".parse().unwrap())); + } + + #[test] + fn http_url_for_host_brackets_ipv6() { + assert_eq!(http_url_for_host("fe80::1", 4310), "http://[fe80::1]:4310"); + assert_eq!( + http_url_for_host("10.0.0.55", 4310), + "http://10.0.0.55:4310" + ); + } + #[test] fn screenshot_accepts_bezel_capture_flag() { let cli = Cli::parse_from(["simdeck", "screenshot", "SIM-1", "--with-bezel"]); diff --git a/server/src/service.rs b/server/src/service.rs index a4efe26c..58a377ca 100644 --- a/server/src/service.rs +++ b/server/src/service.rs @@ -1,4 +1,4 @@ -use crate::{default_client_root, ServiceOptions}; +use crate::{auth, default_client_root, ServiceOptions}; use anyhow::{anyhow, bail, Context}; use std::fs; use std::path::{Path, PathBuf}; @@ -8,15 +8,50 @@ use std::time::{Duration, Instant}; const SERVICE_LABEL: &str = "dev.nativescript.simdeck"; +#[derive(Clone, Debug)] +pub struct ServiceInstallResult { + pub service: String, + pub plist_path: PathBuf, + pub stdout_log: PathBuf, + pub stderr_log: PathBuf, + pub port: u16, + pub advertise_host: Option, + pub access_token: Option, + pub pairing_code: Option, +} + pub fn enable(options: ServiceOptions) -> anyhow::Result<()> { - install(options) + let result = install(options)?; + print_install_result(&result) } pub fn restart(options: ServiceOptions) -> anyhow::Result<()> { + let result = install(options)?; + print_install_result(&result) +} + +pub fn pair(mut options: ServiceOptions) -> anyhow::Result { + let existing_credentials = installed_credentials().unwrap_or(None); + if options.access_token.is_none() { + options.access_token = existing_credentials + .as_ref() + .and_then(|credentials| credentials.access_token.clone()) + .or_else(|| Some(auth::generate_access_token())); + } + if options.pairing_code.is_none() { + options.pairing_code = existing_credentials + .as_ref() + .and_then(|credentials| credentials.pairing_code.clone()) + .or_else(|| Some(auth::generate_pairing_code())); + } install(options) } -fn install(options: ServiceOptions) -> anyhow::Result<()> { +pub fn installed_port() -> anyhow::Result> { + Ok(installed_argument_value("--port")?.and_then(|value| value.parse::().ok())) +} + +fn install(options: ServiceOptions) -> anyhow::Result { let plist_path = plist_path()?; let log_dir = log_dir()?; fs::create_dir_all( @@ -52,14 +87,30 @@ fn install(options: ServiceOptions) -> anyhow::Result<()> { run_launchctl(["bootstrap", &domain, plist_path.to_string_lossy().as_ref()])?; run_launchctl(["kickstart", "-k", &format!("{domain}/{SERVICE_LABEL}")])?; + let advertise_host = options.advertise_host.clone(); + let access_token = options.access_token.clone(); + let pairing_code = options.pairing_code.clone(); + Ok(ServiceInstallResult { + service: SERVICE_LABEL.to_owned(), + plist_path, + stdout_log, + stderr_log, + port: options.port, + advertise_host, + access_token, + pairing_code, + }) +} + +fn print_install_result(result: &ServiceInstallResult) -> anyhow::Result<()> { println!( "{}", serde_json::to_string_pretty(&serde_json::json!({ "ok": true, - "service": SERVICE_LABEL, - "plist": plist_path, - "stdoutLog": stdout_log, - "stderrLog": stderr_log, + "service": result.service, + "plist": result.plist_path, + "stdoutLog": result.stdout_log, + "stderrLog": result.stderr_log, }))? ); Ok(()) @@ -97,6 +148,60 @@ fn log_dir() -> anyhow::Result { Ok(home_dir()?.join("Library/Logs")) } +#[derive(Clone, Debug)] +struct ServiceCredentials { + access_token: Option, + pairing_code: Option, +} + +fn installed_credentials() -> anyhow::Result> { + let Some(arguments) = installed_arguments()? else { + return Ok(None); + }; + Ok(Some(ServiceCredentials { + access_token: argument_value(&arguments, "--access-token"), + pairing_code: argument_value(&arguments, "--pairing-code"), + })) +} + +fn installed_argument_value(name: &str) -> anyhow::Result> { + Ok(installed_arguments()? + .as_deref() + .and_then(|arguments| argument_value(arguments, name))) +} + +fn installed_arguments() -> anyhow::Result>> { + let plist_path = plist_path()?; + if !plist_path.exists() { + return Ok(None); + } + let plist = plist::Value::from_file(&plist_path) + .with_context(|| format!("read {}", plist_path.display()))?; + let Some(arguments) = plist + .as_dictionary() + .and_then(|dict| dict.get("ProgramArguments")) + .and_then(|value| value.as_array()) + else { + return Ok(None); + }; + let arguments = arguments + .iter() + .filter_map(|value| value.as_string()) + .map(ToOwned::to_owned) + .collect::>(); + Ok(Some(arguments)) +} + +fn argument_value(arguments: &[String], name: &str) -> Option { + arguments + .windows(2) + .find(|window| window.first().is_some_and(|value| value == name)) + .and_then(|window| window.get(1)) + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) +} + fn home_dir() -> anyhow::Result { std::env::var_os("HOME") .map(PathBuf::from) diff --git a/server/src/transport/webrtc.rs b/server/src/transport/webrtc.rs index 897741d2..9d469e0a 100644 --- a/server/src/transport/webrtc.rs +++ b/server/src/transport/webrtc.rs @@ -2200,18 +2200,27 @@ async fn write_frame_sample_with_timeout( WEBRTC_WRITE_TIMEOUT }; let started_at = time::Instant::now(); - write_frame_sample(video_track, packetizer, frame, duration, realtime_stream).await?; + let write_result = time::timeout( + slow_write_threshold, + write_frame_sample(video_track, packetizer, frame, duration, realtime_stream), + ) + .await; let elapsed = started_at.elapsed(); - if elapsed > slow_write_threshold { - warn!( - "WebRTC frame write exceeded realtime budget: elapsed_ms={} threshold_ms={} keyframe={} realtime={}", - elapsed.as_millis(), - slow_write_threshold.as_millis(), - frame.is_keyframe, - realtime_stream - ); + + match write_result { + Ok(Ok(())) => Ok(true), + Ok(Err(error)) => Err(error), + Err(_) => { + warn!( + "WebRTC frame write timed out: elapsed_ms={} threshold_ms={} keyframe={} realtime={}", + elapsed.as_millis(), + slow_write_threshold.as_millis(), + frame.is_keyframe, + realtime_stream + ); + Ok(false) + } } - Ok(true) } fn rtp_packet_pacing( diff --git a/skills/simdeck/SKILL.md b/skills/simdeck/SKILL.md index 5fcef571..42d4c6b8 100644 --- a/skills/simdeck/SKILL.md +++ b/skills/simdeck/SKILL.md @@ -22,6 +22,7 @@ SimDeck uses one daemon per workspace (CWD). Use `simdeck ui` and it will print ```bash simdeck ui +simdeck pair # prints LAN/Tailscale pairing URLs, code, and iOS QR simdeck -k # kills the daemon simdeck -r # restarts the daemon simdeck daemon killall # kills all daemons on the machine, use with care @@ -29,6 +30,10 @@ simdeck daemon killall # kills all daemons on the machine, use with care Usually `http://127.0.0.1:4310` or `http://127.0.0.1:4310?device=`. Port may increment if multiple daemons are running. +Use `simdeck pair` when a native iOS client needs to pair. It starts or +refreshes the global LaunchAgent-backed service, detects LAN and Tailscale IPv4 +addresses, and prints a QR with a `simdeck://pair` URL that carries the pairing +code plus alternate server addresses. Always first run `simdeck ui` to open the URL reported by the `simdeck ui` in the in-app browser using Browser Use tool if available.