1 2// 3// Copyright (c) 2013-2021 Winlin 4// 5// SPDX-License-Identifier: MIT 6// 7 8'use strict'; 9 10function SrsError(name, message) { 11 this.name = name; 12 this.message = message; 13 this.stack = (new Error()).stack; 14} 15SrsError.prototype = Object.create(Error.prototype); 16SrsError.prototype.constructor = SrsError; 17 18// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter 19// Async-awat-prmise based SRS RTC Publisher. 20function SrsRtcPublisherAsync() { 21 var self = {}; 22 23 // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia 24 self.constraints = { 25 audio: true, 26 video: { 27 width: {ideal: 320, max: 576} 28 } 29 }; 30 31 // @see https://github.com/rtcdn/rtcdn-draft 32 // @url The WebRTC url to play with, for example: 33 // webrtc://r.ossrs.net/live/livestream 34 // or specifies the API port: 35 // webrtc://r.ossrs.net:11985/live/livestream 36 // or autostart the publish: 37 // webrtc://r.ossrs.net/live/livestream?autostart=true 38 // or change the app from live to myapp: 39 // webrtc://r.ossrs.net:11985/myapp/livestream 40 // or change the stream from livestream to mystream: 41 // webrtc://r.ossrs.net:11985/live/mystream 42 // or set the api server to myapi.domain.com: 43 // webrtc://myapi.domain.com/live/livestream 44 // or set the candidate(eip) of answer: 45 // webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185 46 // or force to access https API: 47 // webrtc://r.ossrs.net/live/livestream?schema=https 48 // or use plaintext, without SRTP: 49 // webrtc://r.ossrs.net/live/livestream?encrypt=false 50 // or any other information, will pass-by in the query: 51 // webrtc://r.ossrs.net/live/livestream?vhost=xxx 52 // webrtc://r.ossrs.net/live/livestream?token=xxx 53 self.publish = async function (url) { 54 var conf = self.__internal.prepareUrl(url); 55 self.pc.addTransceiver("audio", {direction: "sendonly"}); 56 self.pc.addTransceiver("video", {direction: "sendonly"}); 57 58 if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { 59 throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); 60 } 61 var stream = await navigator.mediaDevices.getUserMedia(self.constraints); 62 63 // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack 64 stream.getTracks().forEach(function (track) { 65 self.pc.addTrack(track); 66 67 // Notify about local track when stream is ok. 68 self.ontrack && self.ontrack({track: track}); 69 }); 70 71 var offer = await self.pc.createOffer(); 72 await self.pc.setLocalDescription(offer); 73 var session = await new Promise(function (resolve, reject) { 74 // @see https://github.com/rtcdn/rtcdn-draft 75 var data = { 76 api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, 77 clientip: null, sdp: offer.sdp 78 }; 79 console.log("Generated offer: ", data); 80 81 const xhr = new XMLHttpRequest(); 82 xhr.onload = function() { 83 if (xhr.readyState !== xhr.DONE) return; 84 if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); 85 const data = JSON.parse(xhr.responseText); 86 console.log("Got answer: ", data); 87 return data.code ? reject(xhr) : resolve(data); 88 } 89 xhr.open('POST', conf.apiUrl, true); 90 xhr.setRequestHeader('Content-type', 'application/json'); 91 xhr.send(JSON.stringify(data)); 92 }); 93 await self.pc.setRemoteDescription( 94 new RTCSessionDescription({type: 'answer', sdp: session.sdp}) 95 ); 96 session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; 97 98 return session; 99 }; 100 101 // Close the publisher. 102 self.close = function () { 103 self.pc && self.pc.close(); 104 self.pc = null; 105 }; 106 107 // The callback when got local stream. 108 // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack 109 self.ontrack = function (event) { 110 // Add track to stream of SDK. 111 self.stream.addTrack(event.track); 112 }; 113 114 // Internal APIs. 115 self.__internal = { 116 defaultPath: '/rtc/v1/publish/', 117 prepareUrl: function (webrtcUrl) { 118 var urlObject = self.__internal.parse(webrtcUrl); 119 120 // If user specifies the schema, use it as API schema. 121 var schema = urlObject.user_query.schema; 122 schema = schema ? schema + ':' : window.location.protocol; 123 124 var port = urlObject.port || 1985; 125 if (schema === 'https:') { 126 port = urlObject.port || 443; 127 } 128 129 // @see https://github.com/rtcdn/rtcdn-draft 130 var api = urlObject.user_query.play || self.__internal.defaultPath; 131 if (api.lastIndexOf('/') !== api.length - 1) { 132 api += '/'; 133 } 134 135 var apiUrl = schema + '//' + urlObject.server + ':' + port + api; 136 for (var key in urlObject.user_query) { 137 if (key !== 'api' && key !== 'play') { 138 apiUrl += '&' + key + '=' + urlObject.user_query[key]; 139 } 140 } 141 // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v 142 apiUrl = apiUrl.replace(api + '&', api + '?'); 143 144 var streamUrl = urlObject.url; 145 146 return { 147 apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, 148 tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) 149 }; 150 }, 151 parse: function (url) { 152 // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri 153 var a = document.createElement("a"); 154 a.href = url.replace("rtmp://", "http://") 155 .replace("webrtc://", "http://") 156 .replace("rtc://", "http://"); 157 158 var vhost = a.hostname; 159 var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); 160 var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); 161 162 // parse the vhost in the params of app, that srs supports. 163 app = app.replace("...vhost...", "?vhost="); 164 if (app.indexOf("?") >= 0) { 165 var params = app.slice(app.indexOf("?")); 166 app = app.slice(0, app.indexOf("?")); 167 168 if (params.indexOf("vhost=") > 0) { 169 vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); 170 if (vhost.indexOf("&") > 0) { 171 vhost = vhost.slice(0, vhost.indexOf("&")); 172 } 173 } 174 } 175
176 // when vhost equals to server, and server is ip, 177 // the vhost is __defaultVhost__ 178 if (a.hostname === vhost) { 179 var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; 180 if (re.test(a.hostname)) { 181 vhost = "__defaultVhost__"; 182 } 183 } 184 185 // parse the schema 186 var schema = "rtmp"; 187 if (url.indexOf("://") > 0) { 188 schema = url.slice(0, url.indexOf("://")); 189 } 190 191 var port = a.port; 192 if (!port) { 193 // Finger out by webrtc url, if contains http or https port, to overwrite default 1985. 194 if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) { 195 port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443; 196 } 197 198 // Guess by schema. 199 if (schema === 'http') { 200 port = 80; 201 } else if (schema === 'https') { 202 port = 443; 203 } else if (schema === 'rtmp') { 204 port = 1935; 205 } 206 } 207 208 var ret = { 209 url: url, 210 schema: schema, 211 server: a.hostname, port: port, 212 vhost: vhost, app: app, stream: stream 213 }; 214 self.__internal.fill_query(a.search, ret); 215 216 // For webrtc API, we use 443 if page is https, or schema specified it. 217 if (!ret.port) { 218 if (schema === 'webrtc' || schema === 'rtc') { 219 if (ret.user_query.schema === 'https') { 220 ret.port = 443; 221 } else if (window.location.href.indexOf('https://') === 0) { 222 ret.port = 443; 223 } else { 224 // For WebRTC, SRS use 1985 as default API port. 225 ret.port = 1985; 226 } 227 } 228 } 229 230 return ret; 231 }, 232 fill_query: function (query_string, obj) { 233 // pure user query object. 234 obj.user_query = {}; 235 236 if (query_string.length === 0) { 237 return; 238 } 239 240 // split again for angularjs. 241 if (query_string.indexOf("?") >= 0) { 242 query_string = query_string.split("?")[1]; 243 } 244 245 var queries = query_string.split("&"); 246 for (var i = 0; i < queries.length; i++) { 247 var elem = queries[i]; 248 249 var query = elem.split("="); 250 obj[query[0]] = query[1]; 251 obj.user_query[query[0]] = query[1]; 252 } 253 254 // alias domain for vhost. 255 if (obj.domain) { 256 obj.vhost = obj.domain; 257 } 258 } 259 }; 260 261 self.pc = new RTCPeerConnection(null); 262 263 // To keep api consistent between player and publisher. 264 // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack 265 // @see https://webrtc.org/getting-started/media-devices 266 self.stream = new MediaStream(); 267 268 return self; 269} 270 271// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter 272// Async-await-promise based SRS RTC Player. 273function SrsRtcPlayerAsync() { 274 var self = {}; 275 276 // @see https://github.com/rtcdn/rtcdn-draft 277 // @url The WebRTC url to play with, for example: 278 // webrtc://r.ossrs.net/live/livestream 279 // or specifies the API port: 280 // webrtc://r.ossrs.net:11985/live/livestream 281 // webrtc://r.ossrs.net:80/live/livestream 282 // or autostart the play: 283 // webrtc://r.ossrs.net/live/livestream?autostart=true 284 // or change the app from live to myapp: 285 // webrtc://r.ossrs.net:11985/myapp/livestream 286 // or change the stream from livestream to mystream: 287 // webrtc://r.ossrs.net:11985/live/mystream 288 // or set the api server to myapi.domain.com: 289 // webrtc://myapi.domain.com/live/livestream 290 // or set the candidate(eip) of answer: 291 // webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185 292 // or force to access https API: 293 // webrtc://r.ossrs.net/live/livestream?schema=https 294 // or use plaintext, without SRTP: 295 // webrtc://r.ossrs.net/live/livestream?encrypt=false 296 // or any other information, will pass-by in the query: 297 // webrtc://r.ossrs.net/live/livestream?vhost=xxx 298 // webrtc://r.ossrs.net/live/livestream?token=xxx 299 self.play = async function(url) { 300 var conf = self.__internal.prepareUrl(url); 301 self.pc.addTransceiver("audio", {direction: "recvonly"}); 302 self.pc.addTransceiver("video", {direction: "recvonly"}); 303 304 var offer = await self.pc.createOffer(); 305 await self.pc.setLocalDescription(offer); 306 var session = await new Promise(function(resolve, reject) { 307 // @see https://github.com/rtcdn/rtcdn-draft 308 var data = { 309 api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, 310 clientip: null, sdp: offer.sdp 311 }; 312 console.log("Generated offer: ", data); 313 314 const xhr = new XMLHttpRequest(); 315 xhr.onload = function() { 316 if (xhr.readyState !== xhr.DONE) return; 317 if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); 318 const data = JSON.parse(xhr.responseText); 319 console.log("Got answer: ", data); 320 return data.code ? reject(xhr) : resolve(data); 321 } 322 xhr.open('POST', conf.apiUrl, true); 323 xhr.setRequestHeader('Content-type', 'application/json'); 324 xhr.send(JSON.stringify(data)); 325 }); 326 await self.pc.setRemoteDescription( 327 new RTCSessionDescription({type: 'answer', sdp: session.sdp}) 328 ); 329 session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; 330 331 return session; 332 }; 333 334 // Close the player. 335 self.close = function() { 336 self.pc && self.pc.close(); 337 self.pc = null; 338 }; 339 340 // The callback when got remote track. 341 // Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream 342 self.ontrack = function (event) { 343 // https://webrtc.org/getting-started/remote-streams 344 self.stream.addTrack(event.track); 345 }; 346 347 // Internal APIs. 348 self.__internal = { 349 defaultPath: '/rtc/v1/play/', 350 prepareUrl: function (webrtcUrl) { 351 var urlObject = self.__internal.parse(webrtcUrl); 352 353 // If user specifies the schema, use it as API schema. 354 var schema = urlObject.user_query.schema; 355 schema = schema ? schema + ':' : window.location.protocol; 356 357 var port = urlObject.port || 1985; 358 if (schema === 'https:') { 359 port = urlObject.port || 443; 360 } 361 362 // @see https://github.com/rtcdn/rtcdn-draft 363 var api = urlObject.user_query.play || self.__internal.defaultPath; 364 if (api.lastIndexOf('/') !== api.length - 1) { 365 api += '/'; 366 } 367 368 var apiUrl = schema + '//' + urlObject.server + ':' + port + api; 369 for (var key in urlObject.user_query) { 370 if (key !== 'api' && key !== 'play') { 371 apiUrl += '&' + key + '=' + urlObject.user_query[key]; 372 } 373 } 374 // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v 375 apiUrl = apiUrl.replace(api + '&', api + '?'); 376 377 var streamUrl = urlObject.url; 378 379 return { 380 apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, 381 tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) 382 }; 383 }, 384 parse: function (url) { 385 // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri 386 var a = document.createElement("a"); 387 a.href = url.replace("rtmp://", "http://") 388 .replace("webrtc://", "http://") 389 .replace("rtc://", "http://"); 390 391 var vhost = a.hostname; 392 var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); 393 var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); 394 395 // parse the vhost in the params of app, that srs supports. 396 app = app.replace("...vhost...", "?vhost="); 397 if (app.indexOf("?") >= 0) { 398 var params = app.slice(app.indexOf("?")); 399 app = app.slice(0, app.indexOf("?")); 400 401 if (params.indexOf("vhost=") > 0) { 402 vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); 403 if (vhost.indexOf("&") > 0) { 404 vhost = vhost.slice(0, vhost.indexOf("&")); 405 } 406 } 407 } 408
409 // when vhost equals to server, and server is ip, 410 // the vhost is __defaultVhost__ 411 if (a.hostname === vhost) { 412 var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; 413 if (re.test(a.hostname)) { 414 vhost = "__defaultVhost__"; 415 } 416 } 417 418 // parse the schema 419 var schema = "rtmp"; 420 if (url.indexOf("://") > 0) { 421 schema = url.slice(0, url.indexOf("://")); 422 } 423 424 var port = a.port; 425 if (!port) { 426 // Finger out by webrtc url, if contains http or https port, to overwrite default 1985. 427 if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) { 428 port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443; 429 } 430 431 // Guess by schema. 432 if (schema === 'http') { 433 port = 80; 434 } else if (schema === 'https') { 435 port = 443; 436 } else if (schema === 'rtmp') { 437 port = 1935; 438 } 439 } 440 441 var ret = { 442 url: url, 443 schema: schema, 444 server: a.hostname, port: port, 445 vhost: vhost, app: app, stream: stream 446 }; 447 self.__internal.fill_query(a.search, ret); 448 449 // For webrtc API, we use 443 if page is https, or schema specified it. 450 if (!ret.port) { 451 if (schema === 'webrtc' || schema === 'rtc') { 452 if (ret.user_query.schema === 'https') { 453 ret.port = 443; 454 } else if (window.location.href.indexOf('https://') === 0) { 455 ret.port = 443; 456 } else { 457 // For WebRTC, SRS use 1985 as default API port. 458 ret.port = 1985; 459 } 460 } 461 } 462 463 return ret; 464 }, 465 fill_query: function (query_string, obj) { 466 // pure user query object. 467 obj.user_query = {}; 468 469 if (query_string.length === 0) { 470 return; 471 } 472 473 // split again for angularjs. 474 if (query_string.indexOf("?") >= 0) { 475 query_string = query_string.split("?")[1]; 476 } 477 478 var queries = query_string.split("&"); 479 for (var i = 0; i < queries.length; i++) { 480 var elem = queries[i]; 481 482 var query = elem.split("="); 483 obj[query[0]] = query[1]; 484 obj.user_query[query[0]] = query[1]; 485 } 486 487 // alias domain for vhost. 488 if (obj.domain) { 489 obj.vhost = obj.domain; 490 } 491 } 492 }; 493 494 self.pc = new RTCPeerConnection(null); 495 496 // Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams 497 self.stream = new MediaStream(); 498 499 // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack 500 self.pc.ontrack = function(event) { 501 if (self.ontrack) { 502 self.ontrack(event); 503 } 504 }; 505 506 return self; 507} 508 509// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter 510// Async-awat-prmise based SRS RTC Publisher by WHIP. 511function SrsRtcWhipWhepAsync() { 512 var self = {}; 513 514 // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia 515 self.constraints = { 516 audio: true, 517 video: { 518 width: {ideal: 320, max: 576} 519 } 520 }; 521 522 // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ 523 // @url The WebRTC url to publish with, for example: 524 // http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream 525 self.publish = async function (url) { 526 if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`); 527 528 self.pc.addTransceiver("audio", {direction: "sendonly"}); 529 self.pc.addTransceiver("video", {direction: "sendonly"}); 530 531 if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { 532 throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); 533 } 534 var stream = await navigator.mediaDevices.getUserMedia(self.constraints); 535 536 // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
537 stream.getTracks().forEach(function (track) { 538 self.pc.addTrack(track); 539 540 // Notify about local track when stream is ok. 541 self.ontrack && self.ontrack({track: track}); 542 }); 543 544 var offer = await self.pc.createOffer(); 545 await self.pc.setLocalDescription(offer); 546 const answer = await new Promise(function (resolve, reject) { 547 console.log("Generated offer: ", offer); 548 549 const xhr = new XMLHttpRequest(); 550 xhr.onload = function() { 551 if (xhr.readyState !== xhr.DONE) return; 552 if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); 553 const data = xhr.responseText; 554 console.log("Got answer: ", data); 555 return data.code ? reject(xhr) : resolve(data); 556 } 557 xhr.open('POST', url, true); 558 xhr.setRequestHeader('Content-type', 'application/sdp'); 559 xhr.send(offer.sdp); 560 }); 561 await self.pc.setRemoteDescription( 562 new RTCSessionDescription({type: 'answer', sdp: answer}) 563 ); 564 565 return self.__internal.parseId(url, offer.sdp, answer); 566 }; 567 568 // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ 569 // @url The WebRTC url to play with, for example: 570 // http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream 571 self.play = async function(url) { 572 if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`); 573 574 self.pc.addTransceiver("audio", {direction: "recvonly"}); 575 self.pc.addTransceiver("video", {direction: "recvonly"}); 576 577 var offer = await self.pc.createOffer(); 578 await self.pc.setLocalDescription(offer); 579 const answer = await new Promise(function(resolve, reject) { 580 console.log("Generated offer: ", offer); 581 582 const xhr = new XMLHttpRequest(); 583 xhr.onload = function() { 584 if (xhr.readyState !== xhr.DONE) return; 585 if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); 586 const data = xhr.responseText; 587 console.log("Got answer: ", data); 588 return data.code ? reject(xhr) : resolve(data); 589 } 590 xhr.open('POST', url, true); 591 xhr.setRequestHeader('Content-type', 'application/sdp'); 592 xhr.send(offer.sdp); 593 }); 594 await self.pc.setRemoteDescription( 595 new RTCSessionDescription({type: 'answer', sdp: answer}) 596 ); 597 598 return self.__internal.parseId(url, offer.sdp, answer); 599 }; 600 601 // Close the publisher. 602 self.close = function () { 603 self.pc && self.pc.close(); 604 self.pc = null; 605 }; 606 607 // The callback when got local stream. 608 // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack 609 self.ontrack = function (event) { 610 // Add track to stream of SDK. 611 self.stream.addTrack(event.track); 612 }; 613 614 self.pc = new RTCPeerConnection(null); 615 616 // To keep api consistent between player and publisher. 617 // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack 618 // @see https://webrtc.org/getting-started/media-devices 619 self.stream = new MediaStream(); 620 621 // Internal APIs. 622 self.__internal = { 623 parseId: (url, offer, answer) => { 624 let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length); 625 sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':'; 626 sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length); 627 sessionid = sessionid.substr(0, sessionid.indexOf('\n')); 628 629 const a = document.createElement("a"); 630 a.href = url; 631 return { 632 sessionid: sessionid, // Should be ice-ufrag of answer:offer. 633 simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/', 634 }; 635 }, 636 }; 637 638 // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack 639 self.pc.ontrack = function(event) { 640 if (self.ontrack) { 641 self.ontrack(event); 642 } 643 }; 644 645 return self; 646} 647 648// Format the codec of RTCRtpSender, kind(audio/video) is optional filter. 649// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs 650function SrsRtcFormatSenders(senders, kind) { 651 var codecs = [];
652 senders.forEach(function (sender) { 653 var params = sender.getParameters(); 654 params && params.codecs && params.codecs.forEach(function(c) { 655 if (kind && sender.track.kind !== kind) { 656 return; 657 } 658 659 if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) { 660 return; 661 } 662 663 var s = ''; 664 665 s += c.mimeType.replace('audio/', '').replace('video/', ''); 666 s += ', ' + c.clockRate + 'HZ'; 667 if (sender.track.kind === "audio") { 668 s += ', channels: ' + c.channels; 669 } 670 s += ', pt: ' + c.payloadType; 671 672 codecs.push(s); 673 }); 674 }); 675 return codecs.join(", "); 676} 677
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.