Live video on the AT Protocol
1import RNWebRTCPlugin from "@config-plugins/react-native-webrtc";
2import { ExpoConfig } from "expo/config";
3import {
4 ConfigPlugin,
5 IOSConfig,
6 withAppDelegate,
7 withMainApplication,
8 withXcodeProject,
9} from "expo/config-plugins";
10import { resolve } from "path";
11
12const buildError = (message: string) => {
13 if (process.env.SP_SKIP_CODEMODE_ERRORS !== "true") {
14 throw new Error(`@streamplace/config-native-webrtc ${message}`);
15 } else {
16 console.error(
17 `@streamplace/config-native-webrtc ${message}, skipping because SP_SKIP_CODEMODE_ERRORS=true`,
18 );
19 }
20};
21
22// https://github.com/react-native-webrtc/react-native-webrtc/blob/19ca31d4b77d149a659ee037fae54861a2d90a73/Documentation/AndroidInstallation.md#set-audio-category-output-to-media
23// look, i'm as upset about this as you are
24const androidApplicationReplacements = [
25 {
26 from: "class MainApplication : Application(), ReactApplication {",
27 to: `
28import com.oney.WebRTCModule.WebRTCModuleOptions
29import android.media.AudioAttributes
30import org.webrtc.audio.JavaAudioDeviceModule
31
32class MainApplication : Application(), ReactApplication {`,
33 },
34 {
35 from: "override fun onCreate() {",
36 to: `
37 override fun onCreate() {
38 // append this before WebRTCModule initializes
39 val options = WebRTCModuleOptions.getInstance()
40 val audioAttributes = AudioAttributes.Builder()
41 .setUsage(AudioAttributes.USAGE_MEDIA)
42 .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
43 .build()
44 options.audioDeviceModule = JavaAudioDeviceModule.builder(this)
45 .setAudioAttributes(audioAttributes)
46 .createAudioDeviceModule()
47`,
48 },
49];
50
51export const withWorkingAndroidWebRTCAudio: ConfigPlugin = (configOuter) => {
52 return withMainApplication(configOuter, (config) => {
53 let stringContents: string = config.modResults.contents;
54
55 for (const { from, to } of androidApplicationReplacements) {
56 stringContents = stringContents.replace(from, to);
57 }
58 if (stringContents === config.modResults.contents) {
59 buildError("android codemod failed to apply");
60 }
61
62 config.modResults.contents = stringContents;
63
64 return config;
65 });
66};
67
68const iosDelegateReplacements = [
69 // Objective-C Version
70 {
71 from: "#import <React/RCTLinkingManager.h>",
72 to: (config) => `
73#import <React/RCTLinkingManager.h>
74#import <WebRTC/WebRTC.h>
75#import "CaptureController.h"
76#import "CapturerEventsDelegate.h"
77#import "DataChannelWrapper.h"
78#import "RCTConvert+WebRTC.h"
79#import "RTCMediaStreamTrack+React.h"
80#import "RTCVideoViewManager.h"
81#import "ScreenCaptureController.h"
82#import "ScreenCapturePickerViewManager.h"
83#import "ScreenCapturer.h"
84#import "SerializeUtils.h"
85#import "SocketConnection.h"
86#import "TrackCapturerEventsEmitter.h"
87#import "VideoCaptureController.h"
88#import "WebRTCModule+RTCDataChannel.h"
89#import "WebRTCModule+RTCMediaStream.h"
90#import "WebRTCModule+RTCPeerConnection.h"
91#import "WebRTCModule+VideoTrackAdapter.h"
92#import "WebRTCModule.h"
93#import "WebRTCModuleOptions.h"
94#import "ExpoModulesCore-Swift.h"
95#import "${config.name.replaceAll(" ", "")}-Swift.h"
96`,
97 },
98 {
99 from: " self.initialProps = @{};",
100 to: () => `
101 self.initialProps = @{};
102 ////RTC PATCH////
103 RTCAudioSessionConfiguration* config = [RTCAudioSessionConfiguration webRTCConfiguration];
104
105 AVAudioSession * session = [AVAudioSession sharedInstance];
106 // Set audio to use phone speaker instead of headset speaker
107 [session setCategory:AVAudioSessionCategoryPlayAndRecord
108 withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth
109 error:nil];
110 [session setActive:YES error:nil];
111
112 id<RTCAudioDevice> device;
113 device = [[AUAudioUnitRTCAudioDevice alloc] init];
114
115 WebRTCModuleOptions *options = [WebRTCModuleOptions sharedInstance];
116 options.loggingSeverity = RTCLoggingSeverityWarning;
117 options.audioDevice = device;
118 ////END RTC PATCH////
119 `,
120 },
121 // Swift Version
122 {
123 from: " let delegate = ReactNativeDelegate()",
124 to: () => `
125 // WebRTC Configuration
126 let config = RTCAudioSessionConfiguration.webRTC()
127
128 let session = AVAudioSession.sharedInstance()
129 do {
130 try session.setCategory(.playAndRecord,
131 options: [.defaultToSpeaker, .allowBluetooth])
132 try session.setActive(true)
133 } catch {
134 print("Failed to configure audio session: \(error)")
135 }
136
137 let device = AUAudioUnitRTCAudioDevice()
138
139 let options = WebRTCModuleOptions.sharedInstance()
140 options.loggingSeverity = .warning
141 options.audioDevice = device
142 // End WebRTC Configuration
143
144 let delegate = ReactNativeDelegate()
145 `,
146 },
147 {
148 from: "import ReactAppDependencyProvider",
149 to: () => `
150import ReactAppDependencyProvider
151import WebRTC
152import react_native_webrtc
153import AVFoundation
154 `,
155 },
156];
157
158const withWorkingIOSWebRTCAudio: ConfigPlugin = (config) => {
159 const files = [
160 "AUAudioUnitRTCAudioDevice.swift",
161 "AudioSessionHandler.swift",
162 "SimpleAudioConverter.swift",
163 "Utils.swift",
164 ];
165
166 let called = false;
167 // modify the app delegate to make use of the CustomRTCAudioDevice
168 config = withAppDelegate(config, (config) => {
169 let stringContents: string = config.modResults.contents;
170
171 for (const { from, to } of iosDelegateReplacements) {
172 stringContents = stringContents.replace(from, to(config));
173 }
174 if (stringContents === config.modResults.contents) {
175 buildError("ios codemod failed to change anything, aborting");
176 }
177
178 config.modResults.contents = stringContents;
179 called = true;
180
181 return config;
182 });
183
184 // add the CustomRTCAudioDevice files to the xcode project
185 config = withXcodeProject(config, (config) => {
186 const rtc = require.resolve("rtcaudiodevice");
187 for (const file of files) {
188 IOSConfig.XcodeUtils.addBuildSourceFileToGroup({
189 filepath: resolve(rtc, "..", "CustomRTCAudioDevice", file),
190 groupName: config.name,
191 project: config.modResults,
192 });
193 }
194
195 return config;
196 });
197
198 return config;
199};
200
201export default function withStreamplaceReactNativeWebRTC(config: ExpoConfig) {
202 config = RNWebRTCPlugin(config);
203 config = withWorkingAndroidWebRTCAudio(config);
204 config = withWorkingIOSWebRTCAudio(config);
205 return config;
206}