Skip to content

Commit

Permalink
fix!: extract MP3 encoder plugin (#2447)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MartinCupela authored Jul 10, 2024
1 parent 238e801 commit 625196f
Show file tree
Hide file tree
Showing 17 changed files with 255 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ The dialog can be customized by passing own component to `Channel` component con
<Channel RecordingPermissionDeniedNotification={CustomComponent}>
```

## Custom encoding

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.

1. The library `@breezystack/lamejs` has to be installed as this is a peer dependency to `stream-chat-react`.

```shell
npm install @breezystack/lamejs
```

```shell
yarn add @breezystack/lamejs
```

2. The MP3 encoder has to be imported separately as a plugin:

```tsx
import { MessageInput } from 'stream-chat-react';
import { encodeToMp3 } from 'stream-chat-react/mp3-encoder';

<MessageInput focus audioRecordingConfig={{ transcoderConfig: { encoder: encodeToMp3 } }} />;
```

## Audio recorder states

The `AudioRecorder` UI switches between the following states
Expand Down
48 changes: 48 additions & 0 deletions docusaurus/docs/React/release-guides/upgrade-to-v12.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,48 @@ title: Upgrade to v12
keywords: [migration guide, upgrade, v12, breaking changes]
---

## Audio recordings transcoding

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.

:::important
**Action required**<br/>

1. The library `@breezystack/lamejs` has to be installed as this is a peer dependency to `stream-chat-react`.

```shell
npm install @breezystack/lamejs
```

```shell
yarn add @breezystack/lamejs
```

2. The MP3 encoder has to be imported separately as a plugin:

```tsx
import { MessageInput } from 'stream-chat-react';
import { encodeToMp3 } from 'stream-chat-react/mp3-encoder';

<MessageInput focus audioRecordingConfig={{ transcoderConfig: { encoder: encodeToMp3 } }} />;
```

:::

## EmojiPickerIcon extraction to emojis plugin

The default `EmojiPickerIcon` has been moved to emojis plugin from which we already import `EmojiPicker` component.

:::important
**Action required**<br/>
In case you are importing `EmojiPickerIcon` in your code, make sure to adjust the import as follows:

```tsx
import { EmojiPickerIcon } from 'stream-chat-react/emojis';
```

:::

## Removal of duplicate uploads state in MessageInput

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

Or

```
import 'stream-chat-react/dist/css/v2/index.css';
```

The legacy stylesheet has been removed from the SDK bundle, and therefore it is only possible to import one stylesheet from now on:

```
Expand Down
6 changes: 4 additions & 2 deletions docusaurus/react-docusaurus-dontent-docs.plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ module.exports = {
[
'@docusaurus/plugin-content-docs',
{
lastVersion: 'current',
lastVersion: '11.x.x',
versions: {
current: {
label: 'v12',
banner: 'unreleased',
label: 'v12 (rc)',
path: 'v12',
},
'11.x.x': {
label: 'v11',
Expand Down
20 changes: 15 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@
"default": "./dist/index.js"
},
"./emojis": {
"types": "./dist/components/Emojis/index.d.ts",
"require": "./dist/components/Emojis/index.cjs.js",
"import": "./dist/components/Emojis/index.js",
"default": "./dist/components/Emojis/index.js"
"types": "./dist/plugins/Emojis/index.d.ts",
"require": "./dist/plugins/Emojis/index.cjs.js",
"import": "./dist/plugins/Emojis/index.js",
"default": "./dist/plugins/Emojis/index.js"
},
"./mp3-encoder": {
"types": "./dist/plugins/encoders/mp3.d.ts",
"require": "./dist/plugins/encoders/mp3.cjs.js",
"import": "./dist/plugins/encoders/mp3.js",
"default": "./dist/plugins/encoders/mp3.js"
},
"./dist/css/*": {
"default": "./dist/css/*"
Expand Down Expand Up @@ -60,7 +66,6 @@
],
"dependencies": {
"@braintree/sanitize-url": "^6.0.4",
"@breezystack/lamejs": "^1.2.7",
"@popperjs/core": "^2.11.5",
"@react-aria/focus": "^3",
"clsx": "^2.0.0",
Expand Down Expand Up @@ -98,6 +103,7 @@
"mml-react": "^0.4.7"
},
"peerDependencies": {
"@breezystack/lamejs": "^1.2.7",
"@emoji-mart/data": "^1.1.0",
"@emoji-mart/react": "^1.1.0",
"emoji-mart": "^5.4.0",
Expand All @@ -106,6 +112,9 @@
"stream-chat": "^8.33.1"
},
"peerDependenciesMeta": {
"@breezystack/lamejs": {
"optional": true
},
"emoji-mart": {
"optional": true
},
Expand All @@ -131,6 +140,7 @@
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.12.7",
"@breezystack/lamejs": "^1.2.7",
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@emoji-mart/data": "^1.1.2",
Expand Down
6 changes: 3 additions & 3 deletions scripts/bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import * as esbuild from 'esbuild';
const __dirname = dirname(fileURLToPath(import.meta.url));

const sdkEntrypoint = resolve(__dirname, '../src/index.ts');
const emojiEntrypoint = resolve(__dirname, '../src/components/Emojis/index.ts');
const emojiEntrypoint = resolve(__dirname, '../src/plugins/Emojis/index.ts');
const mp3EncoderEntrypoint = resolve(__dirname, '../src/plugins/encoders/mp3.ts');
const outDir = resolve(__dirname, '../dist');

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

const cjsBundleConfig = {
entryPoints: [sdkEntrypoint, emojiEntrypoint],
entryPoints: [sdkEntrypoint, emojiEntrypoint, mp3EncoderEntrypoint],
bundle: true,
format: 'cjs',
platform: 'node',
Expand Down
1 change: 0 additions & 1 deletion src/components/Emojis/index.ts

This file was deleted.

23 changes: 6 additions & 17 deletions src/components/MediaRecorder/classes/MediaRecorderController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from './AmplitudeRecorder';
import { BrowserPermission } from './BrowserPermission';
import { BehaviorSubject, Subject } from '../observable';
import { transcode } from '../transcode';
import { transcode, TranscoderConfig } from '../transcode';
import { resampleWaveformData } from '../../Attachment';
import {
createFileFromBlobs,
Expand All @@ -30,8 +30,6 @@ const RECORDED_MIME_TYPE_BY_BROWSER = {
},
} as const;

export const POSSIBLE_TRANSCODING_MIME_TYPES = ['audio/wav', 'audio/mp3'] as const;

export const DEFAULT_MEDIA_RECORDER_CONFIG: MediaRecorderConfig = {
mimeType: isSafari()
? RECORDED_MIME_TYPE_BY_BROWSER.audio.safari
Expand All @@ -40,7 +38,6 @@ export const DEFAULT_MEDIA_RECORDER_CONFIG: MediaRecorderConfig = {

export const DEFAULT_AUDIO_TRANSCODER_CONFIG: TranscoderConfig = {
sampleRate: 16000,
targetMimeType: 'audio/mp3',
} as const;

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

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

type SupportedTranscodeMimeTypes = typeof POSSIBLE_TRANSCODING_MIME_TYPES[number];

export type TranscoderConfig = {
// defaults to 16000Hz
sampleRate: number;
// Defaults to audio/mp3;
targetMimeType: SupportedTranscodeMimeTypes;
};

type MediaRecorderConfig = Omit<MediaRecorderOptions, 'mimeType'> &
Required<Pick<MediaRecorderOptions, 'mimeType'>>;

Expand All @@ -71,8 +59,12 @@ export type AudioRecorderConfig = {
transcoderConfig: TranscoderConfig;
};

type PartialValues<T> = { [P in keyof T]?: Partial<T[P]> };

export type CustomAudioRecordingConfig = PartialValues<AudioRecorderConfig>;

export type AudioRecorderOptions = {
config?: Partial<AudioRecorderConfig>;
config?: CustomAudioRecordingConfig;
generateRecordingTitle?: (mimeType: string) => string;
t?: TranslationContextValue['t'];
};
Expand Down Expand Up @@ -135,9 +127,6 @@ export class MediaRecorderController<
{ ...config?.transcoderConfig },
DEFAULT_AUDIO_TRANSCODER_CONFIG,
);
if (!POSSIBLE_TRANSCODING_MIME_TYPES.includes(this.transcoderConfig.targetMimeType)) {
this.transcoderConfig.targetMimeType = DEFAULT_AUDIO_TRANSCODER_CONFIG.targetMimeType;
}

const mediaType = getRecordedMediaTypeFromMimeType(this.mediaRecorderConfig.mimeType);
if (!mediaType) {
Expand Down
Loading

0 comments on commit 625196f

Please sign in to comment.