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 .setUseStereoInput(true)
47 .setUseStereoOutput(true)
48 .createAudioDeviceModule()
49`,
50 },
51];
52
53export const withWorkingAndroidWebRTCAudio: ConfigPlugin = (configOuter) => {
54 return withMainApplication(configOuter, (config) => {
55 let stringContents: string = config.modResults.contents;
56
57 for (const { from, to } of androidApplicationReplacements) {
58 stringContents = stringContents.replace(from, to);
59 }
60 if (stringContents === config.modResults.contents) {
61 buildError("android codemod failed to apply");
62 }
63
64 config.modResults.contents = stringContents;
65
66 return config;
67 });
68};
69
70const iosDelegateReplacements = [
71 // Objective-C Version
72 {
73 from: "#import <React/RCTLinkingManager.h>",
74 to: (config) => `
75#import <React/RCTLinkingManager.h>
76#import <WebRTC/WebRTC.h>
77#import "CaptureController.h"
78#import "CapturerEventsDelegate.h"
79#import "DataChannelWrapper.h"
80#import "RCTConvert+WebRTC.h"
81#import "RTCMediaStreamTrack+React.h"
82#import "RTCVideoViewManager.h"
83#import "ScreenCaptureController.h"
84#import "ScreenCapturePickerViewManager.h"
85#import "ScreenCapturer.h"
86#import "SerializeUtils.h"
87#import "SocketConnection.h"
88#import "TrackCapturerEventsEmitter.h"
89#import "VideoCaptureController.h"
90#import "WebRTCModule+RTCDataChannel.h"
91#import "WebRTCModule+RTCMediaStream.h"
92#import "WebRTCModule+RTCPeerConnection.h"
93#import "WebRTCModule+VideoTrackAdapter.h"
94#import "WebRTCModule.h"
95#import "WebRTCModuleOptions.h"
96#import "ExpoModulesCore-Swift.h"
97#import "${config.name.replaceAll(" ", "")}-Swift.h"
98`,
99 },
100 {
101 from: " self.initialProps = @{};",
102 to: () => `
103 self.initialProps = @{};
104 ////RTC PATCH////
105 RTCAudioSessionConfiguration* config = [RTCAudioSessionConfiguration webRTCConfiguration];
106
107 AVAudioSession * session = [AVAudioSession sharedInstance];
108 // Set audio to use phone speaker instead of headset speaker
109 [session setCategory:AVAudioSessionCategoryPlayAndRecord
110 withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth
111 error:nil];
112 [session setActive:YES error:nil];
113
114 id<RTCAudioDevice> device;
115 device = [[AUAudioUnitRTCAudioDevice alloc] init];
116
117 WebRTCModuleOptions *options = [WebRTCModuleOptions sharedInstance];
118 options.loggingSeverity = RTCLoggingSeverityWarning;
119 options.audioDevice = device;
120 // Enable stereo audio
121 options.enableStereoOutput = YES;
122 ////END RTC PATCH////
123 `,
124 },
125 // Swift Version
126 {
127 from: " let delegate = ReactNativeDelegate()",
128 to: () => `
129 // WebRTC Configuration
130 let config = RTCAudioSessionConfiguration.webRTC()
131
132 let session = AVAudioSession.sharedInstance()
133 do {
134 try session.setCategory(.playAndRecord,
135 options: [.defaultToSpeaker, .allowBluetooth])
136 try session.setActive(true)
137 } catch {
138 print("Failed to configure audio session: \(error)")
139 }
140
141 let device = AUAudioUnitRTCAudioDevice()
142
143 let options = WebRTCModuleOptions.sharedInstance()
144 options.loggingSeverity = .warning
145 options.audioDevice = device
146 // End WebRTC Configuration
147
148 let delegate = ReactNativeDelegate()
149 `,
150 },
151 {
152 from: "import ReactAppDependencyProvider",
153 to: () => `
154import ReactAppDependencyProvider
155import WebRTC
156import react_native_webrtc
157import AVFoundation
158 `,
159 },
160];
161
162const withWorkingIOSWebRTCAudio: ConfigPlugin = (config) => {
163 const files = [
164 "AUAudioUnitRTCAudioDevice.swift",
165 "AudioSessionHandler.swift",
166 "SimpleAudioConverter.swift",
167 "Utils.swift",
168 ];
169
170 let called = false;
171 // modify the app delegate to make use of the CustomRTCAudioDevice
172 config = withAppDelegate(config, (config) => {
173 let stringContents: string = config.modResults.contents;
174
175 for (const { from, to } of iosDelegateReplacements) {
176 stringContents = stringContents.replace(from, to(config));
177 }
178 if (stringContents === config.modResults.contents) {
179 buildError("ios codemod failed to change anything, aborting");
180 }
181
182 config.modResults.contents = stringContents;
183 called = true;
184
185 return config;
186 });
187
188 // add the CustomRTCAudioDevice files to the xcode project
189 config = withXcodeProject(config, (config) => {
190 const rtc = require.resolve("rtcaudiodevice");
191 for (const file of files) {
192 IOSConfig.XcodeUtils.addBuildSourceFileToGroup({
193 filepath: resolve(rtc, "..", "CustomRTCAudioDevice", file),
194 groupName: config.name,
195 project: config.modResults,
196 });
197 }
198
199 return config;
200 });
201
202 return config;
203};
204
205export default function withStreamplaceReactNativeWebRTC(config: ExpoConfig) {
206 config = RNWebRTCPlugin(config);
207 config = withWorkingAndroidWebRTCAudio(config);
208 config = withWorkingIOSWebRTCAudio(config);
209 return config;
210}