@livekit/react-native 2.8.0 → 2.9.0

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.
Files changed (45) hide show
  1. package/README.md +54 -5
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/livekit/reactnative/LivekitReactNativeModule.kt +22 -0
  4. package/android/src/main/java/com/livekit/reactnative/audio/events/Events.kt +1 -0
  5. package/android/src/main/java/com/livekit/reactnative/audio/processing/AudioSinkProcessor.kt +57 -0
  6. package/ios/LiveKitReactNativeModule.swift +21 -1
  7. package/ios/LivekitReactNativeModule.m +6 -0
  8. package/ios/audio/AudioSinkRenderer.swift +51 -0
  9. package/lib/commonjs/audio/MediaRecorder.js +132 -0
  10. package/lib/commonjs/audio/MediaRecorder.js.map +1 -0
  11. package/lib/commonjs/components/BarVisualizer.js +2 -5
  12. package/lib/commonjs/components/BarVisualizer.js.map +1 -1
  13. package/lib/commonjs/components/LiveKitRoom.js.map +1 -1
  14. package/lib/commonjs/events/EventEmitter.js +1 -1
  15. package/lib/commonjs/events/EventEmitter.js.map +1 -1
  16. package/lib/commonjs/index.js +2 -0
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/polyfills/MediaRecorderShim.js +13 -0
  19. package/lib/commonjs/polyfills/MediaRecorderShim.js.map +1 -0
  20. package/lib/module/audio/MediaRecorder.js +125 -0
  21. package/lib/module/audio/MediaRecorder.js.map +1 -0
  22. package/lib/module/components/BarVisualizer.js +2 -5
  23. package/lib/module/components/BarVisualizer.js.map +1 -1
  24. package/lib/module/components/LiveKitRoom.js.map +1 -1
  25. package/lib/module/events/EventEmitter.js +1 -1
  26. package/lib/module/events/EventEmitter.js.map +1 -1
  27. package/lib/module/index.js +2 -0
  28. package/lib/module/index.js.map +1 -1
  29. package/lib/module/polyfills/MediaRecorderShim.js +11 -0
  30. package/lib/module/polyfills/MediaRecorderShim.js.map +1 -0
  31. package/lib/typescript/lib/commonjs/audio/MediaRecorder.d.ts +24 -0
  32. package/lib/typescript/lib/commonjs/polyfills/MediaRecorderShim.d.ts +1 -0
  33. package/lib/typescript/lib/module/audio/MediaRecorder.d.ts +22 -0
  34. package/lib/typescript/lib/module/polyfills/MediaRecorderShim.d.ts +1 -0
  35. package/lib/typescript/src/audio/MediaRecorder.d.ts +54 -0
  36. package/lib/typescript/src/components/LiveKitRoom.d.ts +1 -1
  37. package/lib/typescript/src/index.d.ts +2 -0
  38. package/lib/typescript/src/polyfills/MediaRecorderShim.d.ts +1 -0
  39. package/package.json +7 -5
  40. package/src/audio/MediaRecorder.ts +158 -0
  41. package/src/components/BarVisualizer.tsx +2 -9
  42. package/src/components/LiveKitRoom.tsx +1 -1
  43. package/src/events/EventEmitter.ts +5 -1
  44. package/src/index.tsx +2 -0
  45. 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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/react-native",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
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": "^125.0.12",
62
+ "@livekit/react-native-webrtc": "^137.0.0",
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.9.8",
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": "^125.0.12",
88
- "livekit-client": "^2.9.0",
89
+ "@livekit/react-native-webrtc": "^137.0.0",
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 + 5));
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 = (
@@ -50,7 +50,7 @@ export interface LiveKitRoomProps {
50
50
  screen?: ScreenShareCaptureOptions | boolean;
51
51
  /**
52
52
  * If set to true a connection to LiveKit room is initiated.
53
- * @defaultValue `false`
53
+ * @defaultValue `true`
54
54
  */
55
55
  connect?: boolean;
56
56
  /**
@@ -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 = ['LK_VOLUME_PROCESSED', 'LK_MULTIBAND_PROCESSED'];
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';
@@ -0,0 +1,12 @@
1
+ import { MediaRecorder } from "../audio/MediaRecorder";
2
+
3
+ function shimMediaRecorder() {
4
+ // @ts-expect-error
5
+ if(!global.MediaRecorder) {
6
+ // @ts-expect-error
7
+ global.MediaRecorder = MediaRecorder
8
+ }
9
+ MediaRecorder
10
+ }
11
+
12
+ shimMediaRecorder();