This commit is contained in:
2026-03-25 14:14:07 +01:00
parent d6b31e2ef7
commit a0073b4fb1
10368 changed files with 2214340 additions and 0 deletions

61
APP/nexus-remote/node_modules/sdp/.eslintrc generated vendored Normal file
View File

@@ -0,0 +1,61 @@
{
"rules": {
"array-bracket-spacing": 2,
"block-spacing": [2, "never"],
"brace-style": [2, "1tbs", {"allowSingleLine": false}],
"camelcase": [2, {"properties": "always"}],
"curly": 2,
"default-case": 2,
"dot-notation": 2,
"eqeqeq": 2,
"indent": [
2,
2,
{"SwitchCase": 1}
],
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
"max-len": [2, 80, 2, {"ignoreUrls": true}],
"new-cap": [2, {"newIsCapExceptions": [
"webkitRTCPeerConnection",
"mozRTCPeerConnection"
]}],
"no-console": 0,
"no-else-return": 2,
"no-eval": 2,
"no-multi-spaces": 2,
"no-multiple-empty-lines": [2, {"max": 2}],
"no-shadow": 2,
"no-trailing-spaces": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"args": "none"}],
"object-curly-spacing": [2, "never"],
"padded-blocks": [2, "never"],
"quotes": [
2,
"single"
],
"semi": [
2,
"always"
],
"keyword-spacing": 2,
"space-before-blocks": 2,
"space-before-function-paren": [2, "never"],
"space-unary-ops": 2,
"space-infix-ops": 2,
"spaced-comment": 2,
"valid-typeof": 2
},
"env": {
"es6": true,
"browser": true,
"node": true
},
"extends": ["eslint:recommended"],
"globals": {
"module": true,
"require": true,
"process": true,
"Promise": true,
}
}

View File

@@ -0,0 +1,16 @@
name: npm-test
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- run: npm test
- run: npm run coverage
- uses: codecov/codecov-action@v3
with:
file: ./coverage.lcov

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"uuid":"a9b935d8-5f14-4c64-b895-6b0ece2e9d7c","parent":null,"pid":5750,"argv":["/usr/bin/node","/media/fippo/houseparty/webrtc/sdp/node_modules/.bin/mocha","test/sdp.js"],"execArgv":[],"cwd":"/media/fippo/houseparty/webrtc/sdp","time":1673267457493,"ppid":5743,"root":"9110d26c-7a83-40c7-a41d-9670d698bc02","coverageFilename":"/media/fippo/houseparty/webrtc/sdp/.nyc_output/a9b935d8-5f14-4c64-b895-6b0ece2e9d7c.json","files":["/media/fippo/houseparty/webrtc/sdp/sdp.js"]}

View File

@@ -0,0 +1 @@
{"processes":{"a9b935d8-5f14-4c64-b895-6b0ece2e9d7c":{"parent":null,"children":[]}},"files":{"/media/fippo/houseparty/webrtc/sdp/sdp.js":["a9b935d8-5f14-4c64-b895-6b0ece2e9d7c"]},"externalIds":{}}

19
APP/nexus-remote/node_modules/sdp/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2017 Philipp Hancke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
APP/nexus-remote/node_modules/sdp/README.md generated vendored Normal file
View File

@@ -0,0 +1,4 @@
# SDP parsing and utlities
originally for generating SDP from ORTC and vice versa -- see https://github.com/fippo/adapter/tree/shim-ortc
But it turned out to be useful for manipulating SDP outside the context of Microsofts Edge browser.

1083
APP/nexus-remote/node_modules/sdp/coverage.lcov generated vendored Normal file

File diff suppressed because it is too large Load Diff

35
APP/nexus-remote/node_modules/sdp/package.json generated vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "sdp",
"version": "3.2.1",
"description": "SDP parsing and serialization utilities",
"main": "dist/sdp.js",
"module": "sdp.js",
"typings": "sdp.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/fippo/sdp.git"
},
"scripts": {
"build": "babel sdp.js --out-dir dist",
"coverage": "c8 report --reporter=text-lcov > coverage.lcov",
"prepare": "npm run build",
"test": "eslint sdp.js test/sdp.js && c8 --reporter html mocha test/sdp.js"
},
"keywords": [
"sdp",
"webrtc"
],
"author": "Philipp Hancke",
"license": "MIT",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"c8": "^7.12.0",
"chai": "^4.3.7",
"codecov": "^3.6.5",
"eslint": "^8.31.0",
"mocha": "^9.2.2",
"sinon": "^15.0.1",
"sinon-chai": "^3.7.0"
}
}

220
APP/nexus-remote/node_modules/sdp/sdp.d.ts generated vendored Normal file
View File

@@ -0,0 +1,220 @@
export type SDPBlob = string;
export type SDPLine = string;
export type SDPSection = string;
export type SDPDirection = 'sendonly' | 'recvonly' | 'sendrecv' | 'inactive';
export interface SDPIceCandidate {
foundation: string;
component: 'rtp' | 'rtcp' | number;
protocol: 'tcp' | 'udp';
priority: number;
ip: string;
address: string;
port: number;
type: 'host' | 'prflx' | 'srflx' | 'relay';
relatedAddress?: string;
relatedPort?: number;
tcpType?: string;
ufrag?: string;
usernameFragment?: string;
}
export interface SDPIceParameters {
iceLite?: boolean;
usernameFragment: string;
password: string;
}
export interface SDPCodecParameters {
payloadType: number;
preferredPayloadType?: number;
name: string;
clockRate: number;
channels: number;
numChannels?: number;
maxptime?: number;
}
export interface SDPCodecAdditionalParameters {
[key: string]: string;
}
export interface SDPHeaderExtension {
id: number;
direction?: SDPDirection;
uri: string;
attributes: string | undefined;
}
export interface SDPFeedbackParameter {
type: string;
parameter: string;
}
export interface SDPFingerprint {
algorithm: string;
value: string;
}
export interface SDPDtlsParameters {
role: string;
fingerprints: SDPFingerprint[];
}
export interface SDPMediaSource {
ssrc: number;
attribute?: string;
value?: string;
}
export interface SDPMediaSourceGroup {
semantics: string;
ssrcs: number[];
}
export interface SDPMediaStreamId {
stream: string;
track: string;
}
export interface SDPCodec extends SDPCodecParameters {
payloadType: number;
preferredPayloadType?: number;
parameters?: SDPCodecAdditionalParameters;
rtcpFeedback?: SDPFeedbackParameter[];
}
export interface SDPGroup {
semantics: string;
mids: string[];
}
export interface SDPMLine {
kind: string;
port?: number;
protocol: string;
fmt?: string;
}
export interface SDPOLine {
username: string;
sessionId: string;
sessionVersion: number;
netType: string;
addressType: string;
address: string;
}
export interface SDPRtcpParameters {
cname?: string;
ssrc?: number;
reducedSize?: boolean;
compound?: boolean;
mux?: boolean;
}
export interface SDPEncodingParameters {
ssrc: number;
codecPayloadType?: number;
rtx?: {
ssrc: number;
};
fec?: {
ssrc: number;
mechanism: string;
};
}
export interface SDPRtpCapabilities {
codecs: SDPCodec[];
headerExtensions: SDPHeaderExtension[];
fecMechanisms: string[];
rtcp?: SDPRtcpParameters[];
}
export interface SDPSctpDescription {
port: number;
protocol: string;
maxMessageSize?: number;
}
export const localCname: string;
export function generateIdentifier(): string;
export function splitLines(blob: SDPBlob): SDPLine[];
export function splitSections(blob: SDPBlob): SDPSection[];
export function getDescription(blob: SDPBlob): SDPSection;
export function getMediaSections(blob: SDPBlob): SDPSection[];
export function matchPrefix(blob: SDPBlob, prefix: string): SDPLine[];
export function parseCandidate(line: SDPLine): SDPIceCandidate;
export function writeCandidate(candidate: SDPIceCandidate): SDPLine;
export function parseIceOptions(line: SDPLine): string[];
export function parseRtpMap(line: SDPLine): SDPCodecParameters;
export function writeRtpMap(codec: SDPCodecParameters): SDPLine;
export function parseExtmap(line: SDPLine): SDPHeaderExtension;
export function writeExtmap(headerExtension: SDPHeaderExtension): SDPLine;
export function parseFmtp(line: SDPLine): SDPCodecAdditionalParameters;
export function writeFmtp(codec: SDPCodec): SDPLine;
export function parseRtcpFb(line: SDPLine): SDPFeedbackParameter;
export function writeRtcpFb(codec: SDPCodec): SDPLine[];
export function parseSsrcMedia(line: SDPLine): SDPMediaSource;
export function parseSsrcGroup(line: SDPLine): SDPMediaSourceGroup;
export function getMid(mediaSection: SDPSection): string;
export function parseFingerprint(line: SDPLine): SDPFingerprint;
export function getDtlsParameters(
mediaSection: SDPSection,
session: SDPSection
): SDPDtlsParameters;
export function writeDtlsParameters(params: SDPDtlsParameters, setupType: string): SDPLine;
export function getIceParameters(
mediaSection: SDPSection,
session: SDPSection
): SDPIceParameters;
export function writeIceParameters(params: SDPIceParameters): SDPLine;
export function parseRtpParameters(mediaSection: SDPSection): SDPRtpCapabilities;
export function writeRtpDescription(kind: string, caps: SDPRtpCapabilities): SDPSection;
export function parseRtpEncodingParameters(mediaSection: SDPSection): SDPEncodingParameters[];
export function parseRtcpParameters(mediaSection: SDPSection): SDPRtcpParameters;
export function writeRtcpParameters(params: SDPRtcpParameters): SDPLine;
export function parseMsid(mediaSection: SDPSection): SDPMediaStreamId;
export function parseSctpDescription(mediaSection: SDPSection): SDPSctpDescription;
export function writeSctpDescription(
mediaSection: SDPMLine,
desc: SDPSctpDescription
): SDPSection;
export function generateSessionId(): string;
export function writeSessionBoilerplate(
sessId?: string,
sessVer?: number,
sessUser?: string
): SDPBlob;
export function getDirection(mediaSection: SDPSection, sessionpart: SDPSection): SDPDirection;
export function getKind(mediaSection: SDPSection): string;
export function isRejected(mediaSection: SDPSection): boolean;
export function parseMLine(mediaSection: SDPSection): SDPMLine;
export function parseOLine(mediaSection: SDPSection): SDPOLine;
export function isValidSDP(blob: SDPBlob): boolean;

804
APP/nexus-remote/node_modules/sdp/sdp.js generated vendored Normal file
View File

@@ -0,0 +1,804 @@
/* eslint-env node */
'use strict';
// SDP helpers.
const SDPUtils = {};
// Generate an alphanumeric identifier for cname or mids.
// TODO: use UUIDs instead? https://gist.github.com/jed/982883
SDPUtils.generateIdentifier = function() {
return Math.random().toString(36).substring(2, 12);
};
// The RTCP CNAME used by all peerconnections from the same JS.
SDPUtils.localCName = SDPUtils.generateIdentifier();
// Splits SDP into lines, dealing with both CRLF and LF.
SDPUtils.splitLines = function(blob) {
return blob.trim().split('\n').map(line => line.trim());
};
// Splits SDP into sessionpart and mediasections. Ensures CRLF.
SDPUtils.splitSections = function(blob) {
const parts = blob.split('\nm=');
return parts.map((part, index) => (index > 0 ?
'm=' + part : part).trim() + '\r\n');
};
// Returns the session description.
SDPUtils.getDescription = function(blob) {
const sections = SDPUtils.splitSections(blob);
return sections && sections[0];
};
// Returns the individual media sections.
SDPUtils.getMediaSections = function(blob) {
const sections = SDPUtils.splitSections(blob);
sections.shift();
return sections;
};
// Returns lines that start with a certain prefix.
SDPUtils.matchPrefix = function(blob, prefix) {
return SDPUtils.splitLines(blob).filter(line => line.indexOf(prefix) === 0);
};
// Parses an ICE candidate line. Sample input:
// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
// rport 55996"
// Input can be prefixed with a=.
SDPUtils.parseCandidate = function(line) {
let parts;
// Parse both variants.
if (line.indexOf('a=candidate:') === 0) {
parts = line.substring(12).split(' ');
} else {
parts = line.substring(10).split(' ');
}
const candidate = {
foundation: parts[0],
component: {1: 'rtp', 2: 'rtcp'}[parts[1]] || parts[1],
protocol: parts[2].toLowerCase(),
priority: parseInt(parts[3], 10),
ip: parts[4],
address: parts[4], // address is an alias for ip.
port: parseInt(parts[5], 10),
// skip parts[6] == 'typ'
type: parts[7],
};
for (let i = 8; i < parts.length; i += 2) {
switch (parts[i]) {
case 'raddr':
candidate.relatedAddress = parts[i + 1];
break;
case 'rport':
candidate.relatedPort = parseInt(parts[i + 1], 10);
break;
case 'tcptype':
candidate.tcpType = parts[i + 1];
break;
case 'ufrag':
candidate.ufrag = parts[i + 1]; // for backward compatibility.
candidate.usernameFragment = parts[i + 1];
break;
default: // extension handling, in particular ufrag. Don't overwrite.
if (candidate[parts[i]] === undefined) {
candidate[parts[i]] = parts[i + 1];
}
break;
}
}
return candidate;
};
// Translates a candidate object into SDP candidate attribute.
// This does not include the a= prefix!
SDPUtils.writeCandidate = function(candidate) {
const sdp = [];
sdp.push(candidate.foundation);
const component = candidate.component;
if (component === 'rtp') {
sdp.push(1);
} else if (component === 'rtcp') {
sdp.push(2);
} else {
sdp.push(component);
}
sdp.push(candidate.protocol.toUpperCase());
sdp.push(candidate.priority);
sdp.push(candidate.address || candidate.ip);
sdp.push(candidate.port);
const type = candidate.type;
sdp.push('typ');
sdp.push(type);
if (type !== 'host' && candidate.relatedAddress &&
candidate.relatedPort) {
sdp.push('raddr');
sdp.push(candidate.relatedAddress);
sdp.push('rport');
sdp.push(candidate.relatedPort);
}
if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
sdp.push('tcptype');
sdp.push(candidate.tcpType);
}
if (candidate.usernameFragment || candidate.ufrag) {
sdp.push('ufrag');
sdp.push(candidate.usernameFragment || candidate.ufrag);
}
return 'candidate:' + sdp.join(' ');
};
// Parses an ice-options line, returns an array of option tags.
// Sample input:
// a=ice-options:foo bar
SDPUtils.parseIceOptions = function(line) {
return line.substring(14).split(' ');
};
// Parses a rtpmap line, returns RTCRtpCoddecParameters. Sample input:
// a=rtpmap:111 opus/48000/2
SDPUtils.parseRtpMap = function(line) {
let parts = line.substring(9).split(' ');
const parsed = {
payloadType: parseInt(parts.shift(), 10), // was: id
};
parts = parts[0].split('/');
parsed.name = parts[0];
parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
// legacy alias, got renamed back to channels in ORTC.
parsed.numChannels = parsed.channels;
return parsed;
};
// Generates a rtpmap line from RTCRtpCodecCapability or
// RTCRtpCodecParameters.
SDPUtils.writeRtpMap = function(codec) {
let pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
const channels = codec.channels || codec.numChannels || 1;
return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
(channels !== 1 ? '/' + channels : '') + '\r\n';
};
// Parses a extmap line (headerextension from RFC 5285). Sample input:
// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
SDPUtils.parseExtmap = function(line) {
const parts = line.substring(9).split(' ');
return {
id: parseInt(parts[0], 10),
direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
uri: parts[1],
attributes: parts.slice(2).join(' '),
};
};
// Generates an extmap line from RTCRtpHeaderExtensionParameters or
// RTCRtpHeaderExtension.
SDPUtils.writeExtmap = function(headerExtension) {
return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
(headerExtension.direction && headerExtension.direction !== 'sendrecv'
? '/' + headerExtension.direction
: '') +
' ' + headerExtension.uri +
(headerExtension.attributes ? ' ' + headerExtension.attributes : '') +
'\r\n';
};
// Parses a fmtp line, returns dictionary. Sample input:
// a=fmtp:96 vbr=on;cng=on
// Also deals with vbr=on; cng=on
// Non-key-value such as telephone-events `0-15` get parsed as
// {`0-15`:undefined}
SDPUtils.parseFmtp = function(line) {
const parsed = {};
let kv;
const parts = line.substring(line.indexOf(' ') + 1).split(';');
for (let j = 0; j < parts.length; j++) {
kv = parts[j].trim().split('=');
parsed[kv[0].trim()] = kv[1];
}
return parsed;
};
// Generates a fmtp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
SDPUtils.writeFmtp = function(codec) {
let line = '';
let pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
if (codec.parameters && Object.keys(codec.parameters).length) {
const params = [];
Object.keys(codec.parameters).forEach(param => {
if (codec.parameters[param] !== undefined) {
params.push(param + '=' + codec.parameters[param]);
} else {
params.push(param);
}
});
line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
}
return line;
};
// Parses a rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
// a=rtcp-fb:98 nack rpsi
SDPUtils.parseRtcpFb = function(line) {
const parts = line.substring(line.indexOf(' ') + 1).split(' ');
return {
type: parts.shift(),
parameter: parts.join(' '),
};
};
// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
SDPUtils.writeRtcpFb = function(codec) {
let lines = '';
let pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
// FIXME: special handling for trr-int?
codec.rtcpFeedback.forEach(fb => {
lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
(fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
'\r\n';
});
}
return lines;
};
// Parses a RFC 5576 ssrc media attribute. Sample input:
// a=ssrc:3735928559 cname:something
SDPUtils.parseSsrcMedia = function(line) {
const sp = line.indexOf(' ');
const parts = {
ssrc: parseInt(line.substring(7, sp), 10),
};
const colon = line.indexOf(':', sp);
if (colon > -1) {
parts.attribute = line.substring(sp + 1, colon);
parts.value = line.substring(colon + 1);
} else {
parts.attribute = line.substring(sp + 1);
}
return parts;
};
// Parse a ssrc-group line (see RFC 5576). Sample input:
// a=ssrc-group:semantics 12 34
SDPUtils.parseSsrcGroup = function(line) {
const parts = line.substring(13).split(' ');
return {
semantics: parts.shift(),
ssrcs: parts.map(ssrc => parseInt(ssrc, 10)),
};
};
// Extracts the MID (RFC 5888) from a media section.
// Returns the MID or undefined if no mid line was found.
SDPUtils.getMid = function(mediaSection) {
const mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
if (mid) {
return mid.substring(6);
}
};
// Parses a fingerprint line for DTLS-SRTP.
SDPUtils.parseFingerprint = function(line) {
const parts = line.substring(14).split(' ');
return {
algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
value: parts[1].toUpperCase(), // the definition is upper-case in RFC 4572.
};
};
// Extracts DTLS parameters from SDP media section or sessionpart.
// FIXME: for consistency with other functions this should only
// get the fingerprint line as input. See also getIceParameters.
SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
const lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
'a=fingerprint:');
// Note: a=setup line is ignored since we use the 'auto' role in Edge.
return {
role: 'auto',
fingerprints: lines.map(SDPUtils.parseFingerprint),
};
};
// Serializes DTLS parameters to SDP.
SDPUtils.writeDtlsParameters = function(params, setupType) {
let sdp = 'a=setup:' + setupType + '\r\n';
params.fingerprints.forEach(fp => {
sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
});
return sdp;
};
// Parses a=crypto lines into
// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members
SDPUtils.parseCryptoLine = function(line) {
const parts = line.substring(9).split(' ');
return {
tag: parseInt(parts[0], 10),
cryptoSuite: parts[1],
keyParams: parts[2],
sessionParams: parts.slice(3),
};
};
SDPUtils.writeCryptoLine = function(parameters) {
return 'a=crypto:' + parameters.tag + ' ' +
parameters.cryptoSuite + ' ' +
(typeof parameters.keyParams === 'object'
? SDPUtils.writeCryptoKeyParams(parameters.keyParams)
: parameters.keyParams) +
(parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') +
'\r\n';
};
// Parses the crypto key parameters into
// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam*
SDPUtils.parseCryptoKeyParams = function(keyParams) {
if (keyParams.indexOf('inline:') !== 0) {
return null;
}
const parts = keyParams.substring(7).split('|');
return {
keyMethod: 'inline',
keySalt: parts[0],
lifeTime: parts[1],
mkiValue: parts[2] ? parts[2].split(':')[0] : undefined,
mkiLength: parts[2] ? parts[2].split(':')[1] : undefined,
};
};
SDPUtils.writeCryptoKeyParams = function(keyParams) {
return keyParams.keyMethod + ':'
+ keyParams.keySalt +
(keyParams.lifeTime ? '|' + keyParams.lifeTime : '') +
(keyParams.mkiValue && keyParams.mkiLength
? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength
: '');
};
// Extracts all SDES parameters.
SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) {
const lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
'a=crypto:');
return lines.map(SDPUtils.parseCryptoLine);
};
// Parses ICE information from SDP media section or sessionpart.
// FIXME: for consistency with other functions this should only
// get the ice-ufrag and ice-pwd lines as input.
SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
const ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart,
'a=ice-ufrag:')[0];
const pwd = SDPUtils.matchPrefix(mediaSection + sessionpart,
'a=ice-pwd:')[0];
if (!(ufrag && pwd)) {
return null;
}
return {
usernameFragment: ufrag.substring(12),
password: pwd.substring(10),
};
};
// Serializes ICE parameters to SDP.
SDPUtils.writeIceParameters = function(params) {
let sdp = 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
'a=ice-pwd:' + params.password + '\r\n';
if (params.iceLite) {
sdp += 'a=ice-lite\r\n';
}
return sdp;
};
// Parses the SDP media section and returns RTCRtpParameters.
SDPUtils.parseRtpParameters = function(mediaSection) {
const description = {
codecs: [],
headerExtensions: [],
fecMechanisms: [],
rtcp: [],
};
const lines = SDPUtils.splitLines(mediaSection);
const mline = lines[0].split(' ');
description.profile = mline[2];
for (let i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
const pt = mline[i];
const rtpmapline = SDPUtils.matchPrefix(
mediaSection, 'a=rtpmap:' + pt + ' ')[0];
if (rtpmapline) {
const codec = SDPUtils.parseRtpMap(rtpmapline);
const fmtps = SDPUtils.matchPrefix(
mediaSection, 'a=fmtp:' + pt + ' ');
// Only the first a=fmtp:<pt> is considered.
codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
codec.rtcpFeedback = SDPUtils.matchPrefix(
mediaSection, 'a=rtcp-fb:' + pt + ' ')
.map(SDPUtils.parseRtcpFb);
description.codecs.push(codec);
// parse FEC mechanisms from rtpmap lines.
switch (codec.name.toUpperCase()) {
case 'RED':
case 'ULPFEC':
description.fecMechanisms.push(codec.name.toUpperCase());
break;
default: // only RED and ULPFEC are recognized as FEC mechanisms.
break;
}
}
}
SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(line => {
description.headerExtensions.push(SDPUtils.parseExtmap(line));
});
const wildcardRtcpFb = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:* ')
.map(SDPUtils.parseRtcpFb);
description.codecs.forEach(codec => {
wildcardRtcpFb.forEach(fb=> {
const duplicate = codec.rtcpFeedback.find(existingFeedback => {
return existingFeedback.type === fb.type &&
existingFeedback.parameter === fb.parameter;
});
if (!duplicate) {
codec.rtcpFeedback.push(fb);
}
});
});
// FIXME: parse rtcp.
return description;
};
// Generates parts of the SDP media section describing the capabilities /
// parameters.
SDPUtils.writeRtpDescription = function(kind, caps) {
let sdp = '';
// Build the mline.
sdp += 'm=' + kind + ' ';
sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
sdp += ' ' + (caps.profile || 'UDP/TLS/RTP/SAVPF') + ' ';
sdp += caps.codecs.map(codec => {
if (codec.preferredPayloadType !== undefined) {
return codec.preferredPayloadType;
}
return codec.payloadType;
}).join(' ') + '\r\n';
sdp += 'c=IN IP4 0.0.0.0\r\n';
sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
// Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
caps.codecs.forEach(codec => {
sdp += SDPUtils.writeRtpMap(codec);
sdp += SDPUtils.writeFmtp(codec);
sdp += SDPUtils.writeRtcpFb(codec);
});
let maxptime = 0;
caps.codecs.forEach(codec => {
if (codec.maxptime > maxptime) {
maxptime = codec.maxptime;
}
});
if (maxptime > 0) {
sdp += 'a=maxptime:' + maxptime + '\r\n';
}
if (caps.headerExtensions) {
caps.headerExtensions.forEach(extension => {
sdp += SDPUtils.writeExtmap(extension);
});
}
// FIXME: write fecMechanisms.
return sdp;
};
// Parses the SDP media section and returns an array of
// RTCRtpEncodingParameters.
SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
const encodingParameters = [];
const description = SDPUtils.parseRtpParameters(mediaSection);
const hasRed = description.fecMechanisms.indexOf('RED') !== -1;
const hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
// filter a=ssrc:... cname:, ignore PlanB-msid
const ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
.map(line => SDPUtils.parseSsrcMedia(line))
.filter(parts => parts.attribute === 'cname');
const primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
let secondarySsrc;
const flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
.map(line => {
const parts = line.substring(17).split(' ');
return parts.map(part => parseInt(part, 10));
});
if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
secondarySsrc = flows[0][1];
}
description.codecs.forEach(codec => {
if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
let encParam = {
ssrc: primarySsrc,
codecPayloadType: parseInt(codec.parameters.apt, 10),
};
if (primarySsrc && secondarySsrc) {
encParam.rtx = {ssrc: secondarySsrc};
}
encodingParameters.push(encParam);
if (hasRed) {
encParam = JSON.parse(JSON.stringify(encParam));
encParam.fec = {
ssrc: primarySsrc,
mechanism: hasUlpfec ? 'red+ulpfec' : 'red',
};
encodingParameters.push(encParam);
}
}
});
if (encodingParameters.length === 0 && primarySsrc) {
encodingParameters.push({
ssrc: primarySsrc,
});
}
// we support both b=AS and b=TIAS but interpret AS as TIAS.
let bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
if (bandwidth.length) {
if (bandwidth[0].indexOf('b=TIAS:') === 0) {
bandwidth = parseInt(bandwidth[0].substring(7), 10);
} else if (bandwidth[0].indexOf('b=AS:') === 0) {
// use formula from JSEP to convert b=AS to TIAS value.
bandwidth = parseInt(bandwidth[0].substring(5), 10) * 1000 * 0.95
- (50 * 40 * 8);
} else {
bandwidth = undefined;
}
encodingParameters.forEach(params => {
params.maxBitrate = bandwidth;
});
}
return encodingParameters;
};
// parses http://draft.ortc.org/#rtcrtcpparameters*
SDPUtils.parseRtcpParameters = function(mediaSection) {
const rtcpParameters = {};
// Gets the first SSRC. Note that with RTX there might be multiple
// SSRCs.
const remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
.map(line => SDPUtils.parseSsrcMedia(line))
.filter(obj => obj.attribute === 'cname')[0];
if (remoteSsrc) {
rtcpParameters.cname = remoteSsrc.value;
rtcpParameters.ssrc = remoteSsrc.ssrc;
}
// Edge uses the compound attribute instead of reducedSize
// compound is !reducedSize
const rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
rtcpParameters.reducedSize = rsize.length > 0;
rtcpParameters.compound = rsize.length === 0;
// parses the rtcp-mux attrіbute.
// Note that Edge does not support unmuxed RTCP.
const mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
rtcpParameters.mux = mux.length > 0;
return rtcpParameters;
};
SDPUtils.writeRtcpParameters = function(rtcpParameters) {
let sdp = '';
if (rtcpParameters.reducedSize) {
sdp += 'a=rtcp-rsize\r\n';
}
if (rtcpParameters.mux) {
sdp += 'a=rtcp-mux\r\n';
}
if (rtcpParameters.ssrc !== undefined && rtcpParameters.cname) {
sdp += 'a=ssrc:' + rtcpParameters.ssrc +
' cname:' + rtcpParameters.cname + '\r\n';
}
return sdp;
};
// parses either a=msid: or a=ssrc:... msid lines and returns
// the id of the MediaStream and MediaStreamTrack.
SDPUtils.parseMsid = function(mediaSection) {
let parts;
const spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
if (spec.length === 1) {
parts = spec[0].substring(7).split(' ');
return {stream: parts[0], track: parts[1]};
}
const planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
.map(line => SDPUtils.parseSsrcMedia(line))
.filter(msidParts => msidParts.attribute === 'msid');
if (planB.length > 0) {
parts = planB[0].value.split(' ');
return {stream: parts[0], track: parts[1]};
}
};
// SCTP
// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back
// to draft-ietf-mmusic-sctp-sdp-05
SDPUtils.parseSctpDescription = function(mediaSection) {
const mline = SDPUtils.parseMLine(mediaSection);
const maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:');
let maxMessageSize;
if (maxSizeLine.length > 0) {
maxMessageSize = parseInt(maxSizeLine[0].substring(19), 10);
}
if (isNaN(maxMessageSize)) {
maxMessageSize = 65536;
}
const sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:');
if (sctpPort.length > 0) {
return {
port: parseInt(sctpPort[0].substring(12), 10),
protocol: mline.fmt,
maxMessageSize,
};
}
const sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:');
if (sctpMapLines.length > 0) {
const parts = sctpMapLines[0]
.substring(10)
.split(' ');
return {
port: parseInt(parts[0], 10),
protocol: parts[1],
maxMessageSize,
};
}
};
// SCTP
// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers
// support by now receiving in this format, unless we originally parsed
// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line
// protocol of DTLS/SCTP -- without UDP/ or TCP/)
SDPUtils.writeSctpDescription = function(media, sctp) {
let output = [];
if (media.protocol !== 'DTLS/SCTP') {
output = [
'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\r\n',
'c=IN IP4 0.0.0.0\r\n',
'a=sctp-port:' + sctp.port + '\r\n',
];
} else {
output = [
'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\r\n',
'c=IN IP4 0.0.0.0\r\n',
'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n',
];
}
if (sctp.maxMessageSize !== undefined) {
output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n');
}
return output.join('');
};
// Generate a session ID for SDP.
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
// recommends using a cryptographically random +ve 64-bit value
// but right now this should be acceptable and within the right range
SDPUtils.generateSessionId = function() {
return Math.random().toString().substr(2, 22);
};
// Write boiler plate for start of SDP
// sessId argument is optional - if not supplied it will
// be generated randomly
// sessVersion is optional and defaults to 2
// sessUser is optional and defaults to 'thisisadapterortc'
SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) {
let sessionId;
const version = sessVer !== undefined ? sessVer : 2;
if (sessId) {
sessionId = sessId;
} else {
sessionId = SDPUtils.generateSessionId();
}
const user = sessUser || 'thisisadapterortc';
// FIXME: sess-id should be an NTP timestamp.
return 'v=0\r\n' +
'o=' + user + ' ' + sessionId + ' ' + version +
' IN IP4 127.0.0.1\r\n' +
's=-\r\n' +
't=0 0\r\n';
};
// Gets the direction from the mediaSection or the sessionpart.
SDPUtils.getDirection = function(mediaSection, sessionpart) {
// Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
const lines = SDPUtils.splitLines(mediaSection);
for (let i = 0; i < lines.length; i++) {
switch (lines[i]) {
case 'a=sendrecv':
case 'a=sendonly':
case 'a=recvonly':
case 'a=inactive':
return lines[i].substring(2);
default:
// FIXME: What should happen here?
}
}
if (sessionpart) {
return SDPUtils.getDirection(sessionpart);
}
return 'sendrecv';
};
SDPUtils.getKind = function(mediaSection) {
const lines = SDPUtils.splitLines(mediaSection);
const mline = lines[0].split(' ');
return mline[0].substring(2);
};
SDPUtils.isRejected = function(mediaSection) {
return mediaSection.split(' ', 2)[1] === '0';
};
SDPUtils.parseMLine = function(mediaSection) {
const lines = SDPUtils.splitLines(mediaSection);
const parts = lines[0].substring(2).split(' ');
return {
kind: parts[0],
port: parseInt(parts[1], 10),
protocol: parts[2],
fmt: parts.slice(3).join(' '),
};
};
SDPUtils.parseOLine = function(mediaSection) {
const line = SDPUtils.matchPrefix(mediaSection, 'o=')[0];
const parts = line.substring(2).split(' ');
return {
username: parts[0],
sessionId: parts[1],
sessionVersion: parseInt(parts[2], 10),
netType: parts[3],
addressType: parts[4],
address: parts[5],
};
};
// a very naive interpretation of a valid SDP.
SDPUtils.isValidSDP = function(blob) {
if (typeof blob !== 'string' || blob.length === 0) {
return false;
}
const lines = SDPUtils.splitLines(blob);
for (let i = 0; i < lines.length; i++) {
if (lines[i].length < 2 || lines[i].charAt(1) !== '=') {
return false;
}
// TODO: check the modifier a bit more.
}
return true;
};
// Expose public methods.
if (typeof module === 'object') {
module.exports = SDPUtils;
}