@wireapp/avs 9.4.19 → 9.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/avs_pc.d.ts CHANGED
@@ -4,6 +4,7 @@ export type AudioStreamHandler = (convid: string, stream_id: string, streams: re
4
4
  export type VideoStreamHandler = (convid: string, remote_userid: string, remote_clientid: string, streams: readonly MediaStream[] | null) => void;
5
5
  declare function pc_InitModule(module: any, logh: WcallLogHandler): void;
6
6
  declare function pc_SetUserMediaHandler(umh: UserMediaHandler): void;
7
+ declare function pc_IsConferenceCallingSupported(): boolean;
7
8
  declare function pc_SetAudioStreamHandler(ash: AudioStreamHandler): void;
8
9
  declare function pc_SetVideoStreamHandler(vsh: VideoStreamHandler): void;
9
10
  declare function pc_ReplaceTrack(convid: string, newTrack: MediaStreamTrack): void;
@@ -16,6 +17,7 @@ declare const _default: {
16
17
  setUserMediaHandler: typeof pc_SetUserMediaHandler;
17
18
  setAudioStreamHandler: typeof pc_SetAudioStreamHandler;
18
19
  setVideoStreamHandler: typeof pc_SetVideoStreamHandler;
20
+ isConferenceCallingSupported: typeof pc_IsConferenceCallingSupported;
19
21
  replaceTrack: typeof pc_ReplaceTrack;
20
22
  getStats: typeof pc_GetStats;
21
23
  };
package/dist/avs_pc.js CHANGED
@@ -6,14 +6,17 @@ var logFn = null;
6
6
  var userMediaHandler = null;
7
7
  var audioStreamHandler = null;
8
8
  var videoStreamHandler = null;
9
+ var insertableLegacy = false;
10
+ var insertableStreams = false;
9
11
  var pc_env = 0;
10
12
  var pc_envver = 0;
11
13
  var worker;
12
14
  // Use inlined worker instead....
13
15
  //let worker = new Worker('/worker/avs-worker.js', {name: 'AVS worker'});
14
16
  // This is the content of the avs-worker.js file, copied here.
15
- var workerContent = "\n\nvar coders = {};\n\nconst HDR_VERSION = 0;\n\n/*\nHeader\n\n 1 1 1 1 1 1\n 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n| V=0 |E| RES |S|LEN |1|KLEN |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n| KID... (length=KLEN) |\n+-------------------------------+\n| CTR... (length=LEN) |\n+-------------------------------+\n\nExtensions\n\n 0 1 2 3 4 5 6 7\n+-+-+-+-+-+-+-+-+---------------------------+\n|M| ELN | EID | VAL... (length=ELEN) |\n+-+-+-+-+-+-+-+-+---------------------------+\n*/\n\nfunction calcLen(val) {\n let l = 0;\n\n if (val == 0)\n\treturn 1;\n\n while (val != 0) {\n\tl++;\n\tval = val >>> 8;\t\n }\n\n return l;\n}\n\nfunction writeBytes(buf, p, len, val)\n{\n switch (len) {\n case 8:\n\tbuf[p] = (val >>> 56) & 0xff;\n\tp++;\n\n case 7:\n\tbuf[p] = (val >>> 48) & 0xff;\n\tp++;\n\n case 6:\n\tbuf[p] = (val >>> 40) & 0xff;\n\tp++;\n\t\n case 5:\n\tbuf[p] = (val >>> 32) & 0xff;\n\tp++;\n\n case 4:\n\tbuf[p] = (val >>> 24) & 0xff;\n\tp++;\n\n case 3:\n\tbuf[p] = (val >>> 16) & 0xff;\n\tp++;\n\n case 2:\n\tbuf[p] = (val >>> 8) & 0xff;\n\tp++;\n\n case 1:\n\tbuf[p] = val & 0xff;\n\tbreak;\n\n default:\n\treturn 0;\n }\n \n return len;\n}\n\nfunction readBytes(buf, p, len) {\n let l = len;\n let v = 0;\n\n while (l > 0) {\n\tv = v * 256;\n\tv = v + buf[p];\n\tp++;\n\tl--;\n }\n\n const lv = {\n\tlen: len,\n\tval: v\n };\n \n return lv;\n}\n\nfunction frameHeaderDecode(buf) {\n\n const MAX_EXTS = 16;\n let ext = buf[0] & 0x10;\n const flen = ((buf[1] >>> 4) & 0x7) + 1;\n const x = (buf[1] >>> 3) & 0x1;\n const klen = buf[1] & 0x7;\n let p = 2;\n let kid = -1;\n let fid = -1;\n let csrc = \"0\";\n let i = 0;\n\n if (x) {\n\tconst lv = readBytes(buf, p, klen + 1);\n\n\tp += lv.len;\n\tkid = lv.val;\n }\n else {\n\tkid = klen;\n }\n\n const lv = readBytes(buf, p, flen);\n p += lv.len;\n fid = lv.val;\n\n while(ext && p < buf.length && i < MAX_EXTS) {\n\tconst b = buf[p];\n\text = b & 0x80;\n\tconst elen = ((b >>> 4) & 0x7) + 1;\n\tconst eid = (b & 0xf);\n\n if (eid == 1 && elen == 4) {\n const csrco = readBytes(buf, p + 1, elen);\n csrc = csrco.val.toString();\n }\n p += elen + 1;\n i++;\n }\n\n if (kid < 0 || fid < 0) {\n\treturn null;\n }\n else {\n\tconst frameHdr = {\n\t len: p,\n\t frameId: fid,\n\t keyId: kid,\n\t csrc: csrc\n\t}\n\treturn frameHdr;\n }\n}\n\nfunction frameHeaderEncode(fid, kid, csrc)\n{\n const ab = new ArrayBuffer(32);\n const buf = new Uint8Array(ab);\n let sig = 0;\n let x = 0;\n let klen = 0;\n let flen = 0;\n let p = 2;\n let ext = (csrc > 0) ? 1 : 0;\n\n if (kid > 7) {\n\tx = 1;\n\tklen = calcLen(kid) - 1;\n }\n else {\n\tklen = kid;\n }\n\n flen = calcLen(fid);\n\n buf[0] = (HDR_VERSION << 5) + (ext << 4);\n buf[1] = (sig << 7) + ((flen - 1) << 4) + (x << 3) + klen;\n if (x) {\n\tp += writeBytes(buf, p, klen + 1, kid);\n }\n p += writeBytes(buf, p, flen, fid);\n\n if (csrc > 0) {\n buf[p] = 0x31;\n p++;\n p+= writeBytes(buf, p, 4, csrc);\n }\n return buf.subarray(0, p);\n}\n\nfunction xor_iv(iv, fid, kid) {\n const oiv = new Uint8Array(iv);\n\n oiv[0] = oiv[0] ^ ((fid >> 24) & 0xFF);\n oiv[1] = oiv[1] ^ ((fid >> 16) & 0xFF);\n oiv[2] = oiv[2] ^ ((fid >> 8) & 0xFF);\n oiv[3] = oiv[3] ^ ((fid ) & 0xFF);\n oiv[4] = oiv[4] ^ ((kid >> 24) & 0xFF);\n oiv[5] = oiv[5] ^ ((kid >> 16) & 0xFF);\n oiv[6] = oiv[6] ^ ((kid >> 8) & 0xFF);\n oiv[7] = oiv[7] ^ ((kid ) & 0xFF);\n\n return oiv;\n}\n\nfunction getMediaKey(self, index) {\n postMessage({\n\top: \"getMediaKey\",\n\tself: self,\n\tindex: index\n });\n}\n\nfunction getCurrentMediaKey(self, index) {\n postMessage({\n\top: \"getCurrentMediaKey\",\n\tself: self\n });\n}\n\nfunction doLog(str) {\n postMessage({\n\top: \"log\",\n\tlevel: 1,\n\tlogString: str\n });\n}\n\nfunction encryptFrame(coder, rtcFrame, controller) {\n const dataBuf = rtcFrame.data;\n const data = new Uint8Array(dataBuf);\n const t = Object.prototype.toString.call(rtcFrame);\n const isVideo = t === '[object RTCEncodedVideoFrame]';\n const userid = \"self\";\n\n const meta = rtcFrame.getMetadata();\n const ssrc = meta.synchronizationSource;\n\n if (isVideo && !coder.video.first_recv) {\n doLog(\"frame_enc: encrypt: first frame received type: video uid: \" +\n userid.substring(0,8) + \" fid: \" + coder.frameId + \" ssrc: \" + ssrc);\n coder.video.first_recv = true;\n coder.video.first_succ = false;\n }\n if (!isVideo && !coder.audio.first_recv) {\n doLog(\"frame_enc: encrypt: first frame received type: audio uid: \" +\n userid.substring(0,8) + \" fid: \" + coder.frameId + \" ssrc: \" + ssrc);\n coder.audio.first_recv = true;\n coder.audio.first_succ = false;\n }\n\n if (coder.currentKey == null) {\n getCurrentMediaKey(coder.self);\n return;\n }\n\n const baseiv = isVideo ? coder.video.iv : coder.audio.iv;\n const iv = xor_iv(baseiv, coder.frameId, coder.currentKey.id);\n \n const hdr = frameHeaderEncode(coder.frameId, coder.currentKey.id, ssrc);\n crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: iv,\n tagLength: 128,\n additionalData: hdr\n },\n coder.currentKey.key,\n data\n )\n .then(function(encdata){\n const enc8 = new Uint8Array(encdata); \t \n const enc = new Uint8Array(hdr.length + enc8.length);\n \n enc.set(hdr);\n enc.set(enc8, hdr.length);\n\n rtcFrame.data = enc.buffer;\n controller.enqueue(rtcFrame);\n\n if (isVideo && !coder.video.first_succ) {\n doLog(\"frame_enc: encrypt: first frame encrypted type: video uid: \" +\n userid.substring(0,8) + \" fid: \" + coder.frameId + \" csrc: \" + ssrc);\n coder.video.first_succ = true;\n }\n if (!isVideo && !coder.audio.first_succ) {\n doLog(\"frame_enc: encrypt: first frame encrypted type: audio uid: \" +\n userid.substring(0,8) + \" fid: \" + coder.frameId + \" csrc: \" + ssrc);\n coder.audio.first_succ = true;\n }\n\n coder.frameId++;\n });\n}\n\nfunction decryptFrame(coder, rtcFrame, controller) {\n const dataBuf = rtcFrame.data;\n const data = new Uint8Array(dataBuf);\n\n const t = Object.prototype.toString.call(rtcFrame);\n const isVideo = t === '[object RTCEncodedVideoFrame]'\n\n const frameHdr = frameHeaderDecode(data);\n\n if (frameHdr == null) {\n doLog('decryptFrame: failed to decode frame header');\n return;\n }\n\n const meta = rtcFrame.getMetadata();\n let ssrc = meta.synchronizationSource.toString();\n let csrc = null;\n if (frameHdr.csrc != \"0\") {\n csrc = frameHdr.csrc;\n }\n else if (meta.contributingSources && meta.contributingSources.length > 0) {\n csrc = meta.contributingSources[0].toString();\n }\n else {\n csrc = ssrc;\n }\n\n if (!csrc) {\n doLog('decryptFrame: no csrc for ssrc ' + ssrc);\n return;\n }\n\n let uinfo = null;\n if (isVideo) {\n uinfo = coder.video.users[csrc];\n }\n else {\n uinfo = coder.audio.users[csrc];\n }\n\n if (!uinfo) {\n doLog('decryptFrame: no userinfo for csrc: ' + csrc);\n return;\n }\n \n //console.log('frame_dec: video ' + isVideo + ' ssrc ' + ssrc + ' csrc ' + csrc + ' user ' + uinfo.userid.substring(0,8));\n\n if (isVideo && uinfo.transp_ssrcv != ssrc) {\n doLog(\"frame_dec: decrypt: first frame received type: video uid: \" +\n uinfo.userid.substring(0,8) + \" fid: \" + frameHdr.frameId + \" csrc: \" + csrc);\n uinfo.first_succ_video = false;\n\n /* Only call videoStreamHandler for selective video */\n if (uinfo.ssrcv != ssrc) {\n for (const [key, u] of Object.entries(coder.video.users)) {\n if (u.transp_ssrcv == ssrc) {\n postMessage({\n op: \"setvstream\",\n self: coder.self,\n userid: u.userid,\n clientid: u.clientid,\n ssrc: \"0\",\n });\n u.transp_ssrcv = null;\n }\n }\n\n postMessage({\n op: \"setvstream\",\n self: coder.self,\n userid: uinfo.userid,\n clientid: uinfo.clientid,\n ssrc: ssrc,\n });\n }\n uinfo.transp_ssrcv = ssrc;\n }\n\n if (!isVideo && !uinfo.first_recv_audio) {\n doLog(\"frame_dec: decrypt: first frame received type: audio uid: \" +\n uinfo.userid.substring(0,8) + \" fid: \" + frameHdr.frameId + \" csrc: \" + csrc);\n uinfo.first_recv_audio = true;\n uinfo.first_succ_audio = false;\n }\n /*\n console.log(\"coder[\" + coder.self + \"]\"\n\t+ \" len=\" + frameHdr.len\n\t+ \" kid=\"+ frameHdr.keyId\n\t+ \" fid=\" + frameHdr.frameId);\n */\n\n let kinfo = null;\n for (i = 0; i < coder.keys.length; i++) {\n if (coder.keys[i].id == frameHdr.keyId) {\n kinfo = coder.keys[i];\n break;\n }\n }\n\n if (!kinfo) {\n getMediaKey(coder.self, frameHdr.keyId);\n return;\n }\n\n let iv = null;\n if (isVideo) {\n iv = xor_iv(uinfo.ivv, frameHdr.frameId, frameHdr.keyId);\n }\n else {\n iv = xor_iv(uinfo.iva, frameHdr.frameId, frameHdr.keyId);\n }\n\n const hdr = data.subarray(0, frameHdr.len);\n const dec = data.subarray(frameHdr.len);\n crypto.subtle.decrypt(\n {\n name: \"AES-GCM\",\n iv: iv,\n tagLength: 128,\n additionalData: hdr\n },\n kinfo.key,\n dec\n )\n .then(function(decdata){\n rtcFrame.data = decdata;\n controller.enqueue(rtcFrame);\n if (isVideo && !uinfo.first_succ_video) {\n doLog(\"frame_dec: decrypt: first frame decrypted type: video uid: \" +\n uinfo.userid.substring(0,8) + \" fid: \" + frameHdr.frameId + \" csrc: \" + csrc);\n uinfo.first_succ_video = true;\n }\n if (!isVideo && !uinfo.first_succ_audio) {\n doLog(\"frame_dec: decrypt: first frame decrypted type: audio uid: \" +\n uinfo.userid.substring(0,8) + \" fid: \" + frameHdr.frameId + \" csrc: \" + csrc);\n uinfo.first_succ_audio = true;\n }\n })\n .catch(err => {\n console.log(err);\n });\n}\n\nonmessage = async (event) => {\n const {op, self} = event.data;\n doLog('AVS worker op=' + op + ' self=' + self);\n\n let coder = coders[self];\n if (!coder) {\n\tdoLog('AVS worker: no coder for self=' + self);\n } \n /*\n const {opinfo, readableStream, writableStream} = event.data;\n const mval = opinfo.mtype === 'video' ? 1 : 0; // corresponds to enum frame_media_type\n */\n if (op === 'create') {\n\tconst { iva, ivv } = event.data;\n\tif (!coder) {\n\t coder = {\n\t\tself: self,\n\t\tframeId: Math.floor(Math.random() * 4294967295),\n\t\tcurrentKey: null,\n\t\tkeys: [],\n\t\taudio: {\n\t\t first_recv: false,\n\t\t first_succ: false,\n\t\t iv: iva,\n\t\t users: {},\n\t\t},\n\t\tvideo: {\n\t\t first_recv: false,\n\t\t first_succ: false,\n\t\t iv: ivv,\n\t\t users: {},\n\t\t},\n\t }\n\t coders[self] = coder;\n\n\t doLog('AVS worker: adding coder for self=' + self);\n\t}\n }\n else if (op === 'destroy') {\n\tcoders[self] = null;\n }\n else if (op === 'addUser') {\n\tif (!coder) {\n\t doLog('addUser: no coder');\n\t return;\n\t}\n\tconst {userInfo} = event.data;\n\n\tdoLog('addUser: adding audio ssrc: '+userInfo.ssrca);\n\tcoder.audio.users[userInfo.ssrca] = userInfo;\n\tif (userInfo.ssrcv != 0) {\n\t coder.video.users[userInfo.ssrcv] = userInfo;\n\t}\n }\n else if (op === 'setupSender') {\n\tconst {readableStream, writableStream} = event.data;\n\n\tconst transformStream = new TransformStream({\n\t transform: (frame, controller) => {encryptFrame(coder, frame, controller);},\n\t});\n\treadableStream\n .pipeThrough(transformStream)\n .pipeTo(writableStream);\n }\n else if (op === 'setupReceiver') {\n const {readableStream, writableStream} = event.data;\n\n if (!coder) {\n\t doLog('setupSender: no coder for self=' + self);\n return;\n }\n\n const transformStream = new TransformStream({\n\t transform: (frame, controller) => {decryptFrame(coder, frame, controller);},\n });\n readableStream\n .pipeThrough(transformStream)\n .pipeTo(writableStream); \n }\n else if (op === 'setMediaKey') {\n const {index, current, key} = event.data;\n\n doLog(\"setMediaKey: got key for: \" + self\n\t\t + \" index=\" + index + \" current=\" + current\n\t\t + \" keyLen=\" + key.length);\n\n crypto.subtle.importKey(\n \"raw\",\n key,\n \"AES-GCM\",\n false,\n [\"encrypt\", \"decrypt\"]\n )\n .then(function(k){\n let kinfo = null;\n for (i = 0; i < coder.keys.length; i++) {\n if (coder.keys[i].id == index) {\n coder.keys[i].key = k;\n kinfo = coder.keys[i];\n break;\n }\n }\n if (!kinfo) {\n kinfo = {id:index, key:k};\n coder.keys.push(kinfo);\n }\n if (index == current)\n coder.currentKey = kinfo;\n\n while (coder.keys.length > 4) {\n coder.keys.shift();\n }\n });\n } \n};\n";
16
- function callStreamHandler(pc, userid, clientid, ssrc) {
17
+ var workerContent = "\n\nvar coders = {};\n\nconst HDR_VERSION = 0;\n\n/*\nHeader\n\n 1 1 1 1 1 1\n 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n| V=0 |E| RES |S|LEN |1|KLEN |\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n| KID... (length=KLEN) |\n+-------------------------------+\n| CTR... (length=LEN) |\n+-------------------------------+\n\nExtensions\n\n 0 1 2 3 4 5 6 7\n+-+-+-+-+-+-+-+-+---------------------------+\n|M| ELN | EID | VAL... (length=ELEN) |\n+-+-+-+-+-+-+-+-+---------------------------+\n*/\n\nfunction calcLen(val) {\n let l = 0;\n\n if (val == 0)\n\treturn 1;\n\n while (val != 0) {\n\tl++;\n\tval = val >>> 8;\t\n }\n\n return l;\n}\n\nfunction writeBytes(buf, p, len, val)\n{\n switch (len) {\n case 8:\n\tbuf[p] = (val >>> 56) & 0xff;\n\tp++;\n\n case 7:\n\tbuf[p] = (val >>> 48) & 0xff;\n\tp++;\n\n case 6:\n\tbuf[p] = (val >>> 40) & 0xff;\n\tp++;\n\t\n case 5:\n\tbuf[p] = (val >>> 32) & 0xff;\n\tp++;\n\n case 4:\n\tbuf[p] = (val >>> 24) & 0xff;\n\tp++;\n\n case 3:\n\tbuf[p] = (val >>> 16) & 0xff;\n\tp++;\n\n case 2:\n\tbuf[p] = (val >>> 8) & 0xff;\n\tp++;\n\n case 1:\n\tbuf[p] = val & 0xff;\n\tbreak;\n\n default:\n\treturn 0;\n }\n \n return len;\n}\n\nfunction readBytes(buf, p, len) {\n let l = len;\n let v = 0;\n\n while (l > 0) {\n\tv = v * 256;\n\tv = v + buf[p];\n\tp++;\n\tl--;\n }\n\n const lv = {\n\tlen: len,\n\tval: v\n };\n \n return lv;\n}\n\nfunction frameHeaderDecode(buf) {\n\n const MAX_EXTS = 16;\n let ext = buf[0] & 0x10;\n const flen = ((buf[1] >>> 4) & 0x7) + 1;\n const x = (buf[1] >>> 3) & 0x1;\n const klen = buf[1] & 0x7;\n let p = 2;\n let kid = -1;\n let fid = -1;\n let csrc = \"0\";\n let i = 0;\n\n if (x) {\n\tconst lv = readBytes(buf, p, klen + 1);\n\n\tp += lv.len;\n\tkid = lv.val;\n }\n else {\n\tkid = klen;\n }\n\n const lv = readBytes(buf, p, flen);\n p += lv.len;\n fid = lv.val;\n\n while(ext && p < buf.length && i < MAX_EXTS) {\n\tconst b = buf[p];\n\text = b & 0x80;\n\tconst elen = ((b >>> 4) & 0x7) + 1;\n\tconst eid = (b & 0xf);\n\n if (eid == 1 && elen == 4) {\n const csrco = readBytes(buf, p + 1, elen);\n csrc = csrco.val.toString();\n }\n p += elen + 1;\n i++;\n }\n\n if (kid < 0 || fid < 0) {\n\treturn null;\n }\n else {\n\tconst frameHdr = {\n\t len: p,\n\t frameId: fid,\n\t keyId: kid,\n\t csrc: csrc\n\t}\n\treturn frameHdr;\n }\n}\n\nfunction frameHeaderEncode(fid, kid, csrc)\n{\n const ab = new ArrayBuffer(32);\n const buf = new Uint8Array(ab);\n let sig = 0;\n let x = 0;\n let klen = 0;\n let flen = 0;\n let p = 2;\n let ext = (csrc > 0) ? 1 : 0;\n\n if (kid > 7) {\n\tx = 1;\n\tklen = calcLen(kid) - 1;\n }\n else {\n\tklen = kid;\n }\n\n flen = calcLen(fid);\n\n buf[0] = (HDR_VERSION << 5) + (ext << 4);\n buf[1] = (sig << 7) + ((flen - 1) << 4) + (x << 3) + klen;\n if (x) {\n\tp += writeBytes(buf, p, klen + 1, kid);\n }\n p += writeBytes(buf, p, flen, fid);\n\n if (csrc > 0) {\n buf[p] = 0x31;\n p++;\n p+= writeBytes(buf, p, 4, csrc);\n }\n return buf.subarray(0, p);\n}\n\nfunction xor_iv(iv, fid, kid) {\n const oiv = new Uint8Array(iv);\n\n oiv[0] = oiv[0] ^ ((fid >> 24) & 0xFF);\n oiv[1] = oiv[1] ^ ((fid >> 16) & 0xFF);\n oiv[2] = oiv[2] ^ ((fid >> 8) & 0xFF);\n oiv[3] = oiv[3] ^ ((fid ) & 0xFF);\n oiv[4] = oiv[4] ^ ((kid >> 24) & 0xFF);\n oiv[5] = oiv[5] ^ ((kid >> 16) & 0xFF);\n oiv[6] = oiv[6] ^ ((kid >> 8) & 0xFF);\n oiv[7] = oiv[7] ^ ((kid ) & 0xFF);\n\n return oiv;\n}\n\nfunction getMediaKey(self, index) {\n postMessage({\n\top: \"getMediaKey\",\n\tself: self,\n\tindex: index\n });\n}\n\nfunction getCurrentMediaKey(self, index) {\n postMessage({\n\top: \"getCurrentMediaKey\",\n\tself: self\n });\n}\n\nfunction doLog(str) {\n postMessage({\n\top: \"log\",\n\tlevel: 1,\n\tlogString: str\n });\n}\n\nfunction encryptFrame(coder, rtcFrame, controller) {\n const dataBuf = rtcFrame.data;\n const data = new Uint8Array(dataBuf);\n const t = Object.prototype.toString.call(rtcFrame);\n const isVideo = t === '[object RTCEncodedVideoFrame]';\n const userid = \"self\";\n\n const meta = rtcFrame.getMetadata();\n const ssrc = meta.synchronizationSource;\n\n if (isVideo && !coder.video.first_recv) {\n doLog(\"frame_enc: encrypt: first frame received type: video uid: \" +\n userid.substring(0,8) + \" fid: \" + coder.frameId + \" ssrc: \" + ssrc);\n coder.video.first_recv = true;\n coder.video.first_succ = false;\n }\n if (!isVideo && !coder.audio.first_recv) {\n doLog(\"frame_enc: encrypt: first frame received type: audio uid: \" +\n userid.substring(0,8) + \" fid: \" + coder.frameId + \" ssrc: \" + ssrc);\n coder.audio.first_recv = true;\n coder.audio.first_succ = false;\n }\n\n if (coder.currentKey == null) {\n getCurrentMediaKey(coder.self);\n return;\n }\n\n const baseiv = isVideo ? coder.video.iv : coder.audio.iv;\n const iv = xor_iv(baseiv, coder.frameId, coder.currentKey.id);\n \n const hdr = frameHeaderEncode(coder.frameId, coder.currentKey.id, ssrc);\n crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: iv,\n tagLength: 128,\n additionalData: hdr\n },\n coder.currentKey.key,\n data\n )\n .then(function(encdata){\n const enc8 = new Uint8Array(encdata); \t \n const enc = new Uint8Array(hdr.length + enc8.length);\n \n enc.set(hdr);\n enc.set(enc8, hdr.length);\n\n rtcFrame.data = enc.buffer;\n controller.enqueue(rtcFrame);\n\n if (isVideo && !coder.video.first_succ) {\n doLog(\"frame_enc: encrypt: first frame encrypted type: video uid: \" +\n userid.substring(0,8) + \" fid: \" + coder.frameId + \" csrc: \" + ssrc);\n coder.video.first_succ = true;\n }\n if (!isVideo && !coder.audio.first_succ) {\n doLog(\"frame_enc: encrypt: first frame encrypted type: audio uid: \" +\n userid.substring(0,8) + \" fid: \" + coder.frameId + \" csrc: \" + ssrc);\n coder.audio.first_succ = true;\n }\n\n coder.frameId++;\n });\n}\n\nfunction decryptFrame(track_id, coder, rtcFrame, controller) {\n const dataBuf = rtcFrame.data;\n const data = new Uint8Array(dataBuf);\n\n const t = Object.prototype.toString.call(rtcFrame);\n const isVideo = t === '[object RTCEncodedVideoFrame]'\n\n const frameHdr = frameHeaderDecode(data);\n\n if (frameHdr == null) {\n doLog('decryptFrame: failed to decode frame header');\n return;\n }\n\n const meta = rtcFrame.getMetadata();\n let ssrc = meta.synchronizationSource.toString();\n let csrc = null;\n if (frameHdr.csrc != \"0\") {\n csrc = frameHdr.csrc;\n }\n else if (meta.contributingSources && meta.contributingSources.length > 0) {\n csrc = meta.contributingSources[0].toString();\n }\n else {\n csrc = ssrc;\n }\n\n if (!csrc) {\n doLog('decryptFrame: no csrc for ssrc ' + ssrc);\n return;\n }\n\n let uinfo = null;\n if (isVideo) {\n uinfo = coder.video.users[csrc];\n }\n else {\n uinfo = coder.audio.users[csrc];\n }\n\n if (!uinfo) {\n doLog('decryptFrame: no userinfo for csrc: ' + csrc);\n return;\n }\n \n //console.log('frame_dec: video ' + isVideo + ' ssrc ' + ssrc + ' csrc ' + csrc + ' user ' + uinfo.userid.substring(0,8));\n\n if (isVideo && uinfo.transp_ssrcv != ssrc) {\n doLog(\"frame_dec: decrypt: first frame received type: video uid: \" +\n uinfo.userid.substring(0,8) + \" fid: \" + frameHdr.frameId + \" csrc: \" + csrc + \" ssrc: \" + ssrc + \" uinfo.ssrcv: \" + uinfo.ssrcv);\n uinfo.first_succ_video = false;\n\n /* Only call videoStreamHandler for selective video */\n if (uinfo.ssrcv != ssrc) {\n for (const [key, u] of Object.entries(coder.video.users)) {\n if (u.transp_ssrcv == ssrc) {\n postMessage({\n op: \"setvstream\",\n self: coder.self,\n userid: u.userid,\n clientid: u.clientid,\n ssrc: \"0\",\n\t\t track_id: \"0\",\n });\n u.transp_ssrcv = null;\n }\n }\n\n postMessage({\n op: \"setvstream\",\n self: coder.self,\n userid: uinfo.userid,\n clientid: uinfo.clientid,\n ssrc: ssrc,\n\t track_id: track_id,\n });\n }\n uinfo.transp_ssrcv = ssrc;\n }\n\n if (!isVideo && !uinfo.first_recv_audio) {\n doLog(\"frame_dec: decrypt: first frame received type: audio uid: \" +\n uinfo.userid.substring(0,8) + \" fid: \" + frameHdr.frameId + \" csrc: \" + csrc);\n uinfo.first_recv_audio = true;\n uinfo.first_succ_audio = false;\n }\n /*\n console.log(\"coder[\" + coder.self + \"]\"\n\t+ \" len=\" + frameHdr.len\n\t+ \" kid=\"+ frameHdr.keyId\n\t+ \" fid=\" + frameHdr.frameId);\n */\n\n let kinfo = null;\n for (i = 0; i < coder.keys.length; i++) {\n if (coder.keys[i].id == frameHdr.keyId) {\n kinfo = coder.keys[i];\n break;\n }\n }\n\n if (!kinfo) {\n getMediaKey(coder.self, frameHdr.keyId);\n return;\n }\n\n let iv = null;\n if (isVideo) {\n iv = xor_iv(uinfo.ivv, frameHdr.frameId, frameHdr.keyId);\n }\n else {\n iv = xor_iv(uinfo.iva, frameHdr.frameId, frameHdr.keyId);\n }\n\n const hdr = data.subarray(0, frameHdr.len);\n const dec = data.subarray(frameHdr.len);\n crypto.subtle.decrypt(\n {\n name: \"AES-GCM\",\n iv: iv,\n tagLength: 128,\n additionalData: hdr\n },\n kinfo.key,\n dec\n )\n .then(function(decdata){\n rtcFrame.data = decdata;\n controller.enqueue(rtcFrame);\n if (isVideo && !uinfo.first_succ_video) {\n doLog(\"frame_dec: decrypt: first frame decrypted type: video uid: \" +\n uinfo.userid.substring(0,8) + \" fid: \" + frameHdr.frameId + \" csrc: \" + csrc);\n uinfo.first_succ_video = true;\n }\n if (!isVideo && !uinfo.first_succ_audio) {\n doLog(\"frame_dec: decrypt: first frame decrypted type: audio uid: \" +\n uinfo.userid.substring(0,8) + \" fid: \" + frameHdr.frameId + \" csrc: \" + csrc);\n uinfo.first_succ_audio = true;\n }\n })\n .catch(err => {\n console.log(err);\n });\n}\n\nfunction setupSender(readableStream, writableStream, coder) {\n\tconst transformStream = new TransformStream({\n\t transform: (frame, controller) => {encryptFrame(coder, frame, controller);},\n\t});\n\treadableStream\n .pipeThrough(transformStream)\n .pipeTo(writableStream);\n}\n\nfunction setupReceiver(readableStream, writableStream, coder, track_id) {\n const transformStream = new TransformStream({\n\t transform: (frame, controller) => {decryptFrame(track_id, coder, frame, controller);},\n });\n readableStream\n .pipeThrough(transformStream)\n .pipeTo(writableStream); \n}\n\n\nonrtctransform = (event) => {\n let transform;\n const writableStream = event.transformer.writable;\n const readableStream = event.transformer.readable;\n const coder = coders[event.transformer.options.self];\n\n if (!writableStream) {\n pc_log(LOG_LEVEL_WARN, \"onrtctransform: no writable stream\");\n return;\n }\n if (event.transformer.options.name === \"senderTransform\")\n setupSender(readableStream, writableStream, coder);\n else if (event.transformer.options.name === 'receiverTransform') {\n const track_id = event.transformer.options.track_id;\n doLog(\"onrtctransform: receiver trackid=\" + track_id);\n setupReceiver(readableStream, writableStream, coder, track_id);\n }\n};\n\n\nonmessage = async (event) => {\n const {op, self} = event.data;\n doLog('AVS worker op=' + op + ' self=' + self);\n\n let coder = coders[self];\n if (!coder) {\n\tdoLog('AVS worker: no coder for self=' + self);\n } \n /*\n const {opinfo, readableStream, writableStream} = event.data;\n const mval = opinfo.mtype === 'video' ? 1 : 0; // corresponds to enum frame_media_type\n */\n if (op === 'create') {\n\tconst { iva, ivv } = event.data;\n\tif (!coder) {\n\t coder = {\n\t\tself: self,\n\t\tframeId: Math.floor(Math.random() * 4294967295),\n\t\tcurrentKey: null,\n\t\tkeys: [],\n\t\taudio: {\n\t\t first_recv: false,\n\t\t first_succ: false,\n\t\t iv: iva,\n\t\t users: {},\n\t\t},\n\t\tvideo: {\n\t\t first_recv: false,\n\t\t first_succ: false,\n\t\t iv: ivv,\n\t\t users: {},\n\t\t},\n\t }\n\t coders[self] = coder;\n\n\t doLog('AVS worker: adding coder for self=' + self);\n\t}\n }\n else if (op === 'destroy') {\n\tcoders[self] = null;\n }\n else if (op === 'addUser') {\n\tif (!coder) {\n\t doLog('addUser: no coder');\n\t return;\n\t}\n\tconst {userInfo} = event.data;\n\n\tdoLog('addUser: adding audio ssrc: '+userInfo.ssrca);\n\tcoder.audio.users[userInfo.ssrca] = userInfo;\n\tif (userInfo.ssrcv != 0) {\n\t coder.video.users[userInfo.ssrcv] = userInfo;\n\t}\n }\n else if (op === 'setupSender') {\n\tconst {readableStream, writableStream} = event.data;\n\n\tsetupSender(readableStream, writableStream, coder);\n }\n else if (op === 'setupReceiver') {\n const {readableStream, writableStream} = event.data;\n\n if (!coder) {\n\t doLog('setupSender: no coder for self=' + self);\n return;\n }\n\n setupReceiver(readableStream, writableStream, coder, null);\n }\n else if (op === 'setMediaKey') {\n const {index, current, key} = event.data;\n\n doLog(\"setMediaKey: got key for: \" + self\n\t\t + \" index=\" + index + \" current=\" + current\n\t\t + \" keyLen=\" + key.length);\n\n crypto.subtle.importKey(\n \"raw\",\n key,\n \"AES-GCM\",\n false,\n [\"encrypt\", \"decrypt\"]\n )\n .then(function(k){\n let kinfo = null;\n for (i = 0; i < coder.keys.length; i++) {\n if (coder.keys[i].id == index) {\n coder.keys[i].key = k;\n kinfo = coder.keys[i];\n break;\n }\n }\n if (!kinfo) {\n kinfo = {id:index, key:k};\n coder.keys.push(kinfo);\n }\n if (index == current)\n coder.currentKey = kinfo;\n\n while (coder.keys.length > 4) {\n coder.keys.shift();\n }\n });\n } \n};\n";
18
+ function callStreamHandler(pc, userid, clientid, ssrc, track_id) {
19
+ pc_log(LOG_LEVEL_INFO, "vsh: ".concat(videoStreamHandler, " rtc: ").concat(pc.rtc, " ssrc: ").concat(ssrc));
17
20
  if (ssrc == "0") {
18
21
  if (videoStreamHandler) {
19
22
  pc_log(LOG_LEVEL_INFO, "calling vsh(".concat(pc.convid.substring(0, 8), ", ").concat(userid.substring(0, 8), ", ").concat(clientid.substring(0, 4), ") to remove renderer"));
@@ -21,16 +24,35 @@ function callStreamHandler(pc, userid, clientid, ssrc) {
21
24
  }
22
25
  }
23
26
  else if (pc.rtc) {
24
- var label_1 = pc.streams[ssrc];
25
- pc.rtc.getTransceivers().forEach(function (trans) {
26
- if (trans.receiver.track.label === label_1) {
27
- var stream = new MediaStream([trans.receiver.track]);
28
- if (videoStreamHandler) {
29
- pc_log(LOG_LEVEL_INFO, "calling vsh(".concat(pc.convid.substring(0, 8), ", ").concat(userid.substring(0, 8), ", ").concat(clientid.substring(0, 4), ") with 1 stream"));
30
- videoStreamHandler(pc.convid, userid, clientid, [stream]);
27
+ if (pc_env === ENV_FIREFOX) {
28
+ pc.rtc.getTransceivers().forEach(function (trans) {
29
+ var track = trans.receiver.track;
30
+ if (track.kind != 'video')
31
+ return;
32
+ pc_log(LOG_LEVEL_INFO, "vsh: id:".concat(track.id, " looking for: ").concat(track_id));
33
+ if (track.id === track_id) {
34
+ var stream = new MediaStream([trans.receiver.track]);
35
+ if (videoStreamHandler) {
36
+ pc_log(LOG_LEVEL_INFO, "calling vsh(".concat(pc.convid.substring(0, 8), ", ").concat(userid.substring(0, 8), ", ").concat(clientid.substring(0, 4), ") with 1 stream"));
37
+ videoStreamHandler(pc.convid, userid, clientid, [stream]);
38
+ }
31
39
  }
32
- }
33
- });
40
+ });
41
+ }
42
+ else {
43
+ var label_1 = pc.streams[ssrc];
44
+ pc.rtc.getTransceivers().forEach(function (trans) {
45
+ var track = trans.receiver.track;
46
+ pc_log(LOG_LEVEL_INFO, "vsh: label:".concat(track.label, " id:").concat(track.id, " looking for: ").concat(label_1));
47
+ if (trans.receiver.track.label === label_1) {
48
+ var stream = new MediaStream([trans.receiver.track]);
49
+ if (videoStreamHandler) {
50
+ pc_log(LOG_LEVEL_INFO, "calling vsh(".concat(pc.convid.substring(0, 8), ", ").concat(userid.substring(0, 8), ", ").concat(clientid.substring(0, 4), ") with 1 stream"));
51
+ videoStreamHandler(pc.convid, userid, clientid, [stream]);
52
+ }
53
+ }
54
+ });
55
+ }
34
56
  }
35
57
  }
36
58
  function createWorker() {
@@ -51,10 +73,12 @@ function createWorker() {
51
73
  pc_log(level, logString);
52
74
  }
53
75
  else if (op === 'setvstream') {
54
- var _c = event.data, userid = _c.userid, clientid = _c.clientid, ssrc = _c.ssrc;
76
+ var _c = event.data, userid = _c.userid, clientid = _c.clientid, ssrc = _c.ssrc, track_id = _c.track_id;
77
+ pc_log(LOG_LEVEL_INFO, "setvstream: called");
55
78
  var pcs = connectionsStore.getPeerConnectionBySelf(self);
56
79
  if (pcs.length == 1) {
57
- callStreamHandler(pcs[0], userid, clientid, ssrc);
80
+ pc_log(LOG_LEVEL_INFO, "setvstream: calling videoStreamHandler: pc=".concat(pcs[0], ", ").concat(userid, "/").concat(clientid, " ssrc=").concat(ssrc));
81
+ callStreamHandler(pcs[0], userid, clientid, ssrc, track_id);
58
82
  }
59
83
  }
60
84
  };
@@ -421,6 +445,7 @@ function dataChannelHandler(pc, event) {
421
445
  ccallDcEstabHandler(pc, dcHnd);
422
446
  }
423
447
  function pc_SetEnv(env) {
448
+ pc_log(LOG_LEVEL_INFO, "setEnv=".concat(env));
424
449
  pc_env = env;
425
450
  }
426
451
  function pc_New(self, convidPtr, audioIvPtr, videoIvPtr, ivlen) {
@@ -449,8 +474,6 @@ function pc_New(self, convidPtr, audioIvPtr, videoIvPtr, ivlen) {
449
474
  users: {},
450
475
  iva: iva8,
451
476
  ivv: ivv8,
452
- insertable_legacy: false,
453
- insertable_streams: false,
454
477
  stats: {
455
478
  ploss: 0,
456
479
  lastploss: 0,
@@ -474,10 +497,6 @@ function pc_Create(hnd, privacy, conv_type) {
474
497
  return;
475
498
  }
476
499
  pc.conv_type = conv_type;
477
- var pt = RTCRtpSender.prototype;
478
- pc.insertable_legacy = !!pt.createEncodedVideoStreams;
479
- pc.insertable_streams = !!pt.createEncodedStreams;
480
- pc_log(LOG_LEVEL_INFO, "insertable: ".concat(pc.insertable_legacy, "/").concat(pc.insertable_streams));
481
500
  var transportPolicy = privacy !== 0 ? "relay" : "all";
482
501
  var useEncoding = pc.conv_type === CONV_TYPE_CONFERENCE;
483
502
  var config = {
@@ -508,8 +527,11 @@ function pc_Create(hnd, privacy, conv_type) {
508
527
  for (var _b = 0, _c = stream.getTracks(); _b < _c.length; _b++) {
509
528
  var track = _c[_b];
510
529
  if (track) {
511
- label = track.label;
512
- pc_log(LOG_LEVEL_INFO, "onTrack: convid=".concat(pc.convid.substring(0, 8), " track=").concat(track.id, "/").concat(track.label, " kind=").concat(track.kind, " enabled=").concat(track.enabled, "/").concat(track.muted, "/undefined/").concat(track.readyState, " remote=undefined"));
530
+ if (pc_env === ENV_FIREFOX)
531
+ label = track.id;
532
+ else
533
+ label = track.label;
534
+ pc_log(LOG_LEVEL_INFO, "onTrack: convid=".concat(pc.convid.substring(0, 8), " track=").concat(track.id, "/").concat(track.label, "=>").concat(label, " kind=").concat(track.kind, " enabled=").concat(track.enabled, "/").concat(track.muted, "/undefined/").concat(track.readyState, " remote=undefined"));
513
535
  if (!track.enabled)
514
536
  track.enabled = true;
515
537
  if (pc.conv_type === CONV_TYPE_CONFERENCE) {
@@ -553,7 +575,11 @@ function pc_Close(hnd) {
553
575
  pc.rtc.getTransceivers().forEach(function (trans) {
554
576
  var track = trans.receiver.track;
555
577
  if (track && track.kind === "audio") {
556
- var label = track.label;
578
+ var label = '';
579
+ if (pc_env == ENV_FIREFOX)
580
+ label = track.id;
581
+ else
582
+ label = track.label;
557
583
  if (audioStreamHandler && pc.rtc) {
558
584
  pc_log(LOG_LEVEL_INFO, "pc_Close: calling ash(".concat(pc.convid.substring(0, 8), ", ").concat(label, ") with 0 streams"));
559
585
  audioStreamHandler(pc.convid, hnd.toString() + label, null);
@@ -721,13 +747,17 @@ function setupSenderTransform(pc, sender) {
721
747
  if (!sender || !sender.track) {
722
748
  return;
723
749
  }
724
- if (!pc.insertable_legacy && !pc.insertable_streams) {
750
+ if (!insertableLegacy && !insertableStreams) {
725
751
  pc_log(LOG_LEVEL_WARN, "setupSenderTransform: insertable streams not supported");
726
752
  return;
727
753
  }
754
+ if (pc_env === ENV_FIREFOX) {
755
+ sender.transform = new RTCRtpScriptTransform(worker, { name: "senderTransform", self: pc.self });
756
+ return;
757
+ }
728
758
  var mtype = sender.track.kind === 'video' ? 1 : 0; // corresponds to enum frame_media_type
729
759
  var senderStreams = null;
730
- if (pc.insertable_streams)
760
+ if (insertableStreams)
731
761
  senderStreams = sender.createEncodedStreams();
732
762
  else
733
763
  senderStreams = mtype === 1 ? sender.createEncodedVideoStreams() : sender.createEncodedAudioStreams();
@@ -745,13 +775,17 @@ function setupReceiverTransform(pc, receiver) {
745
775
  if (!receiver || !receiver.track) {
746
776
  return;
747
777
  }
748
- if (!pc.insertable_legacy && !pc.insertable_streams) {
778
+ if (!insertableLegacy && !insertableStreams) {
749
779
  pc_log(LOG_LEVEL_WARN, "setupReceiverTransform: insertable streams not supported");
750
780
  return;
751
781
  }
782
+ if (pc_env == ENV_FIREFOX) {
783
+ receiver.transform = new RTCRtpScriptTransform(worker, { name: "receiverTransform", self: pc.self, track_id: receiver.track.id });
784
+ return;
785
+ }
752
786
  var mtype = receiver.track.kind === 'video' ? 1 : 0; // corresponds to enum frame_media_type
753
787
  var receiverStreams = null;
754
- if (pc.insertable_streams)
788
+ if (insertableStreams)
755
789
  receiverStreams = receiver.createEncodedStreams();
756
790
  else
757
791
  receiverStreams = mtype === 1 ? receiver.createEncodedVideoStreams() : receiver.createEncodedAudioStreams();
@@ -938,7 +972,8 @@ function pc_SetRemoteDescription(hnd, typePtr, sdpPtr) {
938
972
  .catch(function (err) {
939
973
  pc_log(LOG_LEVEL_WARN, "setRemoteDescription failed: " + err, err);
940
974
  });
941
- extractSSRCs(pc, sdp);
975
+ if (pc_env != ENV_FIREFOX)
976
+ extractSSRCs(pc, sdp);
942
977
  }
943
978
  function pc_SetLocalDescription(hnd, typePtr, sdpPtr) {
944
979
  pc_log(LOG_LEVEL_INFO, "pc_SetLocalDescription: hnd=".concat(hnd));
@@ -1164,6 +1199,10 @@ function pc_InitModule(module, logh) {
1164
1199
  createWorker();
1165
1200
  em_module = module;
1166
1201
  logFn = logh;
1202
+ var pt = RTCRtpSender.prototype;
1203
+ insertableStreams = !!pt.createEncodedStreams || "transform" in pt;
1204
+ insertableLegacy = !!pt.createEncodedVideoStreams;
1205
+ pc_log(LOG_LEVEL_INFO, "insertable: ".concat(insertableLegacy, "/").concat(insertableStreams));
1167
1206
  var callbacks = [
1168
1207
  [pc_SetEnv, "vi"],
1169
1208
  [pc_New, "iiiiii"],
@@ -1203,6 +1242,9 @@ function pc_InitModule(module, logh) {
1203
1242
  function pc_SetUserMediaHandler(umh) {
1204
1243
  userMediaHandler = umh;
1205
1244
  }
1245
+ function pc_IsConferenceCallingSupported() {
1246
+ return insertableStreams || insertableLegacy;
1247
+ }
1206
1248
  function pc_SetAudioStreamHandler(ash) {
1207
1249
  audioStreamHandler = ash;
1208
1250
  }
@@ -1334,6 +1376,7 @@ exports.default = {
1334
1376
  setUserMediaHandler: pc_SetUserMediaHandler,
1335
1377
  setAudioStreamHandler: pc_SetAudioStreamHandler,
1336
1378
  setVideoStreamHandler: pc_SetVideoStreamHandler,
1379
+ isConferenceCallingSupported: pc_IsConferenceCallingSupported,
1337
1380
  replaceTrack: pc_ReplaceTrack,
1338
1381
  getStats: pc_GetStats
1339
1382
  };
@@ -165,6 +165,7 @@ export declare class Wcall {
165
165
  setUserMediaHandler(userMediaHandler: UserMediaHandler): void;
166
166
  setAudioStreamHandler(audioStreamHandler: AudioStreamHandler): void;
167
167
  setVideoStreamHandler(videoStreamHandler: VideoStreamHandler): void;
168
+ isConferenceCallingSupported(): boolean;
168
169
  replaceTrack(convid: string, newTrack: MediaStreamTrack): void;
169
170
  getStats(convid: string): Promise<Array<{
170
171
  userid: string;
package/dist/avs_wcall.js CHANGED
@@ -594,6 +594,9 @@ var Wcall = /** @class */ (function () {
594
594
  Wcall.prototype.setVideoStreamHandler = function (videoStreamHandler) {
595
595
  avs_pc_1.default.setVideoStreamHandler(videoStreamHandler);
596
596
  };
597
+ Wcall.prototype.isConferenceCallingSupported = function () {
598
+ return avs_pc_1.default.isConferenceCallingSupported();
599
+ };
597
600
  Wcall.prototype.replaceTrack = function (convid, newTrack) {
598
601
  avs_pc_1.default.replaceTrack(convid, newTrack);
599
602
  };
package/package.json CHANGED
@@ -21,5 +21,5 @@
21
21
  "prepack": "prettier -w src/avs_wcall.ts && tsc && cp src/*.js src/*.d.ts dist/",
22
22
  "test": "karma start"
23
23
  },
24
- "version": "9.4.19"
24
+ "version": "9.5.2"
25
25
  }