diff --git a/internal/servers/webrtc/publish_index.html b/internal/servers/webrtc/publish_index.html index 8176a664c6a..21e1fbe363b 100644 --- a/internal/servers/webrtc/publish_index.html +++ b/internal/servers/webrtc/publish_index.html @@ -376,11 +376,10 @@ const sections = sdp.split('m='); for (let i = 0; i < sections.length; i++) { - const section = sections[i]; - if (section.startsWith('video')) { - sections[i] = setCodec(section, videoForm.codec.value); - } else if (section.startsWith('audio')) { - sections[i] = setAudioBitrate(setCodec(section, audioForm.codec.value), audioForm.bitrate.value, audioForm.voice.checked); + if (sections[i].startsWith('video')) { + sections[i] = setCodec(sections[i], videoForm.codec.value); + } else if (sections[i].startsWith('audio')) { + sections[i] = setAudioBitrate(setCodec(sections[i], audioForm.codec.value), audioForm.bitrate.value, audioForm.voice.checked); } } @@ -391,9 +390,8 @@ const sections = sdp.split('m='); for (let i = 0; i < sections.length; i++) { - const section = sections[i]; - if (section.startsWith('video')) { - sections[i] = setVideoBitrate(section, videoForm.bitrate.value); + if (sections[i].startsWith('video')) { + sections[i] = setVideoBitrate(sections[i], videoForm.bitrate.value); } } diff --git a/internal/servers/webrtc/read_index.html b/internal/servers/webrtc/read_index.html index 4ae88043061..c42e8267037 100644 --- a/internal/servers/webrtc/read_index.html +++ b/internal/servers/webrtc/read_index.html @@ -50,6 +50,7 @@ const video = document.getElementById('video'); const message = document.getElementById('message'); +let nonAdvertisedCodecs = []; let pc = null; let restartTimeout = null; let sessionUrl = ''; @@ -87,14 +88,14 @@ }) : [] ); -const parseOffer = (offer) => { +const parseOffer = (sdp) => { const ret = { iceUfrag: '', icePwd: '', medias: [], }; - for (const line of offer.split('\r\n')) { + for (const line of sdp.split('\r\n')) { if (line.startsWith('m=')) { ret.medias.push(line.slice('m='.length)); } else if (ret.iceUfrag === '' && line.startsWith('a=ice-ufrag:')) { @@ -110,13 +111,13 @@ const enableStereoPcmau = (section) => { let lines = section.split('\r\n'); - lines[0] += " 118"; - lines.splice(lines.length - 1, 0, "a=rtpmap:118 PCMU/8000/2"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:118 transport-cc"); + lines[0] += ' 118'; + lines.splice(lines.length - 1, 0, 'a=rtpmap:118 PCMU/8000/2'); + lines.splice(lines.length - 1, 0, 'a=rtcp-fb:118 transport-cc'); - lines[0] += " 119"; - lines.splice(lines.length - 1, 0, "a=rtpmap:119 PCMA/8000/2"); - lines.splice(lines.length - 1, 0, "a=rtcp-fb:119 transport-cc"); + lines[0] += ' 119'; + lines.splice(lines.length - 1, 0, 'a=rtpmap:119 PCMA/8000/2'); + lines.splice(lines.length - 1, 0, 'a=rtcp-fb:119 transport-cc'); return lines.join('\r\n'); }; @@ -150,17 +151,22 @@ return lines.join('\r\n'); }; -const editOffer = (offer) => { - const sections = offer.sdp.split('m='); +const editOffer = (sdp) => { + const sections = sdp.split('m='); for (let i = 0; i < sections.length; i++) { - const section = sections[i]; - if (section.startsWith('audio')) { - sections[i] = enableStereoPcmau(enableStereoOpus(section)); + if (sections[i].startsWith('audio')) { + sections[i] = enableStereoOpus(sections[i]); + + if (nonAdvertisedCodecs.includes('pcma/8000/2')) { + sections[i] = enableStereoPcmau(sections[i]); + } + + break; } } - offer.sdp = sections.join('m='); + return sections.join('m='); }; const generateSdpFragment = (od, candidates) => { @@ -197,6 +203,70 @@ requestICEServers(); }; +const supportsNonAdvertisedCodec = (codec, fmtp) => ( + new Promise((resolve, reject) => { + const pc = new RTCPeerConnection({ iceServers: [] }); + pc.addTransceiver('audio', { direction: 'recvonly' }); + pc.createOffer() + .then((offer) => { + if (offer.sdp.includes(' ' + codec)) { // codec is advertised, there's no need to add it manually + resolve(false); + return; + } + const sections = offer.sdp.split('m=audio'); + const lines = sections[1].split('\r\n'); + lines[0] += ' 118'; + lines.splice(lines.length - 1, 0, 'a=rtpmap:118 ' + codec); + if (fmtp !== undefined) { + lines.splice(lines.length - 1, 0, 'a=fmtp:118 ' + fmtp); + } + sections[1] = lines.join('\r\n'); + offer.sdp = sections.join('m=audio'); + return pc.setLocalDescription(offer); + }) + .then(() => { + return pc.setRemoteDescription(new RTCSessionDescription({ + type: 'answer', + sdp: 'v=0\r\n' + + 'o=- 6539324223450680508 0 IN IP4 0.0.0.0\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=fingerprint:sha-256 0D:9F:78:15:42:B5:4B:E6:E2:94:3E:5B:37:78:E1:4B:54:59:A3:36:3A:E5:05:EB:27:EE:8F:D2:2D:41:29:25\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 118\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=ice-pwd:7c3bf4770007e7432ee4ea4d697db675\r\n' + + 'a=ice-ufrag:29e036dc\r\n' + + 'a=sendonly\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:118 ' + codec + '\r\n' + + ((fmtp !== undefined) ? 'a=fmtp:118 ' + fmtp + '\r\n' : ''), + })); + }) + .then(() => { + resolve(true); + }) + .catch((err) => { + resolve(false); + }) + .finally(() => { + pc.close(); + }); + }) +); + +const getNonAdvertisedCodecs = () => { + Promise.all([ + ['pcma/8000/2'], + ['multiopus/48000/6', 'channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2'], + ['L16/48000/2'] + ].map((c) => supportsNonAdvertisedCodec(c[0], c[1]).then((r) => (r) ? c[0] : false))) + .then((c) => c.filter((e) => e !== false)) + .then((codecs) => { + nonAdvertisedCodecs = codecs; + loadStream(); + }); +}; + const onError = (err) => { if (restartTimeout === null) { setMessage(err + ', retrying in some seconds'); @@ -309,7 +379,7 @@ const createOffer = () => { pc.createOffer() .then((offer) => { - editOffer(offer); + offer.sdp = editOffer(offer.sdp); offerData = parseOffer(offer.sdp); pc.setLocalDescription(offer); sendOffer(offer); @@ -380,7 +450,7 @@ const init = () => { loadAttributesFromQuery(); - loadStream(); + getNonAdvertisedCodecs(); }; window.addEventListener('DOMContentLoaded', init);