Live video on the AT Protocol
79
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #596 from streamplace/eli/fix-stereo

components: force Opus stereo audio when supported

authored by

Eli Mallon and committed by
GitHub
9a7b8674 9d52ec24

+55
+2
js/app/package.json
··· 91 91 "react-use-websocket": "^4.13.0", 92 92 "reanimated-color-picker": "^4.0.0", 93 93 "rtcaudiodevice": "git+https://github.com/streamplace/RTCAudioDevice.git#918e08a0f6f0818fb495a0db0b696b44d11d1336", 94 + "sdp-transform": "^2.15.0", 94 95 "stream-http": "^3.2.0", 95 96 "streamplace": "workspace:*", 96 97 "ua-parser-js": "^2.0.0-rc.1", ··· 111 112 "@types/babel__plugin-transform-runtime": "^7", 112 113 "@types/qrcode": "^1", 113 114 "@types/react": "~18.3.12", 115 + "@types/sdp-transform": "^2.15.0", 114 116 "@types/uuid": "^10.0.0", 115 117 "typescript": "~5.3.3" 116 118 },
+1
js/components/package.json
··· 17 17 "author": "Streamplace", 18 18 "license": "MIT", 19 19 "devDependencies": { 20 + "@types/sdp-transform": "^2.15.0", 20 21 "tsup": "^8.5.0" 21 22 }, 22 23 "dependencies": {
+31
js/components/src/components/mobile-player/use-webrtc.tsx
··· 1 1 import { useEffect, useRef, useState } from "react"; 2 + import * as sdpTransform from "sdp-transform"; 2 3 import { PlayerStatus, usePlayerStore, useStreamKey } from "../.."; 3 4 import { RTCPeerConnection, RTCSessionDescription } from "./webrtc-primitives"; 4 5 ··· 107 108 offerToReceiveAudio: true, 108 109 offerToReceiveVideo: true, 109 110 }); 111 + if (!offer.sdp) { 112 + throw Error("no SDP in offer"); 113 + } 114 + 115 + const newSDP = forceStereoAudio(offer.sdp); 116 + 117 + offer.sdp = newSDP; 110 118 /** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setLocalDescription */ 111 119 await peerConnection.setLocalDescription(offer); 112 120 ··· 297 305 298 306 return [mediaStream, setMediaStream]; 299 307 } 308 + 309 + export function forceStereoAudio(sdp: string): string { 310 + const parsedSDP = sdpTransform.parse(sdp); 311 + const audioMedia = parsedSDP.media.find((m) => m.type === "audio"); 312 + if (!audioMedia) { 313 + throw Error("no audio media in SDP"); 314 + } 315 + const opusCodec = audioMedia.rtp.find((c) => c.codec === "opus"); 316 + if (!opusCodec) { 317 + throw Error("no opus codec in SDP"); 318 + } 319 + const opusFMTP = audioMedia.fmtp.find((c) => c.payload === opusCodec.payload); 320 + if (!opusFMTP) { 321 + throw Error("no opus fmtp in SDP"); 322 + } 323 + const opusParams = sdpTransform.parseParams(opusFMTP.config); 324 + opusParams.stereo = 1; 325 + const newParams = Object.entries(opusParams) 326 + .map(([k, v]) => `${k}=${v}`) 327 + .join(";"); 328 + opusFMTP.config = newParams; 329 + return sdpTransform.write(parsedSDP); 330 + }
+21
pnpm-lock.yaml
··· 249 249 rtcaudiodevice: 250 250 specifier: git+https://github.com/streamplace/RTCAudioDevice.git#918e08a0f6f0818fb495a0db0b696b44d11d1336 251 251 version: https://codeload.github.com/streamplace/RTCAudioDevice/tar.gz/918e08a0f6f0818fb495a0db0b696b44d11d1336 252 + sdp-transform: 253 + specifier: ^2.15.0 254 + version: 2.15.0 252 255 stream-http: 253 256 specifier: ^3.2.0 254 257 version: 3.2.0 ··· 304 307 '@types/react': 305 308 specifier: ~18.3.12 306 309 version: 18.3.12 310 + '@types/sdp-transform': 311 + specifier: ^2.15.0 312 + version: 2.15.0 307 313 '@types/uuid': 308 314 specifier: ^10.0.0 309 315 version: 10.0.0 ··· 447 453 specifier: ^5.0.5 448 454 version: 5.0.5(immer@10.1.1)(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)) 449 455 devDependencies: 456 + '@types/sdp-transform': 457 + specifier: ^2.15.0 458 + version: 2.15.0 450 459 tsup: 451 460 specifier: ^8.5.0 452 461 version: 8.5.0(@swc/core@1.8.0(@swc/helpers@0.5.17))(jiti@2.4.2)(postcss@8.5.3)(typescript@5.8.3)(yaml@2.5.1) ··· 4622 4631 '@types/sax@1.2.7': 4623 4632 resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} 4624 4633 4634 + '@types/sdp-transform@2.15.0': 4635 + resolution: {integrity: sha512-ikIFF0EaYt/2XetIYYVeMj6SB52oVXFasJUXDzWHgzNJS5ep2Pbsu7f8f3Za+dEie8HQtt3Zr9mHYBpWT0XgxQ==} 4636 + 4625 4637 '@types/send@0.17.4': 4626 4638 resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} 4627 4639 ··· 10710 10722 schema-utils@4.2.0: 10711 10723 resolution: {integrity: sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==} 10712 10724 engines: {node: '>= 12.13.0'} 10725 + 10726 + sdp-transform@2.15.0: 10727 + resolution: {integrity: sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==} 10728 + hasBin: true 10713 10729 10714 10730 select-hose@2.0.0: 10715 10731 resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} ··· 11565 11581 11566 11582 ua-parser-js@2.0.0-rc.1: 11567 11583 resolution: {integrity: sha512-H+kTJv7j04XbMuASIIrp7TwKj8rVHfGLR9dI36FAWneGHyQ6JZVwFukFgxSyM/OChmk7dxsV82R/tnEQxb1EXg==} 11584 + deprecated: You are using an early pre-release version of ua-parser-js v2.x which may be unstable. Please update to the latest stable ua-parser-js v2.x release (https://uaparser.dev) 11568 11585 hasBin: true 11569 11586 11570 11587 uc.micro@1.0.6: ··· 18368 18385 '@types/sax@1.2.7': 18369 18386 dependencies: 18370 18387 '@types/node': 22.15.17 18388 + 18389 + '@types/sdp-transform@2.15.0': {} 18371 18390 18372 18391 '@types/send@0.17.4': 18373 18392 dependencies: ··· 26371 26390 ajv: 8.16.0 26372 26391 ajv-formats: 2.1.1(ajv@8.16.0) 26373 26392 ajv-keywords: 5.1.0(ajv@8.16.0) 26393 + 26394 + sdp-transform@2.15.0: {} 26374 26395 26375 26396 select-hose@2.0.0: {} 26376 26397