Skip to content

Commit bb8be9e

Browse files
committed
Merge branch 'main' into stats
2 parents 903953a + 2797909 commit bb8be9e

File tree

72 files changed

+1609
-360
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1609
-360
lines changed

packages/audio-filters-web/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
44

5+
## [0.5.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/audio-filters-web-0.4.3...@stream-io/audio-filters-web-0.5.0) (2025-09-30)
6+
7+
### Features
8+
9+
- Audio profiles and Hi-Fi stereo audio ([#1887](https://github.com/GetStream/stream-video-js/issues/1887)) ([3b60c89](https://github.com/GetStream/stream-video-js/commit/3b60c89b8c0dbc40544fe13be79c10e93bbddd3d))
10+
511
## [0.4.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/audio-filters-web-0.4.2...@stream-io/audio-filters-web-0.4.3) (2025-07-25)
612

713
### Bug Fixes

packages/audio-filters-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stream-io/audio-filters-web",
3-
"version": "0.4.3",
3+
"version": "0.5.0",
44
"main": "./dist/index.cjs.js",
55
"module": "./dist/index.es.js",
66
"types": "./dist/index.d.ts",

packages/audio-filters-web/src/NoiseCancellation.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,13 @@ export class NoiseCancellation implements INoiseCancellation {
269269
if (!this.filterNode || !this.audioContext) {
270270
throw new Error('NoiseCancellation is not initialized');
271271
}
272+
273+
const [audioTrack] = mediaStream.getAudioTracks();
274+
if (!audioTrack) throw new Error('No audio track found in the stream');
275+
272276
const source = this.audioContext.createMediaStreamSource(mediaStream);
273277
const destination = this.audioContext.createMediaStreamDestination();
278+
destination.channelCount = audioTrack.getSettings().channelCount ?? 1;
274279

275280
source.connect(this.filterNode).connect(destination);
276281
// When filter is started, user's microphone media stream is active.

packages/client/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
44

5+
## [1.33.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.33.0...@stream-io/video-client-1.33.1) (2025-10-02)
6+
7+
### Bug Fixes
8+
9+
- ensure ingress participants are prioritized ([#1943](https://github.com/GetStream/stream-video-js/issues/1943)) ([a51a119](https://github.com/GetStream/stream-video-js/commit/a51a119cfb9f13736395b4afb3d3947ef994a6d9))
10+
11+
## [1.33.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.32.0...@stream-io/video-client-1.33.0) (2025-09-30)
12+
13+
### Features
14+
15+
- Audio profiles and Hi-Fi stereo audio ([#1887](https://github.com/GetStream/stream-video-js/issues/1887)) ([3b60c89](https://github.com/GetStream/stream-video-js/commit/3b60c89b8c0dbc40544fe13be79c10e93bbddd3d))
16+
17+
### Bug Fixes
18+
19+
- **client:** server side pinning ([#1936](https://github.com/GetStream/stream-video-js/issues/1936)) ([cd33b9e](https://github.com/GetStream/stream-video-js/commit/cd33b9e4417e8fdc452b6d4a192e10183ddfa31b))
20+
521
## [1.32.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.31.0...@stream-io/video-client-1.32.0) (2025-09-29)
622

723
### Features

packages/client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stream-io/video-client",
3-
"version": "1.32.0",
3+
"version": "1.33.1",
44
"main": "dist/index.cjs.js",
55
"module": "dist/index.es.js",
66
"browser": "dist/index.browser.es.js",
@@ -42,6 +42,7 @@
4242
"@rollup/plugin-typescript": "^12.1.2",
4343
"@stream-io/audio-filters-web": "workspace:^",
4444
"@stream-io/node-sdk": "^0.4.24",
45+
"@total-typescript/shoehorn": "^0.1.2",
4546
"@types/sdp-transform": "^2.4.9",
4647
"@types/ua-parser-js": "^0.7.39",
4748
"@vitest/coverage-v8": "^3.1.3",

packages/client/src/Call.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Publisher,
99
Subscriber,
1010
toRtcConfiguration,
11+
TrackPublishOptions,
1112
trackTypeToParticipantStreamKey,
1213
} from './rtc';
1314
import {
@@ -1770,9 +1771,14 @@ export class Call {
17701771
*
17711772
* @param mediaStream the media stream to publish.
17721773
* @param trackType the type of the track to announce.
1774+
* @param options the publish options.
17731775
*/
1774-
publish = async (mediaStream: MediaStream, trackType: TrackType) => {
1775-
if (!this.sfuClient) throw new Error(`Call not joined yet.`);
1776+
publish = async (
1777+
mediaStream: MediaStream,
1778+
trackType: TrackType,
1779+
options?: TrackPublishOptions,
1780+
) => {
1781+
if (!this.sfuClient) throw new Error(`Call is not joined yet`);
17761782
// joining is in progress, and we should wait until the client is ready
17771783
await this.sfuClient.joinTask;
17781784

@@ -1797,15 +1803,16 @@ export class Call {
17971803
}
17981804

17991805
pushToIfMissing(this.trackPublishOrder, trackType);
1800-
await this.publisher.publish(track, trackType);
1806+
await this.publisher.publish(track, trackType, options);
18011807

18021808
const trackTypes = [trackType];
18031809
if (trackType === TrackType.SCREEN_SHARE) {
18041810
const [audioTrack] = mediaStream.getAudioTracks();
18051811
if (audioTrack) {
1806-
pushToIfMissing(this.trackPublishOrder, TrackType.SCREEN_SHARE_AUDIO);
1807-
await this.publisher.publish(audioTrack, TrackType.SCREEN_SHARE_AUDIO);
1808-
trackTypes.push(TrackType.SCREEN_SHARE_AUDIO);
1812+
const screenShareAudio = TrackType.SCREEN_SHARE_AUDIO;
1813+
pushToIfMissing(this.trackPublishOrder, screenShareAudio);
1814+
await this.publisher.publish(audioTrack, screenShareAudio, options);
1815+
trackTypes.push(screenShareAudio);
18091816
}
18101817
}
18111818

packages/client/src/__tests__/Call.publishing.test.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('Publishing and Unpublishing tracks', () => {
3535
describe('Validations', () => {
3636
it('publishing is not allowed only when call is not joined', async () => {
3737
const ms = new MediaStream();
38-
const err = 'Call not joined yet.';
38+
const err = 'Call is not joined yet';
3939
await expect(call.publish(ms, TrackType.VIDEO)).rejects.toThrowError(err);
4040
await expect(call.publish(ms, TrackType.AUDIO)).rejects.toThrowError(err);
4141
await expect(
@@ -140,7 +140,11 @@ describe('Publishing and Unpublishing tracks', () => {
140140
vi.spyOn(mediaStream, 'getVideoTracks').mockReturnValue([track]);
141141

142142
await call.publish(mediaStream, TrackType.VIDEO);
143-
expect(publisher.publish).toHaveBeenCalledWith(track, TrackType.VIDEO);
143+
expect(publisher.publish).toHaveBeenCalledWith(
144+
track,
145+
TrackType.VIDEO,
146+
undefined,
147+
);
144148
expect(call['trackPublishOrder']).toEqual([TrackType.VIDEO]);
145149

146150
expect(sfuClient.updateMuteStates).toHaveBeenCalledWith([
@@ -159,7 +163,11 @@ describe('Publishing and Unpublishing tracks', () => {
159163
vi.spyOn(mediaStream, 'getAudioTracks').mockReturnValue([track]);
160164

161165
await call.publish(mediaStream, TrackType.AUDIO);
162-
expect(publisher.publish).toHaveBeenCalledWith(track, TrackType.AUDIO);
166+
expect(publisher.publish).toHaveBeenCalledWith(
167+
track,
168+
TrackType.AUDIO,
169+
undefined,
170+
);
163171
expect(call['trackPublishOrder']).toEqual([TrackType.AUDIO]);
164172

165173
expect(sfuClient.updateMuteStates).toHaveBeenCalledWith([
@@ -181,6 +189,7 @@ describe('Publishing and Unpublishing tracks', () => {
181189
expect(publisher.publish).toHaveBeenCalledWith(
182190
track,
183191
TrackType.SCREEN_SHARE,
192+
undefined,
184193
);
185194
expect(call['trackPublishOrder']).toEqual([TrackType.SCREEN_SHARE]);
186195

@@ -205,10 +214,12 @@ describe('Publishing and Unpublishing tracks', () => {
205214
expect(publisher.publish).toHaveBeenCalledWith(
206215
videoTrack,
207216
TrackType.SCREEN_SHARE,
217+
undefined,
208218
);
209219
expect(publisher.publish).toHaveBeenCalledWith(
210220
audioTrack,
211221
TrackType.SCREEN_SHARE_AUDIO,
222+
undefined,
212223
);
213224
expect(call['trackPublishOrder']).toEqual([
214225
TrackType.SCREEN_SHARE,

packages/client/src/__tests__/StreamVideoClient.api.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const tokenProvider = (userId: string) => {
2525
};
2626
};
2727

28-
describe.skip('StreamVideoClient - coordinator API', () => {
28+
describe('StreamVideoClient - coordinator API', () => {
2929
let client: StreamVideoClient;
3030
const user = {
3131
id: 'sara',
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { DeviceManager } from './DeviceManager';
2+
import { AudioDeviceManagerState } from './AudioDeviceManagerState';
3+
import { AudioBitrateProfile } from '../gen/video/sfu/models/models';
4+
import { TrackPublishOptions } from '../rtc';
5+
6+
/**
7+
* Base class for High Fidelity enabled Device Managers.
8+
*/
9+
export abstract class AudioDeviceManager<
10+
S extends AudioDeviceManagerState<C>,
11+
C = MediaTrackConstraints,
12+
> extends DeviceManager<S, C> {
13+
/**
14+
* Sets the audio bitrate profile and stereo mode.
15+
*/
16+
async setAudioBitrateProfile(profile: AudioBitrateProfile) {
17+
if (!this.call.state.settings?.audio.hifi_audio_enabled) {
18+
throw new Error('High Fidelity audio is not enabled for this call');
19+
}
20+
this.doSetAudioBitrateProfile(profile);
21+
this.state.setAudioBitrateProfile(profile);
22+
if (this.enabled) {
23+
await this.applySettingsToStream();
24+
}
25+
}
26+
27+
/**
28+
* Overrides the default `publishStream` method to inject the audio bitrate profile.
29+
*/
30+
protected override publishStream(
31+
stream: MediaStream,
32+
options?: TrackPublishOptions,
33+
): Promise<void> {
34+
return super.publishStream(stream, {
35+
audioBitrateProfile: this.state.audioBitrateProfile,
36+
...options,
37+
});
38+
}
39+
40+
/**
41+
* Applies Device Manager's specific audio profile settings.
42+
*/
43+
protected abstract doSetAudioBitrateProfile(
44+
profile: AudioBitrateProfile,
45+
): void;
46+
}
47+
48+
/**
49+
* Prepares a new MediaTrackConstraints set based on the provided arguments.
50+
*/
51+
export const createAudioConstraints = (
52+
profile: AudioBitrateProfile,
53+
): MediaTrackConstraints => {
54+
const stereo = profile === AudioBitrateProfile.MUSIC_HIGH_QUALITY;
55+
return {
56+
echoCancellation: !stereo,
57+
noiseSuppression: !stereo,
58+
autoGainControl: !stereo,
59+
channelCount: { ideal: stereo ? 2 : 1 },
60+
};
61+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { BehaviorSubject, distinctUntilChanged, Observable } from 'rxjs';
2+
import { AudioBitrateProfile } from '../gen/video/sfu/models/models';
3+
import { DeviceManagerState, TrackDisableMode } from './DeviceManagerState';
4+
import { RxUtils } from './../store';
5+
import { BrowserPermission } from './BrowserPermission';
6+
7+
/**
8+
* Base state class for High Fidelity enabled device managers.
9+
*/
10+
export abstract class AudioDeviceManagerState<C> extends DeviceManagerState<C> {
11+
private readonly audioBitrateProfileSubject: BehaviorSubject<AudioBitrateProfile>;
12+
13+
/** An Observable that emits the current audio bitrate profile. */
14+
audioBitrateProfile$: Observable<AudioBitrateProfile>;
15+
16+
/**
17+
* Constructs a new AudioDeviceManagerState instance.
18+
*/
19+
protected constructor(
20+
disableMode: TrackDisableMode,
21+
permission: BrowserPermission | undefined,
22+
profile: AudioBitrateProfile,
23+
) {
24+
super(disableMode, permission);
25+
this.audioBitrateProfileSubject = new BehaviorSubject(profile);
26+
this.audioBitrateProfile$ = this.audioBitrateProfileSubject
27+
.asObservable()
28+
.pipe(distinctUntilChanged());
29+
}
30+
31+
/**
32+
* Returns the current audio bitrate profile.
33+
*/
34+
get audioBitrateProfile() {
35+
return RxUtils.getCurrentValue(this.audioBitrateProfile$);
36+
}
37+
38+
/**
39+
* Sets the audio bitrate profile and stereo mode.
40+
*/
41+
setAudioBitrateProfile(profile: AudioBitrateProfile) {
42+
RxUtils.setCurrentValue(this.audioBitrateProfileSubject, profile);
43+
}
44+
}

0 commit comments

Comments
 (0)