Skip to content

Commit 625196f

Browse files
authored
fix!: extract MP3 encoder plugin (#2447)
BREAKING CHANGE: MP3 audio encoder has to be explicitly imported and used as a plugin for audio recordings. The default audio recording format is audio/wav. BREAKING CHANGE: @breezystack/lamejs became a peer dependency and has to be installed by the integrator so that the MP3 audio encoder can work properly.
1 parent 238e801 commit 625196f

File tree

17 files changed

+255
-76
lines changed

17 files changed

+255
-76
lines changed

docusaurus/docs/React/components/message-input-components/audio-recorder.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,29 @@ The dialog can be customized by passing own component to `Channel` component con
5151
<Channel RecordingPermissionDeniedNotification={CustomComponent}>
5252
```
5353

54+
## Custom encoding
55+
56+
By default, the recording is encoded into `audio/wav` format. In order to reduce the size and keep the inter-browser format compatibility, you can use an MP3 encoder that is based on [`lamejs` implementation](https://github.com/gideonstele/lamejs). Follow these steps to achieve the MP3 encoding capability.
57+
58+
1. The library `@breezystack/lamejs` has to be installed as this is a peer dependency to `stream-chat-react`.
59+
60+
```shell
61+
npm install @breezystack/lamejs
62+
```
63+
64+
```shell
65+
yarn add @breezystack/lamejs
66+
```
67+
68+
2. The MP3 encoder has to be imported separately as a plugin:
69+
70+
```tsx
71+
import { MessageInput } from 'stream-chat-react';
72+
import { encodeToMp3 } from 'stream-chat-react/mp3-encoder';
73+
74+
<MessageInput focus audioRecordingConfig={{ transcoderConfig: { encoder: encodeToMp3 } }} />;
75+
```
76+
5477
## Audio recorder states
5578

5679
The `AudioRecorder` UI switches between the following states

docusaurus/docs/React/release-guides/upgrade-to-v12.mdx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,48 @@ title: Upgrade to v12
44
keywords: [migration guide, upgrade, v12, breaking changes]
55
---
66

7+
## Audio recordings transcoding
8+
9+
Until now, the audio recordings were transcoded to `audio/mp3` format for inter-browser compatibility and size reduction. However, as of the v12, the MIME type `audio/wav` will be the default. The MP3 encoder use is opt-in from now on.
10+
11+
:::important
12+
**Action required**<br/>
13+
14+
1. The library `@breezystack/lamejs` has to be installed as this is a peer dependency to `stream-chat-react`.
15+
16+
```shell
17+
npm install @breezystack/lamejs
18+
```
19+
20+
```shell
21+
yarn add @breezystack/lamejs
22+
```
23+
24+
2. The MP3 encoder has to be imported separately as a plugin:
25+
26+
```tsx
27+
import { MessageInput } from 'stream-chat-react';
28+
import { encodeToMp3 } from 'stream-chat-react/mp3-encoder';
29+
30+
<MessageInput focus audioRecordingConfig={{ transcoderConfig: { encoder: encodeToMp3 } }} />;
31+
```
32+
33+
:::
34+
35+
## EmojiPickerIcon extraction to emojis plugin
36+
37+
The default `EmojiPickerIcon` has been moved to emojis plugin from which we already import `EmojiPicker` component.
38+
39+
:::important
40+
**Action required**<br/>
41+
In case you are importing `EmojiPickerIcon` in your code, make sure to adjust the import as follows:
42+
43+
```tsx
44+
import { EmojiPickerIcon } from 'stream-chat-react/emojis';
45+
```
46+
47+
:::
48+
749
## Removal of duplicate uploads state in MessageInput
850

951
As of the version 12 of `stream-chat-react` the `MessageInputContext` will not expose the following state variables:
@@ -138,6 +180,12 @@ Until now, it was possible to import two stylesheets as follows:
138180
import 'stream-chat-react/dist/css/v1/index.css';
139181
```
140182

183+
Or
184+
185+
```
186+
import 'stream-chat-react/dist/css/v2/index.css';
187+
```
188+
141189
The legacy stylesheet has been removed from the SDK bundle, and therefore it is only possible to import one stylesheet from now on:
142190

143191
```

docusaurus/react-docusaurus-dontent-docs.plugin.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ module.exports = {
33
[
44
'@docusaurus/plugin-content-docs',
55
{
6-
lastVersion: 'current',
6+
lastVersion: '11.x.x',
77
versions: {
88
current: {
9-
label: 'v12',
9+
banner: 'unreleased',
10+
label: 'v12 (rc)',
11+
path: 'v12',
1012
},
1113
'11.x.x': {
1214
label: 'v11',

package.json

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@
2121
"default": "./dist/index.js"
2222
},
2323
"./emojis": {
24-
"types": "./dist/components/Emojis/index.d.ts",
25-
"require": "./dist/components/Emojis/index.cjs.js",
26-
"import": "./dist/components/Emojis/index.js",
27-
"default": "./dist/components/Emojis/index.js"
24+
"types": "./dist/plugins/Emojis/index.d.ts",
25+
"require": "./dist/plugins/Emojis/index.cjs.js",
26+
"import": "./dist/plugins/Emojis/index.js",
27+
"default": "./dist/plugins/Emojis/index.js"
28+
},
29+
"./mp3-encoder": {
30+
"types": "./dist/plugins/encoders/mp3.d.ts",
31+
"require": "./dist/plugins/encoders/mp3.cjs.js",
32+
"import": "./dist/plugins/encoders/mp3.js",
33+
"default": "./dist/plugins/encoders/mp3.js"
2834
},
2935
"./dist/css/*": {
3036
"default": "./dist/css/*"
@@ -60,7 +66,6 @@
6066
],
6167
"dependencies": {
6268
"@braintree/sanitize-url": "^6.0.4",
63-
"@breezystack/lamejs": "^1.2.7",
6469
"@popperjs/core": "^2.11.5",
6570
"@react-aria/focus": "^3",
6671
"clsx": "^2.0.0",
@@ -98,6 +103,7 @@
98103
"mml-react": "^0.4.7"
99104
},
100105
"peerDependencies": {
106+
"@breezystack/lamejs": "^1.2.7",
101107
"@emoji-mart/data": "^1.1.0",
102108
"@emoji-mart/react": "^1.1.0",
103109
"emoji-mart": "^5.4.0",
@@ -106,6 +112,9 @@
106112
"stream-chat": "^8.33.1"
107113
},
108114
"peerDependenciesMeta": {
115+
"@breezystack/lamejs": {
116+
"optional": true
117+
},
109118
"emoji-mart": {
110119
"optional": true
111120
},
@@ -131,6 +140,7 @@
131140
"@babel/preset-env": "^7.12.7",
132141
"@babel/preset-react": "^7.23.3",
133142
"@babel/preset-typescript": "^7.12.7",
143+
"@breezystack/lamejs": "^1.2.7",
134144
"@commitlint/cli": "^18.4.3",
135145
"@commitlint/config-conventional": "^18.4.3",
136146
"@emoji-mart/data": "^1.1.2",

scripts/bundle.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import * as esbuild from 'esbuild';
88
const __dirname = dirname(fileURLToPath(import.meta.url));
99

1010
const sdkEntrypoint = resolve(__dirname, '../src/index.ts');
11-
const emojiEntrypoint = resolve(__dirname, '../src/components/Emojis/index.ts');
11+
const emojiEntrypoint = resolve(__dirname, '../src/plugins/Emojis/index.ts');
12+
const mp3EncoderEntrypoint = resolve(__dirname, '../src/plugins/encoders/mp3.ts');
1213
const outDir = resolve(__dirname, '../dist');
1314

1415
// Those dependencies are distributed as ES modules, and cannot be externalized
1516
// in our CJS bundle. We convert them to CJS and bundle them instead.
1617
const bundledDeps = [
17-
'@breezystack/lamejs',
1818
'hast-util-find-and-replace',
1919
'unist-builder',
2020
'unist-util-visit',
@@ -32,7 +32,7 @@ const deps = Object.keys({
3232
const external = deps.filter((dep) => !bundledDeps.includes(dep));
3333

3434
const cjsBundleConfig = {
35-
entryPoints: [sdkEntrypoint, emojiEntrypoint],
35+
entryPoints: [sdkEntrypoint, emojiEntrypoint, mp3EncoderEntrypoint],
3636
bundle: true,
3737
format: 'cjs',
3838
platform: 'node',

src/components/Emojis/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/components/MediaRecorder/classes/MediaRecorderController.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from './AmplitudeRecorder';
88
import { BrowserPermission } from './BrowserPermission';
99
import { BehaviorSubject, Subject } from '../observable';
10-
import { transcode } from '../transcode';
10+
import { transcode, TranscoderConfig } from '../transcode';
1111
import { resampleWaveformData } from '../../Attachment';
1212
import {
1313
createFileFromBlobs,
@@ -30,8 +30,6 @@ const RECORDED_MIME_TYPE_BY_BROWSER = {
3030
},
3131
} as const;
3232

33-
export const POSSIBLE_TRANSCODING_MIME_TYPES = ['audio/wav', 'audio/mp3'] as const;
34-
3533
export const DEFAULT_MEDIA_RECORDER_CONFIG: MediaRecorderConfig = {
3634
mimeType: isSafari()
3735
? RECORDED_MIME_TYPE_BY_BROWSER.audio.safari
@@ -40,7 +38,6 @@ export const DEFAULT_MEDIA_RECORDER_CONFIG: MediaRecorderConfig = {
4038

4139
export const DEFAULT_AUDIO_TRANSCODER_CONFIG: TranscoderConfig = {
4240
sampleRate: 16000,
43-
targetMimeType: 'audio/mp3',
4441
} as const;
4542

4643
const disposeOfMediaStream = (stream?: MediaStream) => {
@@ -53,15 +50,6 @@ const disposeOfMediaStream = (stream?: MediaStream) => {
5350

5451
const logError = (e?: Error) => e && console.error('[MEDIA RECORDER ERROR]', e);
5552

56-
type SupportedTranscodeMimeTypes = typeof POSSIBLE_TRANSCODING_MIME_TYPES[number];
57-
58-
export type TranscoderConfig = {
59-
// defaults to 16000Hz
60-
sampleRate: number;
61-
// Defaults to audio/mp3;
62-
targetMimeType: SupportedTranscodeMimeTypes;
63-
};
64-
6553
type MediaRecorderConfig = Omit<MediaRecorderOptions, 'mimeType'> &
6654
Required<Pick<MediaRecorderOptions, 'mimeType'>>;
6755

@@ -71,8 +59,12 @@ export type AudioRecorderConfig = {
7159
transcoderConfig: TranscoderConfig;
7260
};
7361

62+
type PartialValues<T> = { [P in keyof T]?: Partial<T[P]> };
63+
64+
export type CustomAudioRecordingConfig = PartialValues<AudioRecorderConfig>;
65+
7466
export type AudioRecorderOptions = {
75-
config?: Partial<AudioRecorderConfig>;
67+
config?: CustomAudioRecordingConfig;
7668
generateRecordingTitle?: (mimeType: string) => string;
7769
t?: TranslationContextValue['t'];
7870
};
@@ -135,9 +127,6 @@ export class MediaRecorderController<
135127
{ ...config?.transcoderConfig },
136128
DEFAULT_AUDIO_TRANSCODER_CONFIG,
137129
);
138-
if (!POSSIBLE_TRANSCODING_MIME_TYPES.includes(this.transcoderConfig.targetMimeType)) {
139-
this.transcoderConfig.targetMimeType = DEFAULT_AUDIO_TRANSCODER_CONFIG.targetMimeType;
140-
}
141130

142131
const mediaType = getRecordedMediaTypeFromMimeType(this.mediaRecorderConfig.mimeType);
143132
if (!mediaType) {

0 commit comments

Comments
 (0)