@livekit/react-native 2.8.0 → 2.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -5
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/livekit/reactnative/LivekitReactNativeModule.kt +22 -0
- package/android/src/main/java/com/livekit/reactnative/audio/events/Events.kt +1 -0
- package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioSinkProcessor.kt +57 -0
- package/ios/LiveKitReactNativeModule.swift +21 -1
- package/ios/LivekitReactNativeModule.m +6 -0
- package/ios/audio/AudioSinkRenderer.swift +51 -0
- package/lib/commonjs/audio/MediaRecorder.js +132 -0
- package/lib/commonjs/audio/MediaRecorder.js.map +1 -0
- package/lib/commonjs/components/BarVisualizer.js +2 -5
- package/lib/commonjs/components/BarVisualizer.js.map +1 -1
- package/lib/commonjs/components/LiveKitRoom.js.map +1 -1
- package/lib/commonjs/events/EventEmitter.js +1 -1
- package/lib/commonjs/events/EventEmitter.js.map +1 -1
- package/lib/commonjs/index.js +2 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/polyfills/MediaRecorderShim.js +13 -0
- package/lib/commonjs/polyfills/MediaRecorderShim.js.map +1 -0
- package/lib/module/audio/MediaRecorder.js +125 -0
- package/lib/module/audio/MediaRecorder.js.map +1 -0
- package/lib/module/components/BarVisualizer.js +2 -5
- package/lib/module/components/BarVisualizer.js.map +1 -1
- package/lib/module/components/LiveKitRoom.js.map +1 -1
- package/lib/module/events/EventEmitter.js +1 -1
- package/lib/module/events/EventEmitter.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/polyfills/MediaRecorderShim.js +11 -0
- package/lib/module/polyfills/MediaRecorderShim.js.map +1 -0
- package/lib/typescript/lib/commonjs/audio/MediaRecorder.d.ts +24 -0
- package/lib/typescript/lib/commonjs/polyfills/MediaRecorderShim.d.ts +1 -0
- package/lib/typescript/lib/module/audio/MediaRecorder.d.ts +22 -0
- package/lib/typescript/lib/module/polyfills/MediaRecorderShim.d.ts +1 -0
- package/lib/typescript/src/audio/MediaRecorder.d.ts +54 -0
- package/lib/typescript/src/components/LiveKitRoom.d.ts +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/polyfills/MediaRecorderShim.d.ts +1 -0
- package/package.json +7 -5
- package/src/audio/MediaRecorder.ts +158 -0
- package/src/components/BarVisualizer.tsx +2 -9
- package/src/components/LiveKitRoom.tsx +1 -1
- package/src/events/EventEmitter.ts +5 -1
- package/src/index.tsx +2 -0
- package/src/polyfills/MediaRecorderShim.ts +12 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import 'well-known-symbols/Symbol.asyncIterator/auto';
|
|
2
2
|
import 'well-known-symbols/Symbol.iterator/auto';
|
|
3
|
+
import './polyfills/MediaRecorderShim';
|
|
4
|
+
import 'react-native-quick-base64';
|
|
3
5
|
import './polyfills/EncoderDecoderTogether.min.js';
|
|
4
6
|
import AudioSession, { AndroidAudioTypePresets, type AndroidAudioTypeOptions, type AppleAudioCategory, type AppleAudioCategoryOption, type AppleAudioConfiguration, type AppleAudioMode, type AudioTrackState, getDefaultAppleAudioConfigurationForMode } from './audio/AudioSession';
|
|
5
7
|
import type { AudioConfiguration } from './audio/AudioSession';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livekit/react-native",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "LiveKit for React Native",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -44,9 +44,11 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@livekit/components-react": "^2.8.1",
|
|
46
46
|
"array.prototype.at": "^1.1.1",
|
|
47
|
+
"event-target-shim": "6.0.2",
|
|
47
48
|
"events": "^3.3.0",
|
|
48
49
|
"loglevel": "^1.8.0",
|
|
49
50
|
"promise.allsettled": "^1.0.5",
|
|
51
|
+
"react-native-quick-base64": "2.1.1",
|
|
50
52
|
"react-native-url-polyfill": "^1.3.0",
|
|
51
53
|
"typed-emitter": "^2.1.0",
|
|
52
54
|
"web-streams-polyfill": "^4.1.0",
|
|
@@ -57,7 +59,7 @@
|
|
|
57
59
|
"@babel/preset-env": "^7.20.0",
|
|
58
60
|
"@babel/runtime": "^7.20.0",
|
|
59
61
|
"@commitlint/config-conventional": "^16.2.1",
|
|
60
|
-
"@livekit/react-native-webrtc": "^
|
|
62
|
+
"@livekit/react-native-webrtc": "^137.0.1",
|
|
61
63
|
"@react-native/babel-preset": "0.74.84",
|
|
62
64
|
"@react-native/eslint-config": "0.74.84",
|
|
63
65
|
"@react-native/metro-config": "0.74.84",
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
"eslint-plugin-prettier": "^4.2.1",
|
|
74
76
|
"husky": "^7.0.4",
|
|
75
77
|
"jest": "^29.6.3",
|
|
76
|
-
"livekit-client": "^2.
|
|
78
|
+
"livekit-client": "^2.15.4",
|
|
77
79
|
"pod-install": "^0.2.2",
|
|
78
80
|
"prettier": "2.8.8",
|
|
79
81
|
"react": "18.2.0",
|
|
@@ -84,8 +86,8 @@
|
|
|
84
86
|
"typescript": "5.0.4"
|
|
85
87
|
},
|
|
86
88
|
"peerDependencies": {
|
|
87
|
-
"@livekit/react-native-webrtc": "^
|
|
88
|
-
"livekit-client": "^2.
|
|
89
|
+
"@livekit/react-native-webrtc": "^137.0.1",
|
|
90
|
+
"livekit-client": "^2.15.4",
|
|
89
91
|
"react": "*",
|
|
90
92
|
"react-native": "*"
|
|
91
93
|
},
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { MediaStream } from '@livekit/react-native-webrtc';
|
|
2
|
+
import { addListener } from '../events/EventEmitter';
|
|
3
|
+
import {
|
|
4
|
+
EventTarget,
|
|
5
|
+
Event,
|
|
6
|
+
defineEventAttribute,
|
|
7
|
+
} from 'event-target-shim/index';
|
|
8
|
+
import { toByteArray } from 'react-native-quick-base64';
|
|
9
|
+
import LiveKitModule from '../LKNativeModule';
|
|
10
|
+
import { log } from '../logger';
|
|
11
|
+
|
|
12
|
+
// typeof MediaRecorder
|
|
13
|
+
// const Tester = (stream: MediaStream) => {
|
|
14
|
+
// return new AudioRecorder(stream) satisfies MediaRecorder;
|
|
15
|
+
// };
|
|
16
|
+
|
|
17
|
+
type MediaRecorderState = 'inactive' | 'recording' | 'paused';
|
|
18
|
+
type MediaRecorderEventMap = {
|
|
19
|
+
dataavailable: BlobEvent<'dataavailable'>;
|
|
20
|
+
error: Event<'error'>;
|
|
21
|
+
pause: Event<'pause'>;
|
|
22
|
+
resume: Event<'resume'>;
|
|
23
|
+
start: Event<'start'>;
|
|
24
|
+
stop: Event<'stop'>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A MediaRecord implementation only meant for recording audio streams.
|
|
29
|
+
*
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
export class MediaRecorder extends EventTarget<MediaRecorderEventMap> {
|
|
33
|
+
mimeType: String = 'audio/pcm';
|
|
34
|
+
audioBitsPerSecond: number = 0; // TODO?
|
|
35
|
+
state: MediaRecorderState = 'inactive';
|
|
36
|
+
stream: MediaStream;
|
|
37
|
+
videoBitsPerSecond: number = 0; // TODO?
|
|
38
|
+
audioBitrateMode = 'constant';
|
|
39
|
+
|
|
40
|
+
_reactTag: string | undefined = undefined;
|
|
41
|
+
_parts: string[] = [];
|
|
42
|
+
constructor(stream: MediaStream) {
|
|
43
|
+
super();
|
|
44
|
+
this.stream = stream;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
registerListener() {
|
|
48
|
+
let audioTracks = this.stream.getAudioTracks();
|
|
49
|
+
if (audioTracks.length !== 1) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const mediaStreamTrack = audioTracks[0];
|
|
53
|
+
const peerConnectionId = mediaStreamTrack._peerConnectionId ?? -1;
|
|
54
|
+
const mediaStreamTrackId = mediaStreamTrack?.id;
|
|
55
|
+
this._reactTag = LiveKitModule.createAudioSinkListener(
|
|
56
|
+
peerConnectionId,
|
|
57
|
+
mediaStreamTrackId
|
|
58
|
+
);
|
|
59
|
+
addListener(this, 'LK_AUDIO_DATA', (event: any) => {
|
|
60
|
+
if (
|
|
61
|
+
this._reactTag &&
|
|
62
|
+
event.id === this._reactTag &&
|
|
63
|
+
this.state === 'recording'
|
|
64
|
+
) {
|
|
65
|
+
let str = event.data as string;
|
|
66
|
+
this._parts.push(str);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
unregisterListener() {
|
|
72
|
+
if (this._reactTag) {
|
|
73
|
+
let audioTracks = this.stream.getAudioTracks();
|
|
74
|
+
if (audioTracks.length !== 1) {
|
|
75
|
+
log.error("couldn't find any audio tracks to record from!");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const mediaStreamTrack = audioTracks[0];
|
|
79
|
+
const peerConnectionId = mediaStreamTrack._peerConnectionId ?? -1;
|
|
80
|
+
const mediaStreamTrackId = mediaStreamTrack?.id;
|
|
81
|
+
|
|
82
|
+
LiveKitModule.deleteAudioSinkListener(
|
|
83
|
+
this._reactTag,
|
|
84
|
+
peerConnectionId,
|
|
85
|
+
mediaStreamTrackId
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
pause() {
|
|
91
|
+
this.state = 'paused';
|
|
92
|
+
this.dispatchEvent(new Event('pause'));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resume() {
|
|
96
|
+
this.state = 'recording';
|
|
97
|
+
this.dispatchEvent(new Event('resume'));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
start() {
|
|
101
|
+
this.registerListener();
|
|
102
|
+
this.state = 'recording';
|
|
103
|
+
this.dispatchEvent(new Event('start'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stop() {
|
|
107
|
+
// dispatch data must come before stopping.
|
|
108
|
+
this.dispatchData();
|
|
109
|
+
|
|
110
|
+
this.unregisterListener();
|
|
111
|
+
this.state = 'inactive';
|
|
112
|
+
this.dispatchEvent(new Event('stop'));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
requestData() {
|
|
116
|
+
this.dispatchData();
|
|
117
|
+
}
|
|
118
|
+
dispatchData() {
|
|
119
|
+
let combinedStr = this._parts.reduce((sum, cur) => sum + cur, '');
|
|
120
|
+
let data = toByteArray(combinedStr);
|
|
121
|
+
this._parts = [];
|
|
122
|
+
this.dispatchEvent(
|
|
123
|
+
new BlobEvent('dataavailable', { data: { byteArray: data } })
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @eventClass
|
|
130
|
+
* This event is fired whenever the Track is changed in PeerConnection.
|
|
131
|
+
* @param {TRACK_EVENTS} type - The type of event.
|
|
132
|
+
* @param {IRTCTrackEventInitDict} eventInitDict - The event init properties.
|
|
133
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/track_event MDN} for details.
|
|
134
|
+
*/
|
|
135
|
+
class BlobEvent<TEventType extends string> extends Event<TEventType> {
|
|
136
|
+
/** @eventProperty */
|
|
137
|
+
readonly data: { byteArray: Uint8Array };
|
|
138
|
+
|
|
139
|
+
constructor(
|
|
140
|
+
type: TEventType,
|
|
141
|
+
eventInitDict: { data: { byteArray: Uint8Array } } & Event.EventInit
|
|
142
|
+
) {
|
|
143
|
+
super(type, eventInitDict);
|
|
144
|
+
this.data = eventInitDict.data;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Define the `onxxx` event handlers.
|
|
150
|
+
*/
|
|
151
|
+
const proto = MediaRecorder.prototype;
|
|
152
|
+
|
|
153
|
+
defineEventAttribute(proto, 'dataavailable');
|
|
154
|
+
defineEventAttribute(proto, 'error');
|
|
155
|
+
defineEventAttribute(proto, 'pause');
|
|
156
|
+
defineEventAttribute(proto, 'resume');
|
|
157
|
+
defineEventAttribute(proto, 'start');
|
|
158
|
+
defineEventAttribute(proto, 'stop');
|
|
@@ -149,7 +149,7 @@ export const BarVisualizer = ({
|
|
|
149
149
|
let bars: React.ReactNode[] = [];
|
|
150
150
|
magnitudes.forEach((value, index) => {
|
|
151
151
|
let coerced = Math.min(opts.maxHeight, Math.max(opts.minHeight, value));
|
|
152
|
-
let coercedPercent = Math.min(100, Math.max(0, coerced * 100
|
|
152
|
+
let coercedPercent = Math.min(100, Math.max(0, coerced * 100));
|
|
153
153
|
let opacity = opacityAnimations[index] ?? new Animated.Value(0.3);
|
|
154
154
|
let barStyle = {
|
|
155
155
|
opacity: opacity,
|
|
@@ -160,11 +160,7 @@ export const BarVisualizer = ({
|
|
|
160
160
|
bars.push(
|
|
161
161
|
<Animated.View
|
|
162
162
|
key={index}
|
|
163
|
-
style={[
|
|
164
|
-
{ height: `${coercedPercent}%` },
|
|
165
|
-
barStyle,
|
|
166
|
-
styles.volumeIndicator,
|
|
167
|
-
]}
|
|
163
|
+
style={[{ height: `${coercedPercent}%` }, barStyle]}
|
|
168
164
|
/>
|
|
169
165
|
);
|
|
170
166
|
});
|
|
@@ -177,9 +173,6 @@ const styles = StyleSheet.create({
|
|
|
177
173
|
alignItems: 'center',
|
|
178
174
|
justifyContent: 'space-evenly',
|
|
179
175
|
},
|
|
180
|
-
volumeIndicator: {
|
|
181
|
-
borderRadius: 12,
|
|
182
|
-
},
|
|
183
176
|
});
|
|
184
177
|
|
|
185
178
|
export const useBarAnimator = (
|
|
@@ -7,7 +7,11 @@ import LiveKitModule from '../LKNativeModule';
|
|
|
7
7
|
// re-emit them on a JS-only emitter.
|
|
8
8
|
const nativeEmitter = new NativeEventEmitter(LiveKitModule);
|
|
9
9
|
|
|
10
|
-
const NATIVE_EVENTS = [
|
|
10
|
+
const NATIVE_EVENTS = [
|
|
11
|
+
'LK_VOLUME_PROCESSED',
|
|
12
|
+
'LK_MULTIBAND_PROCESSED',
|
|
13
|
+
'LK_AUDIO_DATA',
|
|
14
|
+
];
|
|
11
15
|
|
|
12
16
|
const eventEmitter = new EventEmitter();
|
|
13
17
|
|
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import 'well-known-symbols/Symbol.asyncIterator/auto';
|
|
2
2
|
import 'well-known-symbols/Symbol.iterator/auto';
|
|
3
|
+
import './polyfills/MediaRecorderShim';
|
|
4
|
+
import 'react-native-quick-base64';
|
|
3
5
|
import { registerGlobals as webrtcRegisterGlobals } from '@livekit/react-native-webrtc';
|
|
4
6
|
import { setupURLPolyfill } from 'react-native-url-polyfill';
|
|
5
7
|
import './polyfills/EncoderDecoderTogether.min.js';
|