Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a817a45
added maxOpusPlaybackRate param
jpsantosbh Mar 8, 2025
1d8c330
Merge branch 'main' into joao/opus_config
jpsantosbh Mar 11, 2025
ff9f615
testing
jpsantosbh Mar 11, 2025
51ab08a
maxaveragebitrate
jpsantosbh Mar 11, 2025
0670610
add PlaybackRate enum
jpsantosbh Mar 12, 2025
950e8df
replace sdp with sdpTransform
jpsantosbh Mar 20, 2025
c65f62d
renaming parameters
jpsantosbh Mar 20, 2025
0941f1d
hardening
jpsantosbh Mar 20, 2025
94623fc
playground changes
jpsantosbh Mar 21, 2025
20bcb3e
Merge branch 'main' into joao/opus_config
jpsantosbh Mar 21, 2025
c7a4220
Merge branch 'main' into joao/opus_config
jpsantosbh Mar 21, 2025
07c55d2
Fix build
jpsantosbh Mar 25, 2025
db83282
cleanup
jpsantosbh Apr 8, 2025
93d1fb0
manual prettier
jpsantosbh Apr 15, 2025
78a1f78
Update internal/playground-js/src/fabric/index.js
jpsantosbh Apr 15, 2025
81a1b4c
Update packages/js/src/fabric/WSClient.ts
jpsantosbh Apr 15, 2025
8e924e0
flexible audio codec params
jpsantosbh May 6, 2025
8d0f26e
Merge remote-tracking branch 'origin/main' into joao/opus_config
jpsantosbh May 6, 2025
035d424
changeset
jpsantosbh May 6, 2025
dc0c11f
Atualizar o index.html
jpsantosbh May 7, 2025
51c5eea
Atualizar o index.html
jpsantosbh May 7, 2025
6e37d34
Atualizar o index.html
jpsantosbh May 7, 2025
00b65d9
Update internal/playground-js/src/fabric/index.html
jpsantosbh May 7, 2025
aad67a7
UI fixes
jpsantosbh May 7, 2025
41817e3
dependencies fix
jpsantosbh May 7, 2025
b8704ad
build fix
jpsantosbh May 7, 2025
255f0cf
formating
jpsantosbh May 7, 2025
37e545d
Update packages/webrtc/src/RTCPeer.ts
jpsantosbh May 7, 2025
a514e78
Update packages/js/src/fabric/interfaces/wsClient.ts
jpsantosbh May 7, 2025
5f99076
fixes
jpsantosbh May 7, 2025
5d5b474
fix
jpsantosbh May 7, 2025
43d5abe
fix test
jpsantosbh May 7, 2025
eb67f33
test fix
jpsantosbh May 7, 2025
b54aa5a
fix sdpMediaOrderHack
jpsantosbh May 7, 2025
4d49452
don't change the remote description
jpsantosbh May 8, 2025
9b38f4a
Merge branch 'main' into joao/opus_config
jpsantosbh May 26, 2025
7e5704c
cleanup
jpsantosbh May 26, 2025
9e7335b
Update audio codec selection and input handling in the demo
jpsantosbh May 26, 2025
a1ec706
Merge branch 'main' into joao/opus_config
jpsantosbh May 29, 2025
88534ea
testing
jpsantosbh Jun 4, 2025
eb4b374
testing
jpsantosbh Jun 4, 2025
aede240
Merge branch 'joao/opus_config' of github.com:signalwire/signalwire-j…
jpsantosbh Jun 4, 2025
97afc46
testing
jpsantosbh Jun 4, 2025
d9f1610
revert
jpsantosbh Jun 4, 2025
bd5996f
Merge main into joao/opus_config and resolve conflicts
jpsantosbh Jun 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
808 changes: 295 additions & 513 deletions internal/playground-js/src/fabric/index.html

Large diffs are not rendered by default.

25 changes: 23 additions & 2 deletions internal/playground-js/src/fabric/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,28 @@ window.dial = async ({ reattach = false } = {}) => {

const dialer = reattach ? client.reattach : client.dial

const call = await dialer({
const dialOptions = {
nodeId: steeringId,
to: document.getElementById('destination').value,
rootElement: document.getElementById('rootElement'),
video: document.getElementById('video').checked,
audio: document.getElementById('audio').checked,
})
}

if (document.getElementById('targetCodec').value.trim().length) {
const codecPrefix = document.getElementById('targetCodec').value
if (document.getElementById('maxPlaybackRate').value.trim().length) {
dialOptions[`${codecPrefix}MaxPlaybackRate`] = parseInt(
document.getElementById('maxPlaybackRate').value
)
}
if (document.getElementById('maxAverageBitrate').value.trim().length) {
dialOptions[`${codecPrefix}maxAverageBitrate`] = parseInt(
document.getElementById('maxAverageBitrate').value
)
}
}
const call = await dialer(dialOptions)

window.__call = call
roomObj = call
Expand Down Expand Up @@ -888,6 +903,12 @@ window.ready(async function () {
document.getElementById('audio').checked = true
document.getElementById('video').checked =
localStorage.getItem('fabric.ws.video') === 'true'
document.getElementById('targetCodec').value =
localStorage.getItem('fabric.ws.targetCodec') || ''
document.getElementById('maxPlaybackRate').value =
localStorage.getItem('fabric.ws.maxPlaybackRate') || ''
document.getElementById('maxAverageBitrate').value =
localStorage.getItem('fabric.ws.maxAverageBitrate') || ''

const urlParams = new URLSearchParams(window.location.search)
const room = urlParams.get('room')
Expand Down
23 changes: 17 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"devDependencies": {
"@types/node": "^20.3.2",
"@types/sdp-transform": "^2.4.9",
"dotenv": "^16.3.1",
"jest-websocket-mock": "^2.5.0",
"mock-socket": "^9.3.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
asyncRetry,
increasingDelay,
decreasingDelay,
constDelay
constDelay,
} from './utils'
import { WEBRTC_EVENT_TYPES, isWebrtcEventType } from './utils/common'
import { BaseSession } from './BaseSession'
Expand Down Expand Up @@ -81,7 +81,7 @@ export {
asyncRetry,
increasingDelay,
decreasingDelay,
constDelay
constDelay,
}

export * from './redux/features/component/componentSlice'
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,15 @@ export type Promisify<T> = {
export type Prettify<T> = NonNullable<unknown> & {
[K in keyof T]: Prettify<T[K]>
} & {}

export enum PlaybackRate {
hz_8000 = 8000,
hz_12000 = 12000,
hz_16000 = 16000,
hz_24000 = 24000,
hz_48000 = 48000,
}

export const isPlaybackRate = (value: unknown): value is PlaybackRate => {
return Object.values(PlaybackRate).includes(value as PlaybackRate)
}
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,4 @@ export const isJSONRPCResponse = (
export const isSATAuth = (e?: Authorization): e is SATAuthorization => {
return typeof e !== 'undefined' && 'jti' in e
}

34 changes: 32 additions & 2 deletions packages/js/src/fabric/WSClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
VertoBye,
VertoSubscribe,
VideoRoomSubscribedEventParams,
isPlaybackRate
} from '@signalwire/core'
import { MakeRoomOptions } from '../video'
import { createFabricRoomSessionObject } from './FabricRoomSession'
Expand Down Expand Up @@ -154,12 +155,39 @@ export class WSClient extends BaseClient<{}> implements WSClientContract {
return room
}

private buildOutboundCall(params: DialParams & { attach?: boolean }) {
const [pathname, query] = params.to.split('?')
private _validateDialParams(params: DialParams) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this validator outside of the class? Since it seems to be a utility which does not need a class instance.

Maybe we move it here: packages/js/src/fabric/utils/validateCallDial.ts

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a logic made exclusive for the class should be defined in another module?
this isn't a generic "utility" this is part of the dial() function just refactored to a function to add more semantics to the code.

Do you prefer this code to be inside the dial function?

const [pathname] = params.to.split('?')

if (!pathname) {
throw new Error('Invalid destination address')
}

if (params.opusMaxPlaybackRate) {
if (!isPlaybackRate(params.opusMaxPlaybackRate)) {
throw new Error('Invalid opusMaxPlaybackRate')
}
if (typeof params.audio === 'object') {
if (
params.audio?.sampleRate &&
params.audio?.sampleRate !== params.opusMaxPlaybackRate
) {
throw new Error(
'Mismatching parameters: opusMaxPlaybackRate, audio.sampleRate'
)
}
}
}

if (params.opusMaxAverageBitrate && params.opusMaxAverageBitrate <= 0) {
throw new Error('Invalid opusMaxPlaybackRate')
}
}

private buildOutboundCall(params: DialParams & { attach?: boolean }) {
this._validateDialParams(params)

const [, query] = params.to.split('?')

let video = false
let negotiateVideo = false

Expand All @@ -186,6 +214,8 @@ export class WSClient extends BaseClient<{}> implements WSClientContract {
attach: params.attach ?? false,
disableUdpIceServers: params.disableUdpIceServers || false,
userVariables: params.userVariables || this.wsClientOptions.userVariables,
opusMaxPlaybackRate: params.opusMaxPlaybackRate,
opusMaxAverageBitrate: params.opusMaxAverageBitrate,
})

// WebRTC connection left the room.
Expand Down
6 changes: 5 additions & 1 deletion packages/js/src/fabric/interfaces/wsClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UserOptions } from '@signalwire/core'
import { PlaybackRate, UserOptions } from '@signalwire/core'
import { IncomingCallHandlers } from './incomingCallManager'
import { FabricRoomSession } from '../FabricRoomSession'
import { ApiRequestRetriesOptions } from '../SATSession'
Expand Down Expand Up @@ -94,6 +94,10 @@ export interface CallParams {
negotiateVideo?: boolean
/** User & UserAgent metadata */
userVariables?: WSClientOptions['userVariables']
/** OPUS audio codec max playback rate in Hz */
opusMaxPlaybackRate?: PlaybackRate
/** OPUS audio codec max average bitrate in Hz */
opusMaxAverageBitrate?: number
}

export interface DialParams extends CallParams {
Expand Down
2 changes: 1 addition & 1 deletion packages/webrtc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"dependencies": {
"@signalwire/core": "4.2.1",
"sdp": "^3.2.0"
"sdp-transform": "2.15.0"
},
"types": "dist/cjs/webrtc/src/index.d.ts"
}
36 changes: 25 additions & 11 deletions packages/webrtc/src/RTCPeer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
filterIceServers,
} from './utils/helpers'
import {
sdpStereoHack,
sdpBitrateHack,
sdpMediaOrderHack,
sdpHasValidCandidates,
updateSDPForOpus,
} from './utils/sdpHelpers'
import { BaseConnection } from './BaseConnection'
import {
Expand Down Expand Up @@ -71,6 +71,8 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
this.options
)

this._validateOptions()

this._onIce = this._onIce.bind(this)
this._onEndedTrackHandler = this._onEndedTrackHandler.bind(this)

Expand All @@ -86,6 +88,16 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
this.rtcConfigPolyfill = this.config
}

private _validateOptions() {
if (
this.options.useStereo === true &&
typeof this.options.audio === 'object' &&
(this.options.audio.channelCount ?? 2) != 2
) {
throw new Error('Mismatch params: useStereo, audio.channelCount')
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a utility function not associated with the class instance. Should we move this to a utility file?

Also, should we throw an error from the constructor or when the actual API where this is used is called?

If you are expecting this to be passed by the developer, you will need to expose the useStereo on the DialParams.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a utility function not associated with the class instance. Should we move this to a utility file?
Why did you have this impression? This utility is to validate the options passed in this class constructor according to the assumptions in this class implementation?

Also, should we throw an error from the constructor or when the actual API where this is used is called?
What actual API? The constructor is the one that receives the options params.

If you are expecting this to be passed by the developer, you will need to expose the useStereo on the DialParams.
I'm not making assumptions about who sets the useStero at this point.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you have this impression?

The definition of the "utility function".

What actual API?

The actual API that creates this instance. For CF SDK, that would be the dial method.

All the above were questions: if you think that you don't need this, then we can leave it here as it is.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, no need to expose the param to the developer that is not the goal of this PR.

I'm still confused about external "utility" idea...

This is the same case as above. This isn't a generic "utility" this is a "guard" for the assumptions/implementation defined in this class alone.


get options() {
return this.call.options
}
Expand Down Expand Up @@ -800,14 +812,13 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
}

private _setLocalDescription(localDescription: RTCSessionDescriptionInit) {
const {
useStereo,
googleMaxBitrate,
googleMinBitrate,
googleStartBitrate,
} = this.options
if (localDescription.sdp && useStereo) {
localDescription.sdp = sdpStereoHack(localDescription.sdp)
const { googleMaxBitrate, googleMinBitrate, googleStartBitrate } =
this.options
if (localDescription.sdp) {
localDescription.sdp = updateSDPForOpus(
localDescription.sdp,
this.options
)
}
if (
localDescription.sdp &&
Expand All @@ -832,8 +843,11 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
}

private _setRemoteDescription(remoteDescription: RTCSessionDescriptionInit) {
if (remoteDescription.sdp && this.options.useStereo) {
remoteDescription.sdp = sdpStereoHack(remoteDescription.sdp)
if (remoteDescription.sdp) {
remoteDescription.sdp = updateSDPForOpus(
remoteDescription.sdp,
this.options
)
}
if (remoteDescription.sdp && this.instance.localDescription) {
remoteDescription.sdp = sdpMediaOrderHack(
Expand Down
Loading
Loading